pandasで「The truth value of a Series is ambiguous」が出た原因と対処法を試した話

pandasでExcelの申請一覧を絞り込もうとしたとき、急に ValueError: The truth value of a Series is ambiguous が出ました。最初は、英語のエラー文を見ただけで手が止まったのを覚えています。Seriesがあいまいと言われても、何があいまいなのか分かりませんでした。そのとき作っていたのは、Excelから読み込んだ一覧を条件で分けるだけの小さな処理です。未処理の申請だけを取り出します。金額が0より大きいものだけを残します。条件に合う行があれば、別のExcelに書き出すつもりでした。

ところが、if文や複数条件を書いたところで処理が止まりました。あとから見ると、原因はpandasのSeriesを1つのTrueかFalseのように扱っていたことでした。この記事では、自分が実際に確認した順番で、同じエラーを直した流れをまとめます。

本記事にはプロモーション(アフィリエイトリンク)を含みます。

pandasでtruth value is ambiguousが出て処理が止まった話

最初に止まったのは、Excelから読み込んだ申請一覧を判定するところでした。DataFrameには、部署、ステータス、金額、申請日の列がありました。自分としては、ステータスが未処理で、金額が0より大きい行だけを取り出したかったです。そこで、いつものif文の感覚で条件を書きました。

止まったコードを小さく見る

import pandas as pd

df = pd.read_excel("申請一覧.xlsx")

if df["ステータス"] == "未処理":
    print("未処理があります")

見た目だけなら、そんなに変なコードには見えませんでした。Excelのセルを1つ見ているつもりだったからです。でも、pandasでは df["ステータス"] == "未処理" の結果が1つの答えになりません。行ごとにTrueやFalseが並んだSeriesになります。

つまり、pandasから見ると「この列の中にTrueとFalseがたくさんありますが、全体としてTrueですか」と聞かれている状態でした。それは確かにあいまいです。1件でも該当すればTrueなのか、全部該当したらTrueなのか、pandasには判断できません。そこで、このエラーが出ていました。

Jupyter Notebookでpandasの条件抽出を実行し、ValueEr

エラー文の中には、a.empty、a.bool()、a.item()、a.any()、a.all() のような候補も出ます。最初は全部使えばよいのかと思いました。でも、ここで大事だったのは、まず自分が何を判定したいのかを分けることでした。行を絞り込みたいのか、全体として該当があるかを知りたいのかで、書き方が変わりました。

まずSeries全体をif文に入れていないか確認する

最初に見直したのは、if文の中でした。pandasで列を指定すると、1つの値ではなくSeriesになることがあります。Seriesは、複数行ぶんの値をまとめたものです。そのため、普通のif文にそのまま入れると、今回のエラーにつながりました。自分がやっていた間違いは、次のような形です。

条件だけを別に出してみる

if df["金額"] > 0:
    print("金額あり")

この書き方だと、df["金額"] > 0 の結果は行ごとの判定になります。たとえば、1行目はTrue、2行目はFalse、3行目はTrueのように並びます。それをif文に入れると、pandasは1つの答えに決められません。ここで、truth value is ambiguousと止まります。

自分はまず、条件式だけを別に出しました。そうすると、何が起きているか見えやすくなります。

mask = df["金額"] > 0
print(mask)

画面には、TrueとFalseが縦に並びます。これを見て、ようやく「if文に入れていたのは1つの答えではなかった」と分かりました。変数名を mask にすると、条件で行を選ぶための印だと考えやすくなります。自分の中では、ここでかなり整理できた感覚がありました。

