はじめに
クソデカ行列を目の前になんとなく pandas データフレームを使うと余裕でメモリアウトだったり,処理に何時間もかかってしまう.そこで,より軽く,より早くデータフレームを扱う際に個人的に気をつけている点を備忘録として置いておく.
※ 以下の記事は自分の経験とメモを文書に落としたものなので,詳しい比較(どのくらい早くなったか)は行なっていません.
もくじ
- 型の最適化
-
read_csv
で型を指定する - for は使わない
- おまけ
- まとめ
型の最適化
kaggle なんかのテーブルコンペで見かけない日はないこの関数.
型のサイズを最適化してメモリアウトを防ぎ高速化をする.
def reduce_mem_usage(df):
start_mem = df.memory_usage().sum() / 1024**2
print('Memory usage of DataFrame is {:.2f} MB'.format(start_mem))
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage().sum() / 1024**2
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
return df
read_csv
で型を指定する
型をこちらで指定することで型を特定する時間が省略でき,より高速にデータ読み込みができる.
カラムが大きすぎるデータにはあまり適していない(めんどくさい).
df = pd.read_csv('./train.csv', dtype={'a': int, 'b': float, 'c': str})
for は使わない
python は for ごと処理ごとにコンパイラが走るため遅い.そのため, for は極力使用せず, map
, apply
を使う.
ちなみに自分はこれらの関数を以下のように使い分けているが,実際何が違うのかわかっていない(誰が教えて)
map
列に対して辞書型を適応させる時
d = {'hoge':'ほげ', 'fuga':'ふが', 'piyo':'ぴよ'}
df['tmp'] = df['tmp'].map(d)
apply
groupby
により対象を絞ったデータに関数を適応する時
df['rolling_mean'] = df.groupby([id])['timeseries'].apply(lambda x:x.rolling(12, 1).mean())
行に対してデータを処理したい時
def cos_sim(v1, v2):
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
df = df.apply(lambda x: cos_sim(v1, x), axis=1) # return pd.Series
ぶっちゃけどちらも列に対して処理ができるので,気分で気分で使い分けている
ちなみに個人的な python での for の温度感は
100 iter → まぁまぁいいでしょう
1000 iter → ん?
10000 iter → ちょっ…
100000 iter → 正気ですか?
おまけ
「カテゴリごとに処理したけど, map
, apply
も使えないし…1試行に時間かかるし待ってられない…」みたいなことがある.
year_list = [2010, 2011, 2012, 2013]
for y in year_list:
df_y = df[df['year']==y]
'''処理'''
df_y_executed.to_csv('./savefolder/save_{y}.csv'.format(y=y))
現在, pandas では物理コアによる並列化がされていないため,リソースは余っているけど待ち時間が勿体無い.
そこで無理やり物理コアによる並列化をしてしまう.
このような時,自分の研究室では以下の方法をとる.
- カテゴリを分割して,csv に保存する(上の例だと [2010, 2011], [2012, 2013]のように分割して csv 保存)
- jupyter をもう一つ起動して 2 つの jupyter で処理を回す
これにより物理コアを2つ使い処理をすることができる.
#まとめ
ちまたでは Dask なるものがあり,メモリ以上のデータを並列化して処理ができるらしい.
pandas との互換性も高くいい感じだと聞いたので試したてみたい.