データ科学IPythonノート7.15高性能Pandas
#7.15高性能Pandas:
プロトコル:CC BY-NC-SA 4.0
本節は『Pythonデータ科学マニュアル』(Python Data Science Handbook)の抜粋です.
前述の章では、PyDataテクノロジースタックの力は、NumPyとPandasが直感的な文法を通じて、基本的な操作をCにプッシュする能力に基づいて構築されていることを示しています.例えば、NumPyにおける量子化/ブロードキャスト操作、およびPandasのパケットタイプ操作などです.これらの抽象は多くの一般的な使用例に対して効率的で有効ですが、通常は一時的な中間オブジェクトの作成に依存し、計算時間とメモリ使用のオーバーヘッドを発生させる可能性があります.
バージョン0.13(2014年1月リリース)から、Pandasには、高価な中間配列割り当てを必要とせずに、Cと同じ速度の操作に直接アクセスできる実験的なツールが含まれています.これらは、Numexprパケットに依存する
NumPyとPandasが高速量子化操作をサポートしているのを見たことがあります.たとえば、2つの配列の要素を加算する場合:
「NumPy配列の計算:汎用関数」で説明したように、Pythonループまたは導出式による加算よりもはるかに高速です.
しかし、複合式を計算する場合、この抽象はそれほど有効ではない可能性があります.たとえば、次の式を考慮します.
NumPyは各サブエクスプレッションを計算するので、次のようになります.
すなわち、各中間ステップはメモリに明確に割り当てられる.
ここでの利点は,Numexprが完全な一時配列を用いないように式を計算するので,特に大型配列に対してNumPyよりも効率的であることである.ここで議論するPandas
効率的な操作のための
4つの
式を文字列にすることで、
この式の
Pandas v 0から.16から、
算術演算子
比較演算子
ビット単位演算子
さらに、ブール式での字面
オブジェクトのプロパティとインデックス
その他の演算子
関数呼び出し、条件文、ループ、その他のより複雑な構造などの他の操作は、現在
列単位の演算に使用
A
B
C
0
0.375506
0.406939
0.069938
1
0.069087
0.235615
0.154374
2
0.677945
0.433839
0.652324
3
0.264038
0.808055
0.347197
4
0.589161
0.252418
0.557789
上記の
カラム名は、解を要求する式の変数とみなされ、結果は私たちが望んでいる結果であることに注意してください.
先ほど説明したオプションに加えて、
A
B
C
0
0.375506
0.406939
0.069938
1
0.069087
0.235615
0.154374
2
0.677945
0.433839
0.652324
3
0.264038
0.808055
0.347197
4
0.589161
0.252418
0.557789
A
B
C
D
0
0.375506
0.406939
0.069938
11.187620
1
0.069087
0.235615
0.154374
1.973796
2
0.677945
0.433839
0.652324
1.704344
3
0.264038
0.808055
0.347197
3.087857
4
0.589161
0.252418
0.557789
1.508776
同じ方法で、既存の列を変更できます.
A
B
C
D
0
0.375506
0.406939
0.069938
-0.449425
1
0.069087
0.235615
0.154374
-1.078728
2
0.677945
0.433839
0.652324
0.374209
3
0.264038
0.808055
0.347197
-1.566886
4
0.589161
0.252418
0.557789
0.603708
ここでは、
これは、より効率的な計算として、マスク式に比べて読みやすく、理解しやすい.注意
パフォーマンス:これらの関数はいつ使用されますか?
これらの関数を使用するかどうかを考慮すると、計算時間とメモリ使用の2つの注意点があります.メモリの使用は最も予測可能な側面です.前述したように、NumPy配列またはPandas
ほぼこれに相当します.
一時
パフォーマンスの面では、システムメモリを超えていなくても、
実際には、従来の方法と
私たちはすでに
eval()
とquery()
原文:High-Performance Pandas:eval()
andquery()
ドラゴンプロトコル:CC BY-NC-SA 4.0
本節は『Pythonデータ科学マニュアル』(Python Data Science Handbook)の抜粋です.
前述の章では、PyDataテクノロジースタックの力は、NumPyとPandasが直感的な文法を通じて、基本的な操作をCにプッシュする能力に基づいて構築されていることを示しています.例えば、NumPyにおける量子化/ブロードキャスト操作、およびPandasのパケットタイプ操作などです.これらの抽象は多くの一般的な使用例に対して効率的で有効ですが、通常は一時的な中間オブジェクトの作成に依存し、計算時間とメモリ使用のオーバーヘッドを発生させる可能性があります.
バージョン0.13(2014年1月リリース)から、Pandasには、高価な中間配列割り当てを必要とせずに、Cと同じ速度の操作に直接アクセスできる実験的なツールが含まれています.これらは、Numexprパケットに依存する
eval()
およびquery()
関数である.このノートでは、それらの使用方法を徐々に紹介し、いつ使用するかを考慮できる経験則を提供します.query()
およびeval()
の動機:複合式NumPyとPandasが高速量子化操作をサポートしているのを見たことがあります.たとえば、2つの配列の要素を加算する場合:
import numpy as np
rng = np.random.RandomState(42)
x = rng.rand(1000000)
y = rng.rand(1000000)
%timeit x + y
# 100 loops, best of 3: 3.39 ms per loop
「NumPy配列の計算:汎用関数」で説明したように、Pythonループまたは導出式による加算よりもはるかに高速です.
%timeit np.fromiter((xi + yi for xi, yi in zip(x, y)), dtype=x.dtype, count=len(x))
# 1 loop, best of 3: 266 ms per loop
しかし、複合式を計算する場合、この抽象はそれほど有効ではない可能性があります.たとえば、次の式を考慮します.
mask = (x > 0.5) & (y < 0.5)
NumPyは各サブエクスプレッションを計算するので、次のようになります.
tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2
すなわち、各中間ステップはメモリに明確に割り当てられる.
x
およびy
配列が非常に大きい場合、メモリおよび計算オーバーヘッドが大量に発生する可能性があります.Numexprライブラリを使用すると、完全な中間配列を割り当てることなく、要素ごとにこのタイプの複合式を計算できます.Numexprドキュメントには詳細がありますが、このライブラリは文字列を受け入れており、計算したいNumPyスタイルの式を提供しています.import numexpr
mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
np.allclose(mask, mask_numexpr)
# True
ここでの利点は,Numexprが完全な一時配列を用いないように式を計算するので,特に大型配列に対してNumPyよりも効率的であることである.ここで議論するPandas
eval()
およびquery()
ツールは,概念的に類似しており,Numexprパケットに依存する.効率的な操作のための
pandas.eval()
Pandasのeval()
関数は、DataFrame
を使用して操作を効率的に計算する文字列式を受け入れます.例えば、以下のDataFrame
:import pandas as pd
nrows, ncols = 100000, 100
rng = np.random.RandomState(42)
df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols))
for i in range(4))
4つの
DataFrame
の合計を計算するには、典型的なPandasメソッドを使用します.%timeit df1 + df2 + df3 + df4
# 10 loops, best of 3: 87.1 ms per loop
式を文字列にすることで、
pd.eval
で同じ結果を計算できます.%timeit pd.eval('df1 + df2 + df3 + df4')
# 10 loops, best of 3: 42.2 ms per loop
この式の
eval()
バージョンの速度は約50%(メモリの使用量が少ない)向上し、同じ結果が得られました.np.allclose(df1 + df2 + df3 + df4,
pd.eval('df1 + df2 + df3 + df4'))
# True
pd.eval()
サポートされているアクションPandas v 0から.16から、
pd.eval()
は幅広い操作をサポートします.これらを実証するために、次の整数DataFrame
を使用します.df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 1000, (100, 3)))
for i in range(5))
算術演算子
pd.eval()
すべての演算子をサポートします.たとえば、次のようになります.result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
np.allclose(result1, result2)
# True
比較演算子
pd.eval()
チェーン式を含むすべての比較演算子をサポートします.result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval('df1 < df2 <= df3 != df4')
np.allclose(result1, result2)
# True
ビット単位演算子
pd.eval()
サポート&
および|
ビット別演算子:result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
np.allclose(result1, result2)
# True
さらに、ブール式での字面
and
およびor
の使用をサポートします.result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')
np.allclose(result1, result3)
# True
オブジェクトのプロパティとインデックス
pd.eval()
は、obj.attr
構文によるオブジェクト属性へのアクセスをサポートし、obj[index]
構文によるインデックスをサポートします.result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')
np.allclose(result1, result2)
# True
その他の演算子
関数呼び出し、条件文、ループ、その他のより複雑な構造などの他の操作は、現在
pd.eval()
で実装されていません.これらのより複雑な式を実行する場合は、Numexprライブラリ自体を使用します.列単位の演算に使用
DataFrame.eval()
Pandasにトップクラスのpd.eval()
関数があるように、DataFrame
にはeval()
メソッドがあり、その動作は似ています.eval()
メソッドの利点は、カラムが名前で参照できることです.このラベル付き配列を例として使用します.df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
df.head()
A
B
C
0
0.375506
0.406939
0.069938
1
0.069087
0.235615
0.154374
2
0.677945
0.433839
0.652324
3
0.264038
0.808055
0.347197
4
0.589161
0.252418
0.557789
上記の
pd.eval()
を使用すると、式を計算するために3つの列を使用できます.result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = pd.eval("(df.A + df.B) / (df.C - 1)")
np.allclose(result1, result2)
# True
DataFrame.eval()
メソッドでは、カラムを使用して式をより簡潔に解くことができます.result3 = df.eval('(A + B) / (C - 1)')
np.allclose(result1, result3)
# True
カラム名は、解を要求する式の変数とみなされ、結果は私たちが望んでいる結果であることに注意してください.
DataFrame.eval()
の付与先ほど説明したオプションに加えて、
DataFrame.eval()
は任意のカラムに値を割り当てることもできます.以前のDataFrame
を使用します.列A
とB
があります.df.head()
A
B
C
0
0.375506
0.406939
0.069938
1
0.069087
0.235615
0.154374
2
0.677945
0.433839
0.652324
3
0.264038
0.808055
0.347197
4
0.589161
0.252418
0.557789
C
を使用して、新しい列df.eval()
を作成し、他の列から計算された値を割り当てることができます.df.eval('D = (A + B) / C', inplace=True)
df.head()
A
B
C
D
0
0.375506
0.406939
0.069938
11.187620
1
0.069087
0.235615
0.154374
1.973796
2
0.677945
0.433839
0.652324
1.704344
3
0.264038
0.808055
0.347197
3.087857
4
0.589161
0.252418
0.557789
1.508776
同じ方法で、既存の列を変更できます.
df.eval('D = (A - B) / C', inplace=True)
df.head()
A
B
C
D
0
0.375506
0.406939
0.069938
-0.449425
1
0.069087
0.235615
0.154374
-1.078728
2
0.677945
0.433839
0.652324
0.374209
3
0.264038
0.808055
0.347197
-1.566886
4
0.589161
0.252418
0.557789
0.603708
'D'
のローカル変数DataFrame.eval()
メソッドは、Pythonローカル変数を使用することができる追加の構文をサポートします.以下を考慮します.column_mean = df.mean(1)
result1 = df['A'] + column_mean
result2 = df.eval('A + @column_mean')
np.allclose(result1, result2)
# True
ここでは、
DataFrame.eval()
文字タグ変数名はカラム名ではなく、カラムの名前空間とPythonオブジェクトの名前空間の2つの「名前空間」に関する式を効率的に計算できます.この@
文字は@
メソッドのみでサポートされ、DataFrame.eval()
関数ではサポートされません.pandas.eval()
関数は1つの(Python)ネーミングスペースにしかアクセスできないためです.pandas.eval ()
メソッドDataFrame.query()
は、DataFrame
メソッドと呼ばれる文字列に基づく別の評価方法がある.以下を考慮します.result1 = df[(df.A < 0.5) & (df.B < 0.5)]
result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
np.allclose(result1, result2)
# True
query()
について説明するときに使用する例と同様に、DataFrame.eval()
列に関する式です.しかし、DataFrame
構文では表現できません!逆に、このタイプのフィルタリング操作では、DataFrame.eval()
メソッドを使用できます.result2 = df.query('A < 0.5 and B < 0.5')
np.allclose(result1, result2)
# True
これは、より効率的な計算として、マスク式に比べて読みやすく、理解しやすい.注意
query()
メソッドもquery()
フラグを受け入れてローカル変数をマークします.Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
np.allclose(result1, result2)
# True
パフォーマンス:これらの関数はいつ使用されますか?
これらの関数を使用するかどうかを考慮すると、計算時間とメモリ使用の2つの注意点があります.メモリの使用は最も予測可能な側面です.前述したように、NumPy配列またはPandas
@
に関する各複合式は、暗黙的に作成された一時配列を生成します.たとえば、これは次のようになります.x = df[(df.A < 0.5) & (df.B < 0.5)]
ほぼこれに相当します.
tmp1 = df.A < 0.5
tmp2 = df.B < 0.5
tmp3 = tmp1 & tmp2
x = df[tmp3]
一時
DataFrame
のサイズが利用可能なシステムメモリ(通常は数千メガバイト)に比べて大きい場合、DataFrame
またはeval()
式を使用するのは良いアイデアです.配列の大体のサイズ(バイト単位)は、次の方法で確認できます.df.values.nbytes
# 32000
パフォーマンスの面では、システムメモリを超えていなくても、
query()
の方が高速です.問題は、あなたの一時eval()
がシステム上のL 1またはL 2 CPUキャッシュのサイズと比較して(2016年は通常数メガバイト)どうですか.より大きい場合、DataFrame
は、異なるメモリキャッシュ間のいくつかの値の移動を回避し、遅い場合があります.実際には、従来の方法と
eval()
方法との間の計算時間の違いを発見しました.通常は大きくありません.もしあれば、従来の方法は小さい配列にとってもっと速いです.eval/query
の利点は、主にメモリを節約し、より明確な構文を提供することです.私たちはすでに
eval/query
とeval()
の大部分の詳細をカバーしています.これらの詳細については、Pandasドキュメントを参照してください.特に、これらのクエリを実行する異なる解析器とエンジンを指定することができる.詳細については、「パフォーマンスの向上」セクションの説明を参照してください.