DataFrameの状態列と金額列を表示し、df[

行の抽出として使う

行を取り出したい場合は、if文ではなくDataFrameの中に条件を入れました。自分の場合は、次のように書き直しました。

amount_mask = df["金額"] > 0
filtered = df[amount_mask]

この形なら、Trueになった行だけが残ります。全体を1つのTrueかFalseにしようとしていません。行ごとの条件として使っているだけです。まずここを切り分けると、エラーの意味が少し分かりやすくなりました。

複数条件ではandやorではなく&と|を使う

次に引っかかったのは、複数条件でした。Pythonのif文では、条件をつなぐときに and や or をよく使います。その感覚でpandasの条件にも同じように書きました。これも、自分には自然に見えていました。

andではなく&でつなぐ

filtered = df[(df["ステータス"] == "未処理") and (df["金額"] > 0)]

でも、pandasの行ごとの条件では、この書き方が合いませんでした。and は、左側と右側をそれぞれ1つのTrueかFalseとして見ようとします。ところが、左側も右側もSeriesです。そのため、ここでもあいまいな真偽値として止まりました。

pandasで行ごとに条件をつなぐときは、& や | を使う形に変えました。& は両方に当てはまる行を選ぶときに使います。| はどちらかに当てはまる行を選ぶときに使います。自分の申請一覧では、未処理かつ金額ありなので & を使いました。

filtered = df[(df["ステータス"] == "未処理") & (df["金額"] > 0)]

これで、ようやく期待した行だけが残ります。最初は、なぜ記号を使うのか少し気持ち悪く感じたのを覚えています。ただ、pandasでは行ごとにTrueとFalseを並べて、その組み合わせで行を残すと考えると納得しやすかったです。普通のif文とは別物として見た方が楽でした。

and/orで書いた条件式と、括弧付きの&/|で直した条件式を左右に比較している

orの代わりは|で書く

or の代わりは | です。たとえば、ステータスが未処理または差戻しの行を取りたい場合は、次のようにしました。

filtered = df[
    (df["ステータス"] == "未処理") |
    (df["ステータス"] == "差戻し")
]

このあたりは、Excelのフィルターに近い感覚で考えました。複数の条件を作り、行ごとに当てはまるかどうかを見ます。その結果を使って、DataFrameから必要な行を取り出します。そう考えると、andやorではなく、pandas用のつなぎ方が必要だと分かりました。

条件式ごとに括弧を付け忘れていないか確認する

& や | に直したあとも、まだエラーが出ることがありました。そこで見直したのが、括弧です。pandasの複数条件では、条件式ごとに括弧を付ける必要があります。ここを忘れると、見た目は似ていても違う意味に読まれます。

人間の見た目とPythonの読み方を分ける

たとえば、次のような書き方は危ないです。

filtered = df[df["ステータス"] == "未処理" & df["金額"] > 0]

自分の目には、ステータスが未処理で金額が0より大きい、という意味に見えました。でも、Python側では演算子の優先順位が絡みます。人間が思ったまとまりと、Pythonが読むまとまりがずれます。その結果、別のエラーになったり、truth value is ambiguousに戻ったりしました。

直した形は、条件ごとに括弧で囲む書き方です。

filtered = df[
    (df["ステータス"] == "未処理") &
    (df["金額"] > 0)
]

この形にすると、1つ目の条件と2つ目の条件がはっきり分かれます。自分は、pandasの複数条件では「条件1つにつき括弧1つ」と覚えることにしました。コードが少し長くなっても、あとから見たときに分かりやすいです。エラーも減りました。

括弧を付けるときは、改行も一緒に入れるようにしました。1行に詰め込むと、自分の場合は見落とします。条件が増えたときほど、縦に並べた方が確認しやすかったです。

maskに分けると確認しやすい

combined_mask = (
    (df["ステータス"] == "未処理") &
    (df["金額"] > 0) &
    (df["部署"] == "経理")
)

filtered = df[combined_mask]

このように、先に mask を作る形も助かりました。条件だけを一度表示できます。Trueになっている件数も数えられます。本番の処理へ渡す前に、条件の結果を確認できました。

ここで一度立ち止まって考えてみてください

Pythonや自動化スキルを体系的に習得して、ITエンジニアとしてのキャリアを切り開きたい方には「Enjoy Tech!(エンジョイテック)」が選択肢のひとつです。現役エンジニアのサポートで、未経験から実践的なスキルを身につけられます。

プログラミングスクール Enjoy Tech!(エンジョイテック) →

1件でも該当するか、全部該当するかでanyとallを使い分ける

行を絞り込むだけなら、DataFrameに条件を入れれば済みました。でも、別の場面ではif文が必要でした。たとえば、未処理の申請が1件でもあればメッセージを出したい場面です。この場合は、Seriesをそのままif文に入れず、1つの答えに変える必要がありました。

1件でもあればanyを使う

自分が最初に書いたのは、また同じような形でした。

if df["ステータス"] == "未処理":
    print("未処理があります")

これは、やはり止まります。未処理の行があるかを知りたいなら、any() を使いました。any() は、1件でもTrueがあればTrueにしてくれます。申請一覧のチェックには、この考え方が合っていました。

全件を見たいときはallを使う

has_pending = (df["ステータス"] == "未処理").any()

if has_pending:
    print("未処理があります")

一方で、全件が条件を満たしているかを見たい場合は all() を使いました。たとえば、全行に申請日が入っているかを確認する場面です。1件でも空欄があれば困るので、全体確認になります。

all_has_date = df["申請日"].notna().all()

if all_has_date:
    print("申請日はすべて入っています")
any()とall()の結果をJupyter Notebookで確認し、該当あり

ここで、自分の中の整理はかなり進みました。行を取り出したいなら、SeriesのままDataFrameに渡します。全体として判定したいなら、any() や all() で1つのTrueかFalseにします。この違いを混ぜると、同じエラーに戻りました。

実務では、先に変数名で目的を分けると間違いにくかったです。mask_pending なら行ごとの条件として読めます。has_pending なら全体の判定だと分かります。名前を変えるだけでも、自分にはかなり効果がありました。

isinやnot条件で同じエラーが出たときに見直すこと

同じエラーは、isin() や否定条件でも出ました。たとえば、ステータスが複数の候補に入っているかを見たい場合です。最初はPythonの in を使えばよいと思っていました。でも、pandasの列に対しては isin() を使う方が分かりやすかったです。

複数候補はisinで見る

たとえば、未処理と差戻しをまとめて取り出す場合は、次のようにしました。

target_status = ["未処理", "差戻し"]
filtered = df[df["ステータス"].isin(target_status)]

これなら、行ごとに対象ステータスかどうかを判定できます。結果はTrueとFalseのSeriesです。そのSeriesをDataFrameに渡すので、行の抽出として自然に動きました。自分のように複数条件で混乱しやすい場合は、isin() の方が読みやすかったです。

否定条件は~で反転する

条件を反転するときも、普通の not を使うと混乱しました。pandasの行ごとの条件を反転したい場合は、~ を使いました。たとえば、完了以外を取り出すなら、次のような形です。

filtered = df[~(df["ステータス"] == "完了")]

isin() と組み合わせる場合もあります。

exclude_status = ["完了", "取消"]
filtered = df[~df["ステータス"].isin(exclude_status)]

ここでも、括弧やまとまりを見やすくすることが大事でした。~ は見落としやすいです。条件が長くなると、自分で書いたコードなのに分からなくなります。そのため、除外条件は一度変数に分けるようにしました。

exclude_status = ["完了", "取消"]
mask_exclude = df["ステータス"].isin(exclude_status)
filtered = df[~mask_exclude]

この書き方なら、まず除外したい行を確認できます。そのあとで、反転して残す行を選べます。少し遠回りに見えますが、あとから見たときの安心感が違いました。エラーが出たときも、どの条件が原因か探しやすかったです。

truth value is ambiguousが出たときの確認順まとめ

最後に、自分が見る順番を決めました。まず、if文の中にSeriesやDataFrameをそのまま入れていないかを見ます。次は、複数条件で and や or や not を使っていないかを見る流れです。そのあと、& や | を使っている条件に括弧が付いているかを確認します。

自分用の確認メモ

自分用には、次のような順番でメモしました。

行を取り出したい場合
row_mask = (df["ステータス"] == "未処理") & (df["金額"] > 0)
filtered = df[row_mask]

1件でもあるか知りたい場合
has_pending = (df["ステータス"] == "未処理").any()

全部そろっているか知りたい場合
all_has_date = df["申請日"].notna().all()

この3つを分けるだけで、かなり迷いが減ります。以前は、全部をif文で何とかしようとしていました。でも、pandasでは行ごとの判定と全体の判定を分けた方が楽でした。Seriesはそのまま悪いものではなく、使う場所を間違えると止まるものだと分かりました。

コード全体より条件式を先に見る

今回のエラーは、英語の文だけ見ると難しく感じます。ただ、自分の場合は「複数のTrue/Falseを1つに決めようとしていないか」と見ると理解しやすかったです。行を絞り込むなら df[mask] と考えます。全体を判定するなら any() や all() を使います。複数条件では & と | を使い、条件ごとに括弧を付ける形にしました。

実務の申請一覧では、この順番で直してから処理が安定しました。もちろん、pandasに詳しい人ならもっと短く書けると思います。でも、自分のように手探りで直している人には、まず分けて書く方が安心でした。同じエラーが出たら、コード全体を疑う前に、条件式の目的を1つずつ見直すのが一番マシだと思っています。

無料プレゼント

Excel業務を自動化する前に確認するチェックリスト(PDF)

自動化していい作業かどうか、VBAかPythonか、最初に避けるべき落とし穴。実務でよく迷うポイントを1枚にまとめました。メールアドレスだけで受け取れます。

無料でチェックリストを受け取る

¥980 ミニキット

コピペで動かせる3スクリプト+自動化チェックリスト

最新ファイルの自動選択・部署名ゆれの正規化・CSV文字コード確認の3本セット。今週の作業を1つだけ楽にするための最小キットです。

ミニキットを見る(¥980)

関連リンクとチェックリスト

関連書籍

Python・pandas関連書籍を、手元で見返せる形にしておきたい場合

pandasのエラー対応は、似た症状を何度も見返すことが多いです。データ前処理や型変換を手元で確認できる本を1冊置いておくと、調べ直しの時間を減らしやすいです。

Python・pandas関連書籍をAmazonで探す

学習サービスとアンケート

このスキルを活かしてさらに前へ進むなら

PythonやExcel自動化スキルを持ったまま、ITエンジニアとして転職したい方には「EBAエデュケーション」が選択肢です。企業が求めるエンジニア像に合わせたカリキュラムで、実務直結のスキルを習得できます。

ITエンジニア転職・EBAエデュケーション →

[アンケート] この記事は役に立ちましたか?


1問だけ回答する