CSVテストデータをpandasを用いて既存データから流用生成する方法について模索してみた

pandasを用いて、既存のCSVデータを元に様々なパターンでのテストデータ作成を試してみました。
2019.04.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

テストケースや脆弱性診断に根拠のある効果を求める場合、用いるデータが想定している問題点をカバーしているかどうかの妥当性は重要です。

データベースのテストに用いるSQLにおいては

  • SJIS波ダッシュ問題検知
  • シングルクォート由来の各種脆弱性や不具合検知

の2点をカバーする上で L'Arc~en~Ciel が欠かせない方も多いと思われます。

CSVにも上記は勿論該当しますが、特にAWSの請求(AWS Cost and Usage Report)サービスを利用する際には/(スラッシュ)等の対処も必要になります。

今回は、既にテストに転用できそうな何らかのデータが存在するという前提にて、CSVのテストデータ妥当性について効率よくカバーする方法を模索してみました。

利用するライブラリ

pandasを利用します。機械学習で利用されているライブラリですが、以下の操作がテストデータ作成にも効果的だと思ったためです。

  • 欠損値の確認
  • 欠損値の削除
  • 欠損値の補完

インストール

pipenv install pandas

使い方

ドキュメントだけをみるとSeriesDataFrame等とっつきにくそうなイメージがありますが、CSVを触る際に必要な操作は最短で以下の通りです。

import pandas as pd
df = pd.read_csv("path/to/csv")
print(df[0:1])

gzipで圧縮されているcsvも問題なく読み取る他、S3上のCSVも読み取れるため、以下の様に直接指定も可能です。

import pandas as pd
df = pd.read_csv("s3://<backet>/path/to/csv.gz")
print(df[0:1])

様々なケースのデータを作成する

ざっくりと想定できるケースとして以下が考えられます。

  • 全て欠損
  • 一部欠損
  • 欠損なし

全て欠損しているデータを出力する

全部のフィールドをNaNで埋めてしまえば問題はないのですが、フィールド個数分を手作業はナンセンスですし、できれば同じpandasで処理の可視性を上げたいところです。

そこで、numpyの配列欠損値numpy.nanを利用してまるっと置き換えることにします。 参考:Replace invalid values with None in Pandas DataFrame

import numpy as np
import pandas as pd
df = pd.read_csv("s3://<backet>/path/to/csv.gz")
# regex=True にて、検索条件を正規表現化
print(df.replace('.*', np.nan, regex=True)[0:2])

一部欠損したデータを出力する

一部欠損したレコードだけピックアップします。

import numby as np
import pandas as pd
df = pd.read_csv("s3://<backet>/path/to/csv.gz")
# axis=0 にて行単位に、how='all' で全フィールド欠損したレコードを除外
# df.isnull().any()でいずれかのフィールドが欠損したレコードのみを取得
print(df.dropna(axis = 0, how = 'all').loc[:,df.isnull().any()][0:2])

欠損していないデータを出力する

全てのフィールドが埋まっているレコードは意外と見つけ難いものです。存在しないケースもあります。そこで、以下の手順をとります。

  • 欠損したフィールドを最頻値で穴埋めする
  • それでも埋まらなかった列があれば、想定される型の値で埋める

それに伴う問題点は以下の通りです。

  • レコード内フィールド間に整合性が存在する場合は維持できなくなる
  • 列まるごとの穴埋めはフィールドの構成によって方法が変わってくる

今回は、整合性を無視した上で、全列まるごとの穴埋めは0の値を決め打ちで入れていきます。

import numby as np
import pandas as pd
df = pd.read_csv("s3://<backet>/path/to/csv.gz")
# NaN値を該当列での最頻値(df.mode())で上書きしてから、
# 全レコードの全フィールドのNaNを0で上書き
print(df.fillna(df.mode()).replace(np.nan, '0')[0:2])

特定の文字列を収めたフィールドにする

いずれかの方法で絞り込んだ後に値を差し替えます。

# 無理やりスラッシュも入れました
df.loc[1,1] = "L'Arc~en~Cie/l"

まとめ

今回はpandasの用途が限定的だったこともあり、利用した関数もほんの一部だけですが、使い方を知る一歩としては丁度よい塩梅だった感じです。

機械学習という分野で利用方法を調べようとすると雲を掴むような感覚になりますが、敢えて絞りこむことで何となく出来ること及びやり方が見えてくるため、迷った時は敢えて具体的なやり方で検索してみるのもよいと思います。

参考リンク