Pythonの統計ライブラリpandasでは、データフレームを読み込む際、一度にメモリ上に展開するので、巨大なデータ&非力なPCではメモリが圧迫される。
また、ある程度は型推論してくれるが、多少メモリ効率の悪い部分がある。
もし読み込みたいカラムが限定されていたり、データ型が判明しているカラムがあれば、読み込み時に指定することで、メモリを削減できる。
read_csv の引数の内、メモリ節約に効果のある以下の3つを紹介する。
読み込むカラムを指定し、不要なカラムを読み込まないようにする。引数として受け付けるデータは以下のいずれか。
[0, 2, 3, 5]
['name', 'age', 'weight']
lambda col: col.lower() in ['aaa', 'bbb', 'ccc']
name , age , sex, weight, height ドラえもん, -100, M , 129.3 , 129.3 野比のび太, 10 , M , 33.2 , 140.0 源静香 , 10 , F , , 136.9
1 2 3 4 5 6 7 8 9 |
usecols = [ 'name' , 'age' , 'weight' ] df = pd.read_csv( 'sample.csv' , usecols = usecols) print (df) # => 指定したカラムのみからなるDataFrameができる # name, age, weight # ドラえもん, -100, 129.3 # 野比のび太, 10, 140.0 # 源静香, 10, nan |
※数値は適当
データ型を適切に指定する。受け付けるデータは以下のいずれか。
np.int64
{'name': np.object, 'age': np.int8, 'weight': np.float32}
pandasでは、カラムがint型やfloat型と判定された場合、64bit環境ならnp.int64、np.float64が基本的には使われる。 取り得る最大値が大きくないなら、int8、int16、int32などを使うことで、メモリ量を削減できる。
文字列は、np.object型として読み込む。np.objectはpythonオブジェクトへのポインタを格納する、何でもアリな型。
メモリの節約からは話は逸れるが、「数値に見えるけど文字列として処理したい」カラムをはじめから文字列として読み込むのにも役立つ。
np.int型では整数しか表現できないため、int型を指定したカラムに欠損値が含まれる場合は読み込みエラーになる。
np.floatなら欠損値を表現できる。以下の選択肢を取ることになる。
1 2 3 4 5 6 |
# 2. カラムの欠損値を-1で埋め、int型にキャスト df[ 'aaa' ] = df[ 'aaa' ].fillna( - 1 ).astype(np.int32) # 3. カラムの欠損値を除去し、int型にキャスト df.dropna(subset = [ 'aaa' ], inplace = True ) df[ 'aaa' ] = df[ 'aaa' ].astype(np.int32) |
上記の節約をしても、とにかくレコード数が多すぎてままならないという場合は、chunksizeに整数値を指定すると、その数ずつ読み込んでくれる。
read_csv()の返値は、「chunksize個のレコードから成るDataFrameを逐次読み込むイテレータ」となる。
1 2 3 4 5 6 7 8 9 |
dfitr = pd.read_csv( 'sample.csv' , chunksize = 10000 ) df = pd.DataFrame(columns = [ 'A' , 'B' , 'C' ]) # 処理結果を順次結合していくための空のDataFrame for subdf in dfitr: # なんか処理 # 必要なカラムのみ残してdfに結合 (A,B,Cカラムのみ残る) df = pd.concat([df, subdf], join = 'inner' , ignore_index = True ) |
いくつかの決まった値しか取らないデータがそれぞれ何個存在するかを、chunksizeを用いながら集計する。
事前に取り得る値が全てわかっていなくても大丈夫。
df.groupby
後、1カラムだけを抜き出した後の count()
は、enumの各値をindexとした Series オブジェクトを返すSeries.add(fill_value=0)
を使うS1 + S2
の加算では結果が NaN になるところ、この方法だと0で初期化してくれるdtype=int
型同士の加算でも、結果がfloat型になってしまう(?)ので、最後にint型になおす
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
counter = pd.Series(dtype = np.float64) for df in pd.read_csv( 'sample.csv' , chunksize = 10000 ): counter = counter.add(df.groupby( 'enum' )[ 'id' ].count(), fill_value = 0 ) counter = counter.astype(np.int64) print (counter) # ==== # enum # cat 2 # dog 3 # rabbit 1 # Name: id, dtype: int64 |
あるいは、以下のpandasのラッパを使うことで、巨大データに対する効率的な処理をやってくれるらしい(詳しくは調べていない)
また、後発の以下のようなツールもある。