LoginSignup
85
85

More than 5 years have passed since last update.

今すぐ使えるpandas高速化テクニック

Last updated at Posted at 2016-12-10

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の平均値を求める場合、下記のように処理を記述すると思われる

Badパターン
import pandas as pd

sr_means = df.mean()
mean_height = sr_means['height'] 
mean_weight = sr_means['weight']

しかし、文字列を含む列 name のせいで上記コードの場合、計算に非常に時間がかかる

下記のように記述を変更することで、処理が桁違いに高速になる

Goodパターン
import pandas as pd

sr_means = df[['height', 'weight']].mean()
mean_height = sr_means['height'] 
mean_weight = sr_means['weight']

追記:書いた後に気になって調べたら、数値だけで行うオプションが合った

追記:Goodパターン
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 を使っていきましょう。
(高階関数が何なのかについてはこの記事では割愛)

Badパターン
# 要素に対して round関数 を適用する
for idx in range(len(df_test['weight'].index)):
    df_test['weight'][idx] = round(df_test['weight'][idx])

map を使った下記に書き直す

Goodパターン
# 要素に対して 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日かかっていたところを、数分で処理できるようになったということがあったので、
どんどん改善していきたいですね。

85
85
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
85