重複行を削除する: pandas / polars / BigQuery

python
SQL
前処理
Author

Ryo Nakagami

Published

2026-03-18

Modified

2026-04-27

問題設定: 指定カラムの組で重複している行を1行に丸めたい

Exercise 1

(a, b) の組が重複する行を1行に集約したい. ただし c カラムには行ごとに異なる情報が入っており,どの行を残すかは別途決められるようにしたい.

import pandas as pd

df = pd.DataFrame({"a": [1, 1, 2], "b": [2, 2, 3], "c": [1, 2, 3]})
df
a b c
0 1 2 1
1 1 2 2
2 2 3 3

期待する出力 ((a, b) で最初に出現した行を残す)

a b c
1 2 1
2 3 3

ポイント

  • (a, b)キーとして扱い,それ以外のカラムは副次情報として持ち越す
  • どの重複行を残すか (first / last / 全削除) は明示する.デフォルト挙動に依存するとライブラリ間で結果が変わる
  • 行順はライブラリによっては保証されない.並びに意味があるなら明示的にソートする

Pattern 1: pandas

DataFrame.drop_duplicates(subset=...) が王道.subset を渡さないと全カラムで完全一致した行のみが重複扱いされる点に注意.

df.drop_duplicates(subset=["a", "b"])
a b c
0 1 2 1
2 2 3 3

keep で残す行を選ぶ

df.drop_duplicates(subset=["a", "b"], keep="last")
a b c
1 1 2 2
2 2 3 3

keep=False を渡すと重複した行をすべて削除してユニークな行だけ残せる.

df.drop_duplicates(subset=["a", "b"], keep=False)
a b c
2 2 3 3
Warning
  • pandas ではNaN == NaN を真として重複判定する.SQL の NULL 比較とは挙動が違うので,BigQuery 移植時にズレる
  • subset のカラム順序は判定に影響しない (集合として扱われる).ただし出力の行順は元の DataFrame の順序が保たれる
  • 「副次カラム c のうち最大値を残したい」のような条件付きの場合は,drop_duplicates ではなく df.sort_values(["a","b","c"]).drop_duplicates(["a","b"], keep="last") のように並び替えてから keep を使う

Pattern 2: polars

polars ではdrop_duplicates という名前のメソッドはなくunique(subset=...) が正準.

import polars as pl

df_pl = pl.DataFrame({"a": [1, 1, 2], "b": [2, 2, 3], "c": [1, 2, 3]})

df_pl.unique(subset=["a", "b"], keep="first", maintain_order=True)
shape: (2, 3)
a b c
i64 i64 i64
1 2 1
2 3 3

maintain_order=True必ず付けるのが実務上のポイント.polars はデフォルトでは並列処理の都合上行順を保証しないため,付け忘れると毎回違う行が残ったように見える事故が起きる.

keep のオプション

keep 残る行 pandas 対応
"first" 最初に出現した行 keep="first"
"last" 最後に出現した行 keep="last"
"any" どれか1行 (順序保証なし・最速) (該当なし)
"none" 重複しているグループは全削除 keep=False

pandas との対応関係

操作 pandas polars
サブセット指定で重複削除 df.drop_duplicates(subset=["a","b"]) df.unique(subset=["a","b"], maintain_order=True)
最後の行を残す keep="last" keep="last"
重複行をすべて削除 keep=False keep="none"
順序より速度優先 (相当なし) keep="any" (順序非保証だが最速)
全カラムでユニーク化 df.drop_duplicates() df.unique(maintain_order=True)

注意点 !

  • polars の uniquenull を一意な値として扱う (= 同じ null 同士は重複扱い).pandas の NaN 挙動と一致するので移植性は高いが,SQL の NULL とは違う
  • 大規模データでは keep="any" の方がハッシュテーブル走査が打ち切れて速い.順序が要らない集計の前段では使い分けるとよい
  • lazy() 経由なら unique も Plan 最適化の対象になり,後続の select と統合される

Pattern 3: BigQuery

BigQuery で「sub-set 指定の重複削除」を一発で書く正準は QUALIFY ROW_NUMBER()SELECT DISTINCT全カラム一致しか扱えないので別問題になる.

QUALIFY パターン

WITH src AS (
  SELECT * FROM UNNEST([
    STRUCT(1 AS a, 2 AS b, 1 AS c),
    STRUCT(1   , 2   , 2),
    STRUCT(2   , 3   , 3)
  ])
)
SELECT *
FROM src
QUALIFY ROW_NUMBER() OVER (PARTITION BY a, b ORDER BY c) = 1;

ポイントはPARTITION BY がキーORDER BY が「どの行を残すか」ORDER BY c なら c 最小の行が残り,ORDER BY c DESC なら最大.ORDER BY を省くと結果が非決定的になるので必ず指定する.

全削除 (keep=False 相当)

COUNT(*) OVER で重複群を識別してから絞り込む.

SELECT * EXCEPT (n)
FROM (
  SELECT *, COUNT(*) OVER (PARTITION BY a, b) AS n
  FROM src
)
WHERE n = 1;
Warning
  • BigQuery の NULLNULL = NULLUNKNOWNで,PARTITION BY ではグループ化キーとして扱われる (= 同じ NULL 同士は同じグループ).ただし WHERE a = b 系の比較では一致扱いされない.pandas / polars との違いに注意
  • QUALIFYOVER 句のあるクエリ専用.通常の WHERE と違い,SELECT リストに窓関数を出していなくても使える
  • ORDER BY のキーが同値で並ぶケースに備えて,タイブレーク用の列 (updated_at, id 等) を追加しておくと結果が安定する

まとめ

Tip3パターンの使い分け
  • pandas: drop_duplicates(subset=..., keep=...) の一行で完結.条件付き keep はsort_valuesdrop_duplicates(keep="last") の組合せが定石
  • polars: メソッド名は uniquemaintain_order=True忘れない.順序が不要な前処理段では keep="any" で速度を取りに行ける
  • BigQuery: QUALIFY ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) = 1 が万能.ORDER BY の決定性を必ず確保する