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のように扱っていたことでした。この記事では、自分が実際に確認した順番で、同じエラーを直した流れをまとめます。

最初に止まったのは、…

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

import pandas as pd

df = p…

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

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

if df[“金額”] > 0:
print(…

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にしようとしていません。行ごとの条件として使っているだけです。まずここを切り分けると、エラーの意味が少し分かりやすくなりました。

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

filtered = df[(df[“ステータス”] …

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

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

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

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

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

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

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

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

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

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

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

括弧を付けるときは、改行も一緒に入れるようにしました

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

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

filtered = df[combined_mask]

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

行を絞り込むだけなら、…

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

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

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

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

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

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

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

has_pending = (df[“ステータス”] …

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()` や否定条件でも出ました。たとえば、ステータスが複数の候補に入っているかを見たい場合です。最初はPythonの `in` を使えばよいと思っていました。でも、pandasの列に対しては `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]

この書き方なら、まず除外したい行を確認できます

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

最後に、自分が見る順番を決めました。まず、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問だけ回答する