静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念


numpyやpandasでThe truth value of ... is ambiguous.のようなエラーが出たときの対処

概要

 条件式を使って生成したようなboolのnumpy配列を使っていると、次のようなエラーが出ることがあります。

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

 また、pandasのSeriesやDataFrameでも同様のエラーが発生する場合があります。

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

 この記事では、こういったエラーの原因と対処法について説明します。

 目次

原因

 numpyやpandasのような複数のboolを格納できる配列・コレクションなどは、そのまま全体をbool値に変換できないことになっています。

 なぜか? というと、そういったケースではコレクション全体の真理値は曖昧(ambiguous)だからです。個別の要素の値は間違いなくTrue or Falseなのですが、全体としては値が定まらないということです。

 ValueErrorのメッセージでは以下のような変換が提案されています。

  • a.any()かa.all()を使う

 anyは要素すべてのOR、allは要素すべてのANDです。このような方法で変換するのが適切であれば、そうしてくれということです。

  • a.empty(pandasのオブジェクトのみ)

 空であるかどうか。

  • a.bool()(pandasのオブジェクトのみ)

 ブール型の単一の要素を持つ場合、その真理値が返ります。それ以外のケースではValueErrorになるようです。

  • a.item()(pandasの)

 要素数1のSeriesに対してその唯一の要素を返します。

 なんとなく雰囲気がつかめてきましたね。何らかの方法で1つにまとめるなり、1つだけ取り出すなりしてくれと言っている訳です。でもまあ、この通りにすればよいかというと必ずしもそうではなく、ケースバイケースで対処する必要があります。

 余談ですが、このような挙動をするのは私が確認した範囲ではnumpyやpandasのコレクション型のみで、組み込みのlist型などではこの挙動にはなりません。どうなるかというと、皆さんよくご存知の標準的なpythonと同等の真理値判定が行われます。空リストならFalse、それ以外はTrueとかですね。

4. 組み込み型 — Python 3.6.9 ドキュメント

 numpyやpandasの実装は、大雑把に言えば__bool__が例外を送出するようにしてあるというだけのものです。たとえばpandasはこんな感じ↓です。

https://github.com/pandas-dev/pandas/blob/master/pandas/core/generic.py#L1498

 どうしてわざわざこんなことをしているのか? というと、素のpythonと同等の判定だと(たとえば空だとFalseとか)中身のデータに対する真理値だと思いこんで処理する人が出てきたときにバグが発生するので、配慮して__bool__を潰してあるのだと思われます。

 要するに、こんな親切なメッセージまで出してくれるのは「優しさ」なので、「なんだこの腹立つエラーは」とか思ってはいけません。

スポンサーリンク


対処

 対処法ははっきり言ってケースバイケースです。このエラーはいろいろな原因で発生するので、状況にあった対処をする必要があります。

 よくある(であろう)ケースを幾つか紹介してみます。

if文の条件式にそのまま書いた

 if文は内部で条件式のbool値への変換を行います。ということは、このエラーが発生する可能性があります。

if numpyやpandasの配列・DataFrame、Seriesなど:
    ....

 これに関しては、はっきり言ってこういうことをやろうとするのが悪いです。何かしら考えが間違っていると思います。要素を一つ一つループで取り出すべきかもしれませんし、indexingで処理するべき状況の可能性もあります。あるいはそもそも期待と違うものが変数に代入されている可能性もあります。コードを全般的にデバッグしてください。

andやorを使った

 andやorなどの演算子は「pythonのbool」に対して働くので、numpy配列、pandasのSeriesやDataFrameに対して使うのは基本的に不適当です。これらは内部の判定時にboolへの変換を行うので、この記事で取り上げているエラーを発生させる要因になります。

print(np.array([True, False]) and np.array([True, True]))
# => ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

 この場合、期待しているものは&演算子と|演算子で得られるかと思われます。

print(np.array([True, False]) & np.array([True, True]))
# => [ True False]

 他にも~や^も使えます。それぞれ論理否定(NOT)と排他的論理和(XOR)です。

 なお少し補足しておくと、&, |などは本来はスカラーのビットOR, ビットAND演算子として用いるために用意されていますが、numpy, pandasなどはこの演算子が使われたときに呼ばれるメソッドを独自に定義することで、boolean arrayに対する演算子として活用しています。面白い工夫だと思うのですが、そのせいで次の節で触れる問題も手てきます。

複数の条件式の組み合わせで発生した

 このケースは割と上級者の人でも、ハマるときはハマります。

 上述の通り、配列同士の論理演算は&や|などでできるのですが、これは==のような比較演算子より優先順位が強い演算子になります。まあ、本来はビット演算用の演算子なのを流用しているので仕方ないのですが、結果的に評価の順序が狂ってエラーになるケースがあります。

6. 式 (expression) — Python 3.7.4 ドキュメント

a = np.array([0,1,2])
b = np.array([3,4,5])
print(a == 1 | b == 3)
# => ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

 これはこう書いたのと同じです。

print(a == (1 | b) and (1 | b) == 3)

 pythonの比較演算子の仕様がちょっと特殊で、連結するとandで連結したのと同じになることに留意。

x < y <= z は x < y and y <= z と等価になります
6. 式 (expression) — Python 3.7.4 ドキュメント

 幸い、かっこを付けることで、比較的容易に優先順序(評価の順番)を制御できます。

a = np.array([0,1,2])
b = np.array([3,4,5])
print((a == 1) | (b == 3)) # => [ True  True False]

 しかしまあ、ちょっと困った話です。けっきょくpythonの世界であれこれ処理しようとするとこういう細かい齟齬が出てくるので、pandasにqueryメソッドがあったりするのもむべなるかなという感があります。かといってqueryの書き方を覚えるのも面倒くさいんですが。

まとめ

 割とよく見かけるエラーですが、基本的に何かがおかしくなってしまったときに出るもので、ケースバイケースで対応することになるからかあまり掘り下げた解説がなかったので、書いてみました。この記事でどこまで深く掘り下げられているかというと少し微妙な感がありますが・・・

 とにかく「複数の値が入っているから真理値が一意に決められない(ことになっている)」というのがミソなので、それだけは覚えておきましょう。

関連記事

【python】その矛盾した__eq__は・・・ - 静かなる名辞
 Pythonの比較演算子==の挙動についてもう少し掘り下げた記事です。