ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

概要

  • 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().

解決方法

  1. 各条件を括弧でくくる
  2. and/orではなく記号(&や|)で接続する
df[(df['var1'] >= 0) & (df['var2'] <= 0.5)]

なぜこのエラーが出るのか

対処法だけ知っていれば書けますが、理由を知っておくと後述の「括弧が必須な理由」も納得できるので、先に原因を書いておきます。

and / or は「1つの真偽値」を要求する

Pythonの andor は、左右の値をそれぞれ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 &amp; df['var2'] &lt;= 0.5

# 実際にはこう解釈される
df['var1'] >= (0 &amp; df['var2']) &lt;= 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'] &lt;= 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'] &lt;= 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 &amp; df['var2'] &lt;= 0.5]

TypeError: cannot compare a dtyped [float64] array with a scalar of type [bool]

正常

df[(df['var1'] >= 0) &amp; (df['var2'] &lt;= 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で複数行・列を一括で削除するが参考になると思います。