pandasの行抽出・削除まとめ|複数条件フィルタ・正規表現・欠損値の処理
/ 10 min read
Table of Contents
はじめに
試算表や仕訳データを扱っていると、作業の大半は「必要な行だけ残す」ことに費やされます。特定の勘定科目だけ抜き出す、金額が閾値を超える行を探す、摘要に特定の文字が入っている行を除く、空欄の行を落とす。
この記事では、pandasで行を絞り込む操作をひととおりまとめました。**コードはすべて pandas 3.0 で実際に動かして確認しています。**古い書き方のまま使うと動かなくなっているものがあるので、そこも明示しました。
複数条件でフィルタする
条件を &(かつ)や |(または)でつなぎます。各条件を括弧で囲むのが必須です。Pythonの演算子の優先順位の都合で、括弧がないと比較より先に & が評価されてエラーになります。
df = df[ (df["勘定科目"].isin(["売掛金", "未収入金"])) & (df["金額"] >= 100000) & (df["摘要"].str.contains("振替"))]否定は ~ を使います(not は使えません)。
# 「振替」を含まない行df[~df["摘要"].str.contains("振替", na=False)]条件が増えて読みにくくなったら、変数に切り出すと見通しがよくなります。
is_target = df["勘定科目"].isin(["売掛金", "未収入金"])is_large = df["金額"] >= 100000
df[is_target & is_large]文字列を含む行を抽出する
df[df["摘要"].str.contains("振替")]欠損値が混ざっている場合
摘要欄が空の行があると、str.contains() の結果は True / False ではなく欠損(NA)になります。古いpandasでは、これをそのまま絞り込みに使うと次のエラーが出ました。
ValueError: Cannot mask with non-boolean array containing NA / NaN valuespandas 3.0 では欠損は自動的に除外されるようになり、このエラーは出ません。ただしバージョンによって挙動が変わる部分なので、na を明示するのが安全です。
# 欠損行は「含まない」とみなすdf[df["摘要"].str.contains("振替", na=False)]
# 欠損行も残したいときdf[df["摘要"].str.contains("振替", na=True)]会計データの摘要欄は空欄が普通にあるので、na=False を癖にしておくと事故が減ります。
正規表現で抽出する
str.contains() はデフォルトで正規表現として解釈されます。より厳密にマッチさせたい場合は match / fullmatch を使い分けます。
# 数字を含む行(部分一致)df[df["コード"].str.contains(r"\d", na=False)]
# 先頭からマッチする行df[df["コード"].str.match(r"[A-Z]{2}", na=False)]
# 文字列全体が完全に一致する行df[df["コード"].str.fullmatch(r"[A-Z]{2}\d{4}", na=False)]| メソッド | マッチ範囲 | 用途 |
|---|---|---|
contains |
どこかに含まれる | 摘要の部分検索 |
match |
先頭から | コードの接頭辞判定 |
fullmatch |
文字列全体 | コード体系の妥当性チェック |
真偽値だけが欲しい場合は、そのまま列として受け取れます。
df["数字あり"] = df["コード"].str.contains(r"\d", na=False)記号を含む文字列を検索するとき
. や ( は正規表現の特殊文字なので、そのまま検索すると意図しない結果になります。文字どおり検索したいときは regex=False を付けます。
df[df["摘要"].str.contains("(株)", regex=False, na=False)]条件に合う行を削除する
いちばん素直なのは、残したい条件で絞り直す方法です。
# 「振替」を含む行を削除する = 含まない行を残すdf = df[~df["摘要"].str.contains("振替", na=False)]インデックスを取得して drop() する方法もあります。複数条件を組み合わせるときは、こちらのほうが意図が読み取りやすい場合があります。
# 「tomato」を含み、かつ「orange」を含まない行を削除するdrop_index = df.index[ df["fruits"].str.contains("tomato", na=False) & ~df["fruits"].str.contains("orange", na=False)]df = df.drop(drop_index)複数条件でソートする
sort_values() に列のリストを渡し、ascending に列と同じ数の真偽値を渡します。
# 部門(昇順)、金額(降順)、日付(昇順)df = df.sort_values( ["部門", "金額", "日付"], ascending=[True, False, True],)並べ替えた後にインデックスを振り直したいときは ignore_index=True を付けます。
df = df.sort_values(["部門", "金額"], ascending=[True, False], ignore_index=True)欠損値のある行・列を削除する
dropna() で subset に対象列を指定します。
# 指定した列がすべて欠損の行を削除df = df.dropna(subset=["借方金額", "貸方金額"], how="all")
# 指定した列のどれか1つでも欠損なら削除df = df.dropna(subset=["借方金額", "貸方金額"], how="any")how の違いは実務ではかなり重要です。
| 指定 | 削除される行 |
|---|---|
how="all" |
対象列がすべて欠損の行 |
how="any" |
対象列のどれか1つでも欠損の行(デフォルト) |
借方・貸方のように「どちらか一方に入っていれば正しい」データでは how="all" を使わないと、正常な仕訳まで消えてしまいます。
列方向に削除したい場合は axis=1 を指定します。
# 指定した行がすべて欠損の列を削除するdf = df.dropna(subset=[0, 1, 2], axis=1, how="all")欠損を他の列の値で埋める
A列の欠損をB列の値で埋めます。
df["ColA"] = df["ColA"].fillna(df["ColB"])固定値や、列ごとに違う値で埋めることもできます。
# 金額列は0、摘要列は空文字で埋めるdf = df.fillna({"金額": 0, "摘要": ""})おわりに
行の絞り込みは基本的な操作ですが、欠損値が絡んだ瞬間に挙動が変わるのが厄介です。特に str.contains() の na と dropna() の how は、指定を間違えても処理は通ってしまい、件数が合わなくなって初めて気づきます。
絞り込んだ後は len(df) で件数を、金額列は df["金額"].sum() で合計を確認する習慣をつけておくと、消えてはいけない行が落ちたことにその場で気づけます。
