pandasを使って大量データ処理する場合、
数GBの処理に数十分〜数時間、下手したら数日かかるということが往々にしてある
処理が遅いと進む作業も進まなくなるので
簡単なソースコード修正で高速化出来る方法をメモ
DataFrameのsumやmeanは数値データのみで行う
index | name | weight | height |
---|---|---|---|
0 | Tanaka | 160.1 | 60.1 |
1 | Suzuki | 172.4 | 75.0 |
2 | Saitou | 155.8 | 42.2 |
... | |||
999998 | Morita | 167.9 | 94.07 |
999999 | Satou | 177.7 | 80.3 |
例えば、上記のようなデータフレームがあり、
weightとheightの平均値を求める場合、下記のように処理を記述すると思われる
import pandas as pd
sr_means = df.mean()
mean_height = sr_means['height']
mean_weight = sr_means['weight']
しかし、文字列を含む列 name のせいで上記コードの場合、計算に非常に時間がかかる
下記のように記述を変更することで、処理が桁違いに高速になる
import pandas as pd
sr_means = df[['height', 'weight']].mean()
mean_height = sr_means['height']
mean_weight = sr_means['weight']
追記:書いた後に気になって調べたら、数値だけで行うオプションが合った
import pandas as pd
sr_means = df.mean(numeric_only = True)
mean_height = sr_means['height']
mean_weight = sr_means['weight']
実際に時間を計ってみる
import pandas as pd
import numpy as np
N = 100000
df_test = pd.DataFrame(
{
'name':['abc'] * N,
'weight': np.random.normal(60, 5, N),
'height': np.random.normal(160, 5, N)
}
)
print("df_test.mean()")
%time df_test.mean()
print("df_test[['height', 'weight']].mean()")
%time df_test[['height', 'weight']].mean()
上記の結果が下記。
計算する列が一つ減ったということを考慮しても、後者の方が4桁ぐらい桁違いに早い。
df_test.mean()
Wall time: 3.06 s
df_test[['height', 'weight']].mean()
Wall time: 4 ms
高階関数(map)を使おう
例えば、列 weight を整数に丸める処理を行うとき round関数 を使いますが
pythonや今時ぽい書き方に慣れていないと、for文を使った方法で書きがち
高階関数 map を使っていきましょう。
(高階関数が何なのかについてはこの記事では割愛)
# 要素に対して round関数 を適用する
for idx in range(len(df_test['weight'].index)):
df_test['weight'][idx] = round(df_test['weight'][idx])
map を使った下記に書き直す
# 要素に対して round関数 を適用する
df_test['weight'] = df_test['weight'].map(round)
今回も実際に時間を測る。for文が遅すぎるのでデータ数を先ほどより減らす
def func(sr):
for idx in range(len(sr.index)):
sr[idx] = round(sr[idx])
return(sr)
N = 1000
df_test = pd.DataFrame(
{
'name':['abc'] * N,
'weight': np.random.normal(60, 5, N),
'height': np.random.normal(160, 5, N)
}
)
print("forの場合")
%time df_test['weight'] = func(df_test['weight'])
print("mapの場合")
%time df_test['weight'] = df_test['weight'].map(round)
その結果が下記。
mapなら光の速さで処理できることを、for文だととんでもなく遅くなる
たった100個のデータでこれなのだから、
100万〜1億のデータ扱うことを考えると恐ろしくなる
forの場合
Wall time: 22.1 s
mapの場合
Wall time: 0 ns
上記2つの処理を改善するだけで、
データ処理1日かかっていたところを、数分で処理できるようになったということがあったので、
どんどん改善していきたいですね。