2つのDataFrameがある場合
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df1
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
df2
A B C D
0 5 9 8 9
1 4 3 0 3
2 5 0 2 3
3 8 1 3 3
4 3 7 0 1
pd.eval
を使用して、1つ以上の列で算術演算を実行したいと思います。具体的には、次のコードを移植したいと思います。
x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
...eval
を使用してコーディングします。 eval
を使用する理由は、多くのワークフローを自動化するためです。そのため、動的にワークフローを作成すると便利です。
engine
およびparser
引数をよりよく理解して、問題を解決する最善の方法を判断しようとしています。私は documentation を経験しましたが、違いは私には明らかではありませんでした。
df2
に戻す方法はありますか?x
をどのように渡すのですか?この答えは、 pd.eval
、 df.query
、および df.eval
によって提供されるさまざまな機能に掘り下げます。
セットアップ
例には、これらのデータフレームが含まれます(特に指定されていない限り)。
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
pandas.eval
-「Missing Manual」注
説明されている3つの関数のうち、pd.eval
が最も重要です。df.eval
およびdf.query
は、内部でpd.eval
を呼び出します。動作と使用法は、3つの関数間で多少の一貫性がありますが、いくつかのマイナーなセマンティクスのバリエーションについては後で説明します。このセクションでは、3つの機能すべてに共通する機能を紹介します-これには、許可された構文、優先規則、およびキーワードが含まれますが、これらに限定されません引数
pd.eval
は、変数やリテラルで構成される算術式を評価できます。これらの式は文字列として渡す必要があります。だから、質問に答えるために、あなたができる
x = 5
pd.eval("df1.A + (df1.B * x)")
ここで注意すべき点がいくつかあります。
df1
、df2
、およびx
は、グローバル名前空間の変数を参照します。これらは、式の解析時にeval
によって取得されます"df1['A'] + (df1['B'] * x)"
を同じ効果に使用することもできます。以下のtarget=...
属性を説明するセクションで、再割り当ての特定の問題に対処します。しかし、今のところ、pd.eval
を使用した有効な操作のより簡単な例を次に示します。
pd.eval("df1.A + df2.A") # Valid, returns a pd.Series object
pd.eval("abs(df1) ** .5") # Valid, returns a pd.DataFrame object
...等々。条件式も同じ方法でサポートされます。以下のステートメントはすべて有効な式であり、エンジンによって評価されます。
pd.eval("df1 > df2")
pd.eval("df1 > 5")
pd.eval("df1 < df2 and df3 < df4")
pd.eval("df1 in [1, 2, 3]")
pd.eval("1 < 2 < 3")
サポートされているすべての機能と構文の詳細なリストは、 documentation にあります。要約すれば、
- 左シフト(
<<
)および右シフト(>>
)演算子を除く算術演算(例:df + 2 * pi / s ** 4 % 42
-the_golden_ratio)- 連鎖比較を含む比較演算(例:
2 < df < df2
)- ブール演算、例えば
df < df2 and df3 < df4
またはnot df_bool
list
およびTuple
リテラル、例えば[1, 2]
または(1, 2)
- 属性アクセス、例:
df.a
- 添字式、例えば
df[0]
- 単純な変数評価、たとえば、
pd.eval('df')
(これはあまり役に立ちません)- 数学関数:sin、cos、exp、log、expm1、log1p、sqrt、sinh、cosh、tanh、arcsin、arccos、arctan、arccosh、arcsinh、arctanh、abs、arctan2.
ドキュメントのこのセクションでは、set
/dict
リテラル、if-elseステートメント、ループ、内包表記、ジェネレーター式など、サポートされていない構文規則も指定します。
リストから、次のようなインデックスを含む式を渡すこともできます。
pd.eval('df1.A * (df1.index > 1)')
parser=...
引数pd.eval
は、式文字列を解析して構文ツリーを生成するときに、2つの異なるパーサーオプションをサポートします:pandas
およびpython
。 2つの主な違いは、優先順位ルールがわずかに異なることで強調されています。
デフォルトのパーサーpandas
を使用すると、ORオブジェクトでベクトル化されたANDおよびpandas演算を実装するオーバーロードされたビット演算子&
および|
は、and
およびor
と同じ演算子優先順位を持ちます。そう、
pd.eval("(df1 > df2) & (df3 < df4)")
と同じになります
pd.eval("df1 > df2 & df3 < df4")
# pd.eval("df1 > df2 & df3 < df4", parser='pandas')
そしてまた同じ
pd.eval("df1 > df2 and df3 < df4")
ここでは、括弧が必要です。これを従来どおりに行うには、ビット単位演算子のより高い優先順位を上書きする必要があります。
(df1 > df2) & (df3 < df4)
それがなければ、
df1 > df2 & df3 < df4
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
文字列を評価する際にPythonの実際の演算子優先順位規則との一貫性を維持する場合は、parser='python'
を使用します。
pd.eval("(df1 > df2) & (df3 < df4)", parser='python')
2つのタイプのパーサーのその他の違いは、リストおよびタプルノードを使用する==
および!=
演算子のセマンティクスです。これらは、not in
パーサーを使用する場合のin
および'pandas'
と同様のセマンティクスを持っています。例えば、
pd.eval("df1 == [1, 2, 3]")
有効であり、次と同じセマンティクスで実行されます
pd.eval("df1 in [1, 2, 3]")
OTOH、pd.eval("df1 == [1, 2, 3]", parser='python')
はNotImplementedError
エラーをスローします。
engine=...
引数numexpr
(デフォルト)とpython
の2つのオプションがあります。 numexpr
オプションは、パフォーマンスのために最適化された numexpr バックエンドを使用します。
'python'
バックエンドを使用すると、式はPythonのeval
関数に式を渡すだけのように評価されます。たとえば、文字列操作などの式の内部をより柔軟に行うことができます。
df = pd.DataFrame({'A': ['abc', 'def', 'abacus']})
pd.eval('df.A.str.contains("ab")', engine='python')
0 True
1 False
2 True
Name: A, dtype: bool
残念ながら、このメソッドはnumexpr
エンジンよりもnoパフォーマンス上の利点を提供し、危険な式が評価されないようにするためのセキュリティ対策が非常に少ないため、AT自分のリスクを使用してください!通常、このオプションを'python'
に変更するのは、何をしているのかわからない限りお勧めしません。
local_dict
およびglobal_dict
引数式内で使用されているが、現在ネームスペースで定義されていない変数の値を指定すると便利な場合があります。辞書をlocal_dict
に渡すことができます
例えば、
pd.eval("df1 > thresh")
UndefinedVariableError: name 'thresh' is not defined
thresh
が定義されていないため、これは失敗します。ただし、これは機能します。
pd.eval("df1 > thresh", local_dict={'thresh': 10})
これは、辞書から提供する変数がある場合に便利です。または、'python'
エンジンを使用して、これを簡単に行うことができます。
mydict = {'thresh': 5}
# Dictionary values with *string* keys cannot be accessed without
# using the 'python' engine.
pd.eval('df1 > mydict["thresh"]', engine='python')
しかし、これはおそらく'numexpr'
エンジンを使用して辞書をlocal_dict
またはglobal_dict
に渡すよりもmuch遅くなります。うまくいけば、これはこれらのパラメータの使用について説得力のある議論をするはずです。
target
(+ inplace
)引数、および割り当て式通常、これを行うにはより簡単な方法があるため、これは要件ではありませんが、pd.eval
の結果を、dict
sなどの__getitem__
を実装するオブジェクトに割り当てることができます。
質問の例を考えてください
x = 5 df2['D'] = df1['A'] + (df1['B'] * x)
列「D」をdf2
に割り当てるには、次のようにします
pd.eval('D = df1.A + (df1.B * x)', target=df2)
A B C D
0 5 9 8 5
1 4 3 0 52
2 5 0 2 22
3 8 1 3 48
4 3 7 0 42
これは、df2
のインプレース変更ではありません(ただし、読み進めることができます)。別の例を考えてみましょう:
pd.eval('df1.A + df2.A')
0 10
1 11
2 7
3 16
4 10
dtype: int32
(たとえば)これをDataFrameに割り当てたい場合、次のようにtarget
引数を使用できます。
df = pd.DataFrame(columns=list('FBGH'), index=df1.index)
df
F B G H
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
df = pd.eval('B = df1.A + df2.A', target=df)
# Similar to
# df = df.assign(B=pd.eval('df1.A + df2.A'))
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
df
でインプレースミューテーションを実行する場合は、inplace=True
を設定します。
pd.eval('B = df1.A + df2.A', target=df, inplace=True)
# Similar to
# df['B'] = pd.eval('df1.A + df2.A')
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
inplace
がターゲットなしで設定されている場合、ValueError
が発生します。
target
引数はいじって楽しいですが、使用する必要はほとんどありません。
df.eval
を使用してこれを実行する場合は、割り当てを含む式を使用します。
df = df.eval("B = @df1.A + @df2.A")
# df.eval("B = @df1.A + @df2.A", inplace=True)
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
注pd.eval
の意図しない用途の1つは、ast.literal_eval
と非常によく似た方法でリテラル文字列を解析することです。
pd.eval("[1, 2, 3]")
array([1, 2, 3], dtype=object)
'python'
エンジンを使用して、ネストされたリストを解析することもできます。
pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python')
[[1, 2, 3], [4, 5], [10]]
そして、文字列のリスト:
pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
[[1, 2, 3], [4, 5], [10]]
ただし、問題は、長さが100を超えるリストの場合です。
pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
このエラー、原因、修正、および回避策の詳細については、 here を参照してください。
DataFrame.eval
-pandas.eval
との並置上記のように、df.eval
は内部でpd.eval
を呼び出します。 v0.23ソースコード はこれを示しています:
def eval(self, expr, inplace=False, **kwargs):
from pandas.core.computation.eval import eval as _eval
inplace = validate_bool_kwarg(inplace, 'inplace')
resolvers = kwargs.pop('resolvers', None)
kwargs['level'] = kwargs.pop('level', 0) + 1
if resolvers is None:
index_resolvers = self._get_index_resolvers()
resolvers = dict(self.iteritems()), index_resolvers
if 'target' not in kwargs:
kwargs['target'] = self
kwargs['resolvers'] = kwargs.get('resolvers', ()) + Tuple(resolvers)
return _eval(expr, inplace=inplace, **kwargs)
eval
は引数を作成し、少し検証を行い、引数をpd.eval
に渡します。
詳細については、以下をお読みください。 DataFrame.eval()とpandas.eval()またはpython eval() を使用する場合
データフレーム全体に関連付けられた動的クエリの場合は、pd.eval
を選択する必要があります。たとえば、df1.eval
またはdf2.eval
を呼び出すときに、pd.eval("df1 + df2")
に相当するものを指定する簡単な方法はありません。
もう1つの大きな違いは、列へのアクセス方法です。たとえば、df1
に2つの列「A」と「B」を追加するには、次の式でpd.eval
を呼び出します。
pd.eval("df1.A + df1.B")
Df.evalを使用すると、列名を指定するだけで済みます。
df1.eval("A + B")
df1
のコンテキスト内では、「A」と「B」が列名を指すことは明らかです。
index
を使用してインデックスと列を参照することもできます(インデックスに名前が付けられていない場合は、名前を使用します)。
df1.eval("A + index")
または、より一般的には、1つ以上のレベルを持つインデックスを持つDataFrameについては、kを参照できます番目 変数を使用する式のインデックスのレベル "ilevel_k"私ndex atlevel k "。IOW、上記の式はdf1.eval("A + ilevel_0")
と書くことができます。
これらの規則はquery
にも適用されます。
列名との混同を避けるために、式内で提供される変数の前には「@」記号を付ける必要があります。
A = 5
df1.eval("A > @A")
query
についても同じことが言えます。
eval
内でアクセスするには、列名がpythonの有効な識別子の命名規則に従う必要があることは言うまでもありません。識別子の命名に関する規則のリストについては、 here を参照してください。
あまり知られていない事実は、eval
が割り当てを扱う複数行の式をサポートしていることです。たとえば、いくつかの列の算術演算に基づいてdf1に2つの新しい列「E」と「F」を作成し、3つ目の列「G」を以前に作成した「E」と「F」に基づいて、
df1.eval("""
E = A + B
F = @df2.A + @df2.B
G = E >= F
""")
A B C D E F G
0 5 0 3 3 5 14 False
1 7 9 3 5 16 7 True
2 2 4 7 6 6 5 True
3 8 8 1 6 16 9 True
4 7 7 8 1 14 10 True
...気の利いた!ただし、これはquery
ではサポートされていないことに注意してください。
eval
v/s query
-最終単語df.query
をサブルーチンとしてpd.eval
を使用する関数と考えると役立ちます。
通常、(名前が示すように)query
は、条件式(つまりTrue/False値になる式)を評価し、True
の結果に対応する行を返すために使用されます。次に、式の結果がloc
(ほとんどの場合)に渡され、式を満たす行が返されます。ドキュメントによると、
この式の評価結果は最初に
DataFrame.loc
に渡され、多次元キー(たとえば、DataFrame)が原因で失敗した場合、結果はDataFrame.__getitem__()
に渡されます。このメソッドは、トップレベルの
pandas.eval()
関数を使用して、渡されたクエリを評価します。
類似性の観点から、query
とdf.eval
は、列名と変数にアクセスする方法が似ています。
上記の2つの主な違いは、式の結果の処理方法です。これは、これら2つの関数を使用して式を実際に実行すると明らかになります。たとえば、考慮してください
df1.A
0 5
1 7
2 2
3 8
4 7
Name: A, dtype: int32
df1.B
0 9
1 3
2 0
3 1
4 7
Name: B, dtype: int32
df1
で "A"> = "B"であるすべての行を取得するには、次のようにeval
を使用します。
m = df1.eval("A >= B")
m
0 True
1 False
2 False
3 True
4 True
dtype: bool
m
は、式「A> = B」を評価することによって生成された中間結果を表します。次に、マスクを使用してdf1
をフィルタリングします。
df1[m]
# df1.loc[m]
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
ただし、query
を使用すると、中間結果「m」がloc
に直接渡されるため、query
を使用すると、単に
df1.query("A >= B")
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
パフォーマンスに関しては、まったく同じです。
df1_big = pd.concat([df1] * 100000, ignore_index=True)
%timeit df1_big[df1_big.eval("A >= B")]
%timeit df1_big.query("A >= B")
14.7 ms ± 33.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.7 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
ただし、後者はより簡潔であり、同じ操作を1つのステップで表現します。
このようなquery
を使用して奇妙なこともできることに注意してください(たとえば、df1.indexによってインデックス付けされたすべての行を返すため)
df1.query("index")
# Same as df1.loc[df1.index] # Pointless,... I know
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
しかし、しないでください。
結論:条件式に基づいて行をクエリまたはフィルタリングする場合は、query
を使用してください。
すでにすばらしいチュートリアルですが、より単純な構文に惹かれるeval/query
の使用法に乱入する前に、データセットの行数が15,000未満の場合、パフォーマンスに重大な問題があることに注意してください。
その場合、単にdf.loc[mask1, mask2]
を使用します。
参照: https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval