パンダのiterrowsを使用すると、パフォーマンスが非常に悪いことに気付きました。
これは他の人が経験していることですか? iterrowsに固有のものですか?特定のサイズのデータに対してこの関数を避けるべきですか?
この議論 GitHubで、データフレームにdtypeが混在している場合に発生すると考えられましたが、以下の簡単な例では、1つのdtype(float64)を使用している場合でも表示されます。私のマシンでは36秒かかります:
import pandas as pd
import numpy as np
import time
s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})
start = time.time()
i=0
for rowindex, row in dfa.iterrows():
i+=1
end = time.time()
print end - start
なぜ適用のようなベクトル化された操作がこれほど速くなるのですか?行ごとの反復も行われているはずだと思います。
私の場合はiterrowsを使用しない方法がわかりません(これは将来の質問のために保存します)。したがって、この繰り返しを一貫して回避できた場合は、お気軽にお問い合わせください。別のデータフレームのデータに基づいて計算を行っています。ありがとうございました!
---編集:実行したいものの簡易版が以下に追加されました---
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b'],
'number1':[50,-10]}
t2 = {'letter':['a','a','b','b'],
'number2':[0.2,0.5,0.1,0.4]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])
#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.ix[row_index,] = optimize(t2info,row['number1'])
#%% Define optimization
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2']*t1info)
maxrow = calculation.index(max(calculation))
return t2info.ix[maxrow]
一般に、iterrows
は非常に特定の場合にのみ使用する必要があります。これは、さまざまな操作のパフォーマンスの一般的な優先順位です。
_1) vectorization
2) using a custom cython routine
3) apply
a) reductions that can be performed in cython
b) iteration in python space
4) itertuples
5) iterrows
6) updating an empty frame (e.g. using loc one-row-at-a-time)
_
通常、カスタムcythonルーチンの使用は複雑すぎるため、ここでは省略します。
1)ベクトル化は常に最初で最良の選択です。ただし、明らかな方法でベクトル化できないケースの小さなセットがあります(ほとんどが再発を伴います)。さらに、小さなフレームでは、他の方法を実行する方が速い場合があります。
3)適用には、canが含まれます。通常、Cython空間のイテレータによって実行されます(これはパンダで内部的に実行されます)(これがケースです)。
これは、適用式内で何が行われているかに依存します。例えばdf.apply(lambda x: np.sum(x))
は非常に迅速に実行されます(もちろんdf.sum(1)
はさらに優れています)。ただし、次のようなものです。df.apply(lambda x: x['b'] + 1)
は、pythonスペースで実行されるため、処理が遅くなります。
4)itertuples
はデータをSeriesにボックス化せず、Tupleとして返します
5)iterrows
は、データをシリーズにボックス化します。これが本当に必要でない限り、別の方法を使用してください。
6)空のフレームを一度に1行ずつ更新します。この方法があまりにも多く使用されているのを見てきました。はるかに遅いです。おそらく一般的な場所です(そして、いくつかのpython構造)に対してはかなり高速ですが)、DataFrameはインデックス作成に関してかなりの数のチェックを行うため、一度に行を更新するのは常に非常に遅くなります。新しい構造とconcat
を作成する方がずっと良い。
Numpyとpandasのベクトル演算ははるかに速い Vanillaのスカラー演算よりもPythonいくつかの理由で:
Amortized type lookup:Pythonは動的に型付けされた言語であるため、配列内の各要素には実行時のオーバーヘッドがあります。ただし、Numpy(したがってパンダ)は、 C(多くの場合Cython経由):配列のタイプは反復の開始時にのみ決定されます;この節約だけでも最大のメリットの1つです。
キャッシュの改善:C配列の反復処理はキャッシュに優しいため、非常に高速です。 A pandas DataFrameは「列指向のテーブル」です。つまり、各列は実際には単なる配列です。したがって、DataFrameで実行できるネイティブアクション(列)は、キャッシュミスがほとんどありません。
並列処理の機会が増える:SIMD命令を介して単純なC配列を操作できます。 Numpyの一部では、CPUとインストールプロセスに応じてSIMDが有効になります。並列処理の利点は、静的型付けやキャッシュの改善ほど劇的ではありませんが、それでも確かな勝利です。
ストーリーの教訓:NumpyとPandaでベクター演算を使用します。 Pythonこれらの操作はCプログラマーがとにかく手で書いたものとまったく同じであるという単純な理由のために)のスカラー操作よりも高速です。 SIMD命令が埋め込まれたループ。)
問題を解決する方法は次のとおりです。これはすべてベクトル化されています。
In [58]: df = table1.merge(table2,on='letter')
In [59]: df['calc'] = df['number1']*df['number2']
In [60]: df
Out[60]:
letter number1 number2 calc
0 a 50 0.2 10
1 a 50 0.5 25
2 b -10 0.1 -1
3 b -10 0.4 -4
In [61]: df.groupby('letter')['calc'].max()
Out[61]:
letter
a 25
b -1
Name: calc, dtype: float64
In [62]: df.groupby('letter')['calc'].idxmax()
Out[62]:
letter
a 1
b 2
Name: calc, dtype: int64
In [63]: df.loc[df.groupby('letter')['calc'].idxmax()]
Out[63]:
letter number1 number2 calc
1 a 50 0.5 25
2 b -10 0.1 -1
別のオプションは、to_records()
を使用することです。これは、itertuples
とiterrows
の両方よりも高速です。
しかし、あなたの場合、他の種類の改善の余地があります。
これが私の最終的な最適化バージョンです
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
# np.multiply is in general faster than "x * y"
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
ベンチマークテスト:
-- iterrows() --
100 loops, best of 3: 12.7 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
-- itertuple() --
100 loops, best of 3: 12.3 ms per loop
-- to_records() --
100 loops, best of 3: 7.29 ms per loop
-- Use group by --
100 loops, best of 3: 4.07 ms per loop
letter number2
1 a 0.5
2 b 0.1
4 c 5.0
5 d 4.0
-- Avoid multiplication --
1000 loops, best of 3: 1.39 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
完全なコード:
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b','c','d'],
'number1':[50,-10,.5,3]}
t2 = {'letter':['a','a','b','b','c','d','c'],
'number2':[0.2,0.5,0.1,0.4,5,4,1]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index)
print('\n-- iterrows() --')
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2'] * t1info)
maxrow_in_t2 = calculation.index(max(calculation))
return t2info.loc[maxrow_in_t2]
#%% Iterate through filtering relevant data, optimizing, returning info
def iterthrough():
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.iloc[row_index,:] = optimize(t2info, row['number1'])
%timeit iterthrough()
print(table3)
print('\n-- itertuple() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.itertuples():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.itertuples():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- to_records() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.to_records():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.to_records():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- Use group by --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
for index, letter, n1 in table1.to_records():
t2 = table2.iloc[grouped.groups[letter]]
calculation = t2.number2 * n1
maxrow = calculation.argsort().iloc[-1]
ret.append(t2.iloc[maxrow])
global table3
table3 = pd.DataFrame(ret)
%timeit iterthrough()
print(table3)
print('\n-- Even Faster --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
%timeit iterthrough()
print(table3)
最終バージョンは、元のコードよりもほぼ10倍高速です。戦略は次のとおりです。
groupby
を使用して、値の比較を繰り返し行わないようにします。to_records
生のnumpy.recordsオブジェクトにアクセスします。はい、Pandas itertuples()はiterrows()よりも高速です。ドキュメントを参照できます。 https://pandas.pydata.org/pandas-docs/stable/reference/ api/pandas.DataFrame.iterrows.html
"行の反復中にdtypeを保持するには、値の名前付きタプルを返し、一般にiterrowsよりも高速なitertuples()を使用することをお勧めします。"