[解決済み] 既存の列から新しい列を作成するためのPandas applyとnp.vectorizeの性能比較
質問
Pandasのデータフレームを使用しており、既存のカラムの関数として新しいカラムを作成したいと考えています。との間の速度の違いについて良い議論を見たことがありません。
df.apply()
と
np.vectorize()
の2種類があり、こちらでお聞きしようと思いました。
パンダについて
apply()
関数は遅いです。私が測定したところ(以下にいくつかの実験で示します)では
np.vectorize()
を使うのは、DataFrame 関数を使うよりも 25 倍(またはそれ以上)速いです。
apply()
は、少なくとも私の 2016 MacBook Pro 上では。
これは予想された結果なのでしょうか、またその理由は?
例えば、次のようなデータフレームがあるとします。
N
の行があるとします。
N = 10
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
df.head()
# A B
# 0 78 50
# 1 23 91
# 2 55 62
# 3 82 64
# 4 99 80
さらに、2つのカラムの関数として新しいカラムを作成したいとします。
A
と
B
. 以下の例では、簡単な関数である
divide()
. この関数を適用するには
df.apply()
または
np.vectorize()
:
def divide(a, b):
if b == 0:
return 0.0
return float(a)/b
df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
df['result2'] = np.vectorize(divide)(df['A'], df['B'])
df.head()
# A B result result2
# 0 78 50 1.560000 1.560000
# 1 23 91 0.252747 0.252747
# 2 55 62 0.887097 0.887097
# 3 82 64 1.281250 1.281250
# 4 99 80 1.237500 1.237500
もし私が
N
を 100 万以上の現実的なサイズにすると、次のようになります。
np.vectorize()
が 25 倍以上速いことがわかります。
df.apply()
.
以下は完全なベンチマークコードです。
import pandas as pd
import numpy as np
import time
def divide(a, b):
if b == 0:
return 0.0
return float(a)/b
for N in [1000, 10000, 100000, 1000000, 10000000]:
print ''
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
start_epoch_sec = int(time.time())
df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
end_epoch_sec = int(time.time())
result_apply = end_epoch_sec - start_epoch_sec
start_epoch_sec = int(time.time())
df['result2'] = np.vectorize(divide)(df['A'], df['B'])
end_epoch_sec = int(time.time())
result_vectorize = end_epoch_sec - start_epoch_sec
print 'N=%d, df.apply: %d sec, np.vectorize: %d sec' % \
(N, result_apply, result_vectorize)
# Make sure results from df.apply and np.vectorize match.
assert(df['result'].equals(df['result2']))
結果は以下のようになります。
N=1000, df.apply: 0 sec, np.vectorize: 0 sec
N=10000, df.apply: 1 sec, np.vectorize: 0 sec
N=100000, df.apply: 2 sec, np.vectorize: 0 sec
N=1000000, df.apply: 24 sec, np.vectorize: 1 sec
N=10000000, df.apply: 262 sec, np.vectorize: 4 sec
もし
np.vectorize()
が一般的に常に
df.apply()
であるならば、なぜ
np.vectorize()
はもっと言及されないのでしょうか?私は、StackOverflow の投稿の中で唯一
df.apply()
のようなものです。
pandasは他のカラムの値に基づいて新しいカラムを作成します。
Pandasの 'apply' 関数を複数のカラムに使用するにはどうすればよいですか?
どのように解決するのですか?
私は 開始 は、PandasとNumPyの配列のパワーが高性能であることに由来していると言うことです。 ベクトル化された 計算から得られるものです。 1 ベクトル化された計算の全体のポイントは、高度に最適化されたCコードに計算を移動し、連続したメモリブロックを利用することによって、Pythonレベルのループを回避することです。 2
Pythonレベルのループ
ここで、いくつかのタイミングを見ることができます。以下は
すべて
を生成するPythonレベルのループです。
pd.Series
,
np.ndarray
または
list
オブジェクトが同じ値を含んでいます。データフレーム内の系列への割り当ての目的では、結果は同等です。
# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0
np.random.seed(0)
N = 10**5
%timeit list(map(divide, df['A'], df['B'])) # 43.9 ms
%timeit np.vectorize(divide)(df['A'], df['B']) # 48.1 ms
%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])] # 49.4 ms
%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)] # 112 ms
%timeit df.apply(lambda row: divide(*row), axis=1, raw=True) # 760 ms
%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1) # 4.83 s
%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()] # 11.6 s
いくつかの収穫がありました。
-
は
tuple
-に基づくメソッド (最初の 4 つ) はpd.Series
-ベースのメソッド (最後の 3 つ) よりも 1 倍効率的です。 -
np.vectorize
, リスト内包 +.zip
とmap
メソッド、つまり上位3つのメソッドはすべてほぼ同じ性能を持っています。これは、これらのメソッドがtuple
と は、Pandasのオーバーヘッドを回避するためにpd.DataFrame.itertuples
. -
を使用することにより、大幅な速度向上があります。
raw=True
とpd.DataFrame.apply
を付けるかどうか。このオプションは、NumPyの配列をカスタム関数に送り込む際に、代わりにpd.Series
オブジェクトの代わりにNumpyの配列をカスタム関数に送ります。
pd.DataFrame.apply
: 単なるループ
見るために まさに を見るには、関数を修正すればよいでしょう。
def foo(row):
print(type(row))
assert False # because you only need to see this once
df.apply(lambda row: foo(row), axis=1)
出力します。
<class 'pandas.core.series.Series'>
. Pandasの系列オブジェクトを作成し、渡し、問い合わせることは、NumPyの配列と比較して大きなオーバーヘッドを伴います。これは驚くことではありません。Pandasの系列は、インデックス、値、属性などを保持するための適切な量の雛形を含んでいます。
もう一度、同じ演習を
raw=True
でもう一度同じことをしてみてください。
<class 'numpy.ndarray'>
. これら全てはドキュメントに記載されていますが、実際に見た方がより説得力があります。
np.vectorize
: 偽のベクトル化
のドキュメントは
np.vectorize
には以下のような注意書きがあります。
ベクトル化された関数が評価するのは
pyfunc
の連続したタプルに対して評価します。 を評価します。 ただし、numpyのブロードキャストルールを使用します。
入力配列は同じ大きさなので、quot;broadcasting rules"はここでは関係ない。との並列は
map
は有益です。
map
のバージョンはほとんど同じ性能だからです。その
ソースコード
は何が起こっているかを示しています。
np.vectorize
は入力関数を
ユニバーサル関数
("ufunc") に変換します。
np.frompyfunc
. キャッシュなどの最適化もあり、多少のパフォーマンス向上は見込めます。
要するに
np.vectorize
は、Pythonレベルのループである
は
を実行しますが
pd.DataFrame.apply
は膨大なオーバーヘッドを追加します。JIT コンパイルはありません。
numba
で見られるような JIT コンパイルはありません (後述)。それは
は単なる便宜的なもので
.
真のベクトル化: あなたは べきこと を使用します。
なぜ上記のような違いがどこにも書かれていないのでしょうか。なぜなら、真にベクトル化された計算のパフォーマンスは、それらを無関係にするからです。
%timeit np.where(df['B'] == 0, 0, df['A'] / df['B']) # 1.17 ms
%timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0) # 1.96 ms
はい、これは上記のループ状のソリューションのうち最も速いものよりも ~40 倍も速いです。これらのどちらかは許容範囲内です。私の意見では、1つ目は簡潔で読みやすく、効率的です。他の方法だけを見て、例えば
numba
のように、パフォーマンスが重要で、これがボトルネックの一部である場合のみ、他のメソッドに目を向けてください。
numba.njit
: より大きな効率
ループの場合
は
が実行可能であると考えられる場合、通常は
numba
を介して最適化されます。
確かに
numba
は
マイクロ秒
. 面倒な作業をしなければ、これよりはるかに効率的になることは難しいでしょう。
from numba import njit
@njit
def divide(a, b):
res = np.empty(a.shape)
for i in range(len(a)):
if b[i] != 0:
res[i] = a[i] / b[i]
else:
res[i] = 0
return res
%timeit divide(df['A'].values, df['B'].values) # 717 µs
使用方法
@njit(parallel=True)
を使うことで、より大きな配列のためのさらなるブーストを提供できるかもしれません。
1
数値型は以下の通りです。
int
,
float
,
datetime
,
bool
,
category
. それらは
を除く
object
d型であり、連続したメモリブロックに保持することができます。
2 NumPyの演算がPythonに比べて効率的である理由は、少なくとも2つあります。
- Pythonではすべてがオブジェクトです。これには、Cとは異なり、数値も含まれます。したがって、Pythonの型は、ネイティブCの型には存在しないオーバーヘッドを持ちます。
- NumPyのメソッドは通常C言語ベースです。加えて、最適化されたアルゴリズム は可能な限り使用されます。
関連
-
[解決済み] pandasで1つの列を除くすべての列を選択する方法は?
-
[解決済み] Pandas DataFrameからカラムを削除する
-
[解決済み] 既存のDataFrameに新しい列を追加する方法は?
-
[解決済み] Typeから新しいオブジェクトのインスタンスを作成する方法
-
[解決済み] pandas が他の列の値に基づいて新しい列を作成する / 複数の列の関数を行単位で適用する
-
[解決済み] Numpyの配列からPandas DataFrameを作成する。インデックスカラムとカラムヘッダはどのように指定するのですか?
-
[解決済み】Pandas DataFrameのカラムヘッダからリストを取得する。
-
[解決済み】pandasの関数をカラムに適用して、複数の新しいカラムを作成する?
-
[解決済み] Pythonの構文に新しいステートメントを追加することはできますか?
-
[解決済み] Spyderを仮想環境で動作させるには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] pandas が他の列の値に基づいて新しい列を作成する / 複数の列の関数を行単位で適用する
-
[解決済み】複数のカラムを参照するPandasの「apply」関数が動作しないのはなぜですか?[クローズド]
-
[解決済み] 私のコードでpandas apply()を使用したい(したくない)のはどんなときですか?
-
[解決済み] DataFrameの文字列、dtypeがobjectの場合
-
[解決済み] Jupyterノートブックでenv変数を設定する方法
-
[解決済み] Python 2.7サポート終了?
-
[解決済み] Django Rest Framework ファイルアップロード
-
[解決済み] Pythonの検索パスを他のソースに展開する
-
[解決済み] Python の sorted() はどのようなアルゴリズムを使っているのですか?重複
-
[解決済み] pandas iterrowsは性能に問題がありますか?