概要
- pandas.DataFrameで複数条件を指定したときによく出るエラーの対処方法
df[df['var1'] >= 0 and df['var2'] <= 0.5]
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
解決方法
- 各条件を括弧でくくる
- and/orではなく記号(&や|)で接続する
df[(df['var1'] >= 0) & (df['var2'] <= 0.5)]
なぜこのエラーが出るのか
対処法だけ知っていれば書けますが、理由を知っておくと後述の「括弧が必須な理由」も納得できるので、先に原因を書いておきます。
and / or は「1つの真偽値」を要求する
Pythonの and と or は、左右の値をそれぞれ1つの真偽値として評価しようとします。このとき内部的に bool() が呼ばれます。
ところが df['var1'] >= 0 の結果は、行数分の True / False が並んだSeriesです。要素が複数あるものを1つの真偽値にしようとすると、「全部Trueのときが真なのか、1つでもTrueなら真なのか」が決められません。
そこでpandasは、勝手に解釈せずにエラーを出します。エラーメッセージが a.empty, a.bool(), a.item(), a.any() or a.all() を挙げているのは、「どれのつもりなのか明示してくれ」という意味です。
& と | は要素ごとに評価する
一方 & と | は、Seriesに対して要素ごとの論理積・論理和として動きます。行ごとに True / False を計算して、Seriesを返します。
DataFrameの絞り込みに必要なのは行ごとの真偽値なので、こちらを使うのが正解になります。
否定したい場合も同様で、not ではなく ~ を使います。
df[~(df['var1'] >= 0)] # var1が0未満の行
括弧が必須な理由
これが一番はまりやすいところです。Pythonでは、& と | の優先順位が比較演算子(>= や <=)より高いという規則になっています。
そのため括弧を付けないと、次のように解釈されます。
df['var1'] >= 0 & df['var2'] <= 0.5
# 実際にはこう解釈される
df['var1'] >= (0 & df['var2']) <= 0.5
意図とはまったく違う式になっているため、後述の「エラーその3」のように別のエラーが出ます。C言語などと優先順位が違うので、感覚で書くと間違えます。
比較を含む条件を & や | でつなぐときは、必ず各条件を括弧でくくると覚えておくのが確実です。
詳細
準備
import numpy as np
import pandas as pd
cols = ['var1', 'var2', 'var3', 'var4']
df = pd.DataFrame(np.random.randn(4, 4), columns=cols)
df
var1 var2 var3 var4
0 0.597118 -0.853204 1.813645 0.694750
1 -1.118426 0.011119 -2.161933 0.792262
2 0.665828 0.384975 1.676278 -0.487037
3 -0.216118 2.084042 -0.279242 -1.785128
エラーその1
df[df['var1'] >= 0 and df['var2'] <= 0.5]
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
エラーその2
df[(df['var1'] >= 0) and (df['var2'] <= 0.5)]
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
エラーその3
df[df['var1'] >= 0 & df['var2'] <= 0.5]
TypeError: cannot compare a dtyped [float64] array with a scalar of type [bool]
正常
df[(df['var1'] >= 0) & (df['var2'] <= 0.5)]
var1 var2 var3 var4
0 0.597118 -0.853204 1.813645 0.694750
2 0.665828 0.384975 1.676278 -0.487037
括弧を書きたくない場合
条件が増えてくると括弧だらけになって読みにくくなります。その場合は query() を使うと、文字列の中で and / or がそのまま書けます。
df.query('var1 >= 0 and var2 <= 0.5')
こちらは括弧も不要で、条件が多いときは見通しが良くなります。変数を埋め込みたい場合は @ を付けて参照します。
threshold = 0.5
df.query('var1 >= 0 and var2 <= @threshold')
単一の真偽値がほしい場合
そもそも「条件を満たす行が1つでもあるか」を判定したいだけなら、エラーメッセージが示している通り .any() や .all() を使います。
(df['var1'] >= 0).any() # 1つでも満たす行があればTrue
(df['var1'] >= 0).all() # すべての行が満たすならTrue
if文の条件に書きたい場合は、こちらが必要になります。Seriesをそのまま if に渡すと、同じ ValueError が出ます。
条件を書き直して値を入れ替える場合はpandas.DataFrameで置換処理が、該当する行や列を落としてしまう場合はpandas.DataFrameで複数行・列を一括で削除するが参考になると思います。