重複排除(Dedup)は名寄せの後工程。「同じエンティティ」と判定された複数レコードから、どれを正として残すかを決める作業です。
4 つの戦略
| 戦略 | ロジック | 向くケース |
|---|---|---|
| 最新を残す | updated_at が最大の行 | 顧客情報(住所変更等) |
| 最古を残す | created_at が最小の行 | 「最初の登録」が信頼源 |
| 情報量で残す | NULL が少ない / 必須フィールドが埋まってる | アンケート回答 |
| マージで残す | 重複行のフィールドを横方向に統合 | 情報を捨てたくない場面 |
での実装
「最新を残す」Dedup
SQL
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY normalized_name ORDER BY updated_at DESC) AS rn FROM customers)SELECT * EXCEPT(rn) FROM ranked WHERE rn = 1;Python(pandas)での実装
1. 戦略 A: 最新を残す
Python
import pandas as pd
# 重複検出print(f"全行: {len(df)}, 重複: {df.duplicated(subset='normalized_name').sum()}")
# 「最新を残す」(updated_at で並べ替え → drop_duplicates の keep='first')df_sorted = df.sort_values("updated_at", ascending=False)deduped_latest = df_sorted.drop_duplicates(subset="normalized_name", keep="first")print(f"Dedup 後: {len(deduped_latest)}")2. 戦略 B: 情報量で残す(NULL が少ない行を選ぶ)
Python
def dedup_by_completeness(df: pd.DataFrame, key: str) -> pd.DataFrame: """各グループで NULL の少ない行を残す""" df = df.copy() df["_completeness"] = df.notna().sum(axis=1) deduped = ( df.sort_values("_completeness", ascending=False) .drop_duplicates(subset=key, keep="first") .drop(columns=["_completeness"]) ) return deduped
deduped = dedup_by_completeness(df, key="normalized_name")print(f"Dedup 後(情報量優先): {len(deduped)}")3. 戦略 C: マージで残す(フィールドを横に統合)
Python
import numpy as np
def merge_duplicates(df: pd.DataFrame, key: str) -> pd.DataFrame: """重複行のフィールドを「最新の non-null 値」で埋める""" def take_first_valid(series): valid = series.dropna() return valid.iloc[0] if len(valid) > 0 else np.nan
df_sorted = df.sort_values("updated_at", ascending=False) merged = df_sorted.groupby(key, as_index=False).agg(take_first_valid) return merged
merged = merge_duplicates(df, key="normalized_name")# 例: 行A は phone あり / email NULL, 行B は phone NULL / email あり# → マージ後の 1 行: phone も email も埋まる4. 検証:Dedup 前後で件数とフィールド充足率を比較
Python
def validate_dedup(before: pd.DataFrame, after: pd.DataFrame) -> dict: return { "before_n": len(before), "after_n": len(after), "removed_n": len(before) - len(after), "removed_pct": (len(before) - len(after)) / len(before), "before_fillrate": before.notna().mean().mean(), "after_fillrate": after.notna().mean().mean(), }
print(validate_dedup(df, deduped))# 期待: after の fillrate が before より高い(マージ戦略時)ふくふくの進め方
「重複行が多くて困っている」のご相談には、重複パターンの分析 → 戦略選定 → SQL 実装 → 効果測定を 1〜2 週間で。
次回予告
EP.10 は結合の前処理:JOIN キー設計と「漏れ」の防止。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。