HOWTO · Pandas

pandasで列の値に基づいてDataFrameの行を絞り込む

pandasのDataFrameをブールマスク、loc、isin、between、文字列・欠損値の判定、queryで絞り込む方法を説明します。

このページの内容

行の絞り込みでは、各行に1つのブール値を作り、Trueの行だけを残します。基本形はdf.loc[条件]です。第2引数を使えば出力列も同時に指定できます。

サンプルDataFrameを作成する

例の表には文字列、数値、日付、欠損した商品名があります。以降はordersを再利用します。元のインデックスは保持され、必要な場合だけ.reset_index(drop=True)で振り直します。

import pandas as pd

orders = pd.DataFrame(
    {
        "order_id": [101, 102, 103, 104, 105, 106],
        "region": ["East", "West", "East", "North", "West", "East"],
        "product": ["Desk", "Chair", "Desk lamp", "Desk", None, "Chair"],
        "sales": [450, 275, 125, 700, 320, 410],
        "status": ["paid", "pending", "paid", "paid", "cancelled", "pending"],
        "ordered_at": pd.to_datetime(["2026-08-01", "2026-08-03", "2026-08-08", "2026-08-11", "2026-08-14", "2026-08-18"]),
    }
)

1つの列値で行を絞り込む

Seriesとの比較がマスクを作ります。単純な角括弧でも同じ行を選べますが、.locなら列も明示できます。除外には!=、欠損値にはisna()notna()を使います。

east_orders = orders.loc[orders["region"] == "East"]
print(east_orders[["order_id", "region", "sales"]].to_string(index=False))

出力:

 order_id region  sales
      101   East    450
      103   East    125
      106   East    410
large_orders = orders.loc[orders["sales"] >= 400, ["order_id", "sales"]]
print(large_orders.to_string(index=False))

出力:

 order_id  sales
      101    450
      104    700
      106    410

複数の条件を組み合わせる

要素単位の条件は&|~で結び、各比較を括弧で囲みます。Pythonのandorは単一の真偽値を要求するため、Seriesでは真偽値が曖昧というエラーになります。

mask = (orders["region"] == "East") & (orders["sales"] >= 400)
result = orders.loc[mask, ["order_id", "region", "sales"]]
print(result.to_string(index=False))

出力:

 order_id region  sales
      101   East    450
      106   East    410
mask = (orders["status"] == "paid") | (orders["sales"] > 600)
result = orders.loc[mask, ["order_id", "status", "sales"]]
# Raises ValueError: The truth value of a Series is ambiguous.
orders.loc[(orders["region"] == "East") and (orders["sales"] >= 400)]

値のリストで絞り込む

Series.isin()は多数の等価比較を置き換えます。単一の文字列ではなく、リストなどのリスト状コレクションを渡します。除外する場合はisin()の結果全体に~を付けます。

allowed = ["East", "West"]
result = orders.loc[orders["region"].isin(allowed), ["order_id", "region"]]
print(result.to_string(index=False))

出力:

 order_id region
      101   East
      102   West
      103   East
      105   West
      106   East
excluded = ["cancelled", "pending"]
paid_only = orders.loc[~orders["status"].isin(excluded)]

範囲と日付で絞り込む

between()は既定で両端を含みます。pd.to_datetime()で変換した日付にも使えます。タイムゾーン付きデータでは境界も互換性のあるタイムゾーンにします。

mid_value = orders.loc[orders["sales"].between(300, 500), ["order_id", "sales"]]
print(mid_value.to_string(index=False))

出力:

 order_id  sales
      101    450
      105    320
      106    410
date_mask = orders["ordered_at"].between("2026-08-03", "2026-08-14")
result = orders.loc[date_mask, ["order_id", "ordered_at"]]

欠損値または非欠損値で絞り込む

isna()NoneNaNNaTを選び、notna()は値がある行を選びます。空文字列は自動的には欠損値にならないため、別に正規化または判定します。

missing_product = orders.loc[orders["product"].isna(), ["order_id", "product"]]
present_product = orders.loc[orders["product"].notna()]
print(missing_product.to_string(index=False))

出力:

 order_id product
      105    None

文字列の値で絞り込む

str.contains()はベクトル化された文字列マスクを作ります。regex=Falseはリテラル検索、case=Falseは大文字小文字を無視し、na=Falseは欠損文字列を非一致にします。

has_desk = orders["product"].str.contains("desk", case=False, regex=False, na=False)
result = orders.loc[has_desk, ["order_id", "product"]]
print(result.to_string(index=False))

出力:

 order_id   product
      101      Desk
      103 Desk lamp
      104      Desk

読みやすい式にDataFrame.query()を使う

query()では@でローカル変数を参照し、空白を含む列名はバッククォートで囲みます。信頼できない入力を式へ連結しないでください。評価時にコードを実行できるためです。

minimum_sales = 400
result = orders.query("region == 'East' and sales >= @minimum_sales")
print(result[["order_id", "region", "sales"]].to_string(index=False))

出力:

 order_id region  sales
      101   East    450
      106   East    410

.loc、角括弧、query()を使い分ける

一般には.loc、行だけの短い選択には角括弧、管理された式が読みやすい場合にquery()を使います。独立した結果を変更する前には.copy()を呼びます。

east_orders = orders.loc[orders["region"] == "East"].copy()
east_orders["sales_with_tax"] = east_orders["sales"] * 1.2

インデックスの整合とデータ型の問題を避ける

マスクはインデックスラベルで整合されるため、同じDataFrameから作ります。数値や日付の文字列は比較前に変換し、業務規則が必要とするときだけ表記を正規化します。

ブールマスクを再利用して検証する

名前付きマスクは件数確認やテストができます。一致がなくても有効な空のDataFrameが返ります。DataFrame.filter()はセル値ではなく軸ラベルを選ぶ別のAPIです。

元データを変更せずに絞り込む

元の表を更新するなら元DataFrameへ.locで代入し、別の作業表が必要ならコピーします。これにより連鎖インデックスや不明確なメモリ共有に依存しません。

DataFrame.loc, Series.isin, Series.between, Series.str.contains, DataFrame.query