静かなる名辞

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値に変換できないことになっています。なぜか? というと、そういったケースではコレクション全体の真理値は曖昧(ambiguous)だからです。

 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と同等の真理値判定が行われます。

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

 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のコレクション型:
    ....

 これに関しては、はっきり言ってこういうことをやろうとするのが悪いです。何かしら考えが間違っていると思います。要素を一つ一つループで取り出すべきかもしれませんし、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]

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

 上の2つは割と初歩的ですが、このケースは割と上級者の人でもハマるときはハマります。

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

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

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で連結したのと同じになることに留意。

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

 幸いかっこを付けて比較的容易に優先順位を制御できます。

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

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

まとめ

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

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