重複データの処理【Pandas duplicatedとdrop_duplicates完全ガイド】
データの結合、繰り返し実行される収集処理、システムの不具合など、重複データはどこにでも存在します。1件の顧客レコードが3回登場するだけで、統計は3倍に膨れ上がってしまいます。Pandasのduplicated / drop_duplicatesを使えば、重複を正確に検出してクリーニングできます。さらにsubset + keepパラメータにより、粒度を柔軟に制御できます。本セクションでは、検出から削除までの完全なワークフローを解説します。
⚠️ 注意: 以下のコードはローカルのPython環境で実行してください。
1. このセクションで学ぶこと
- ❶ duplicatedによる重複の検出
- ❷ drop_duplicatesによる重複の削除
- ❸ subsetを使った列基準の重複排除
- ❹ keepによる保持戦略
- ❺ 複数カラムの重複排除と実践的な戦略
2. アリスの重複したコーヒー注文
(1) 問題点:システム不具合による注文の重複
アリスのコーヒーショップで、混雑時にシステムに不具合が発生し、3件の注文が2回送信されてしまいました:
PYTHON
import pandas as pd
orders = pd.DataFrame({
'order_id': ['O001', 'O001', 'O002', 'O003', 'O003', 'O003', 'O004'],
'customer': ['Alice', 'Alice', 'Bob', 'Charlie', 'Charlie', 'Charlie', 'Carol'],
'drink': ['Latte', 'Latte', 'Americano', 'Mocha', 'Mocha', 'Mocha', 'Espresso'],
'price': [4.50, 4.50, 3.00, 5.50, 5.50, 5.50, 2.50]
})
print(f"総行数: {len(orders)}")
print(f"重複数: {orders.duplicated().sum()}")
# 総行数: 7、重複数: 3
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) 解決策:drop_duplicatesによる1行でのクリーニング
▶ サンプル:基本的な重複排除(難易度 ⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
orders = pd.DataFrame({
'order_id': ['O001', 'O001', 'O002', 'O003', 'O003', 'O003', 'O004'],
'customer': ['Alice', 'Alice', 'Bob', 'Charlie', 'Charlie', 'Charlie', 'Carol'],
'drink': ['Latte', 'Latte', 'Americano', 'Mocha', 'Mocha', 'Mocha', 'Espresso'],
'price': [4.50, 4.50, 3.00, 5.50, 5.50, 5.50, 2.50]
})
# 行が完全に一致する重複を削除する
clean = orders.drop_duplicates()
print(clean)
# order_id customer drink price
# 0 O001 Alice Latte 4.5
# 2 O002 Bob Americano 3.0
# 3 O003 Charlie Mocha 5.5
# 6 O004 Carol Espresso 2.5
print(f"処理前: {len(orders)} → 処理後: {len(clean)}")
# 処理前: 7 → 処理後: 4
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
3. duplicatedによる重複の検出
(1) 基本的な検出
▶ サンプル:duplicatedによる重複の検出(難易度 ⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
'age': [28, 34, 28, 25, 35]
})
# 重複をマークする(最初の出現 = False、それ以降 = True)
print(df.duplicated())
# 0 False ← 最初のAlice(保持)
# 1 False ← 最初のBob(保持)
# 2 True ← 2番目のAlice(重複!)
# 3 False ← 最初のCharlie(保持)
# 4 True ← Bobだがage=35(重複ではない — 行が異なる)
# 重複行だけを抽出して確認する
print(df[df.duplicated()])
# name age
# 2 Alice 28
# 重複をカウントする
print(f"重複行: {df.duplicated().sum()}") # 1
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) keepパラメータがマーキングロジックを制御する
| keepの値 | 動作 | 最初の重複 | 最後の重複 |
|---|---|---|---|
| 'first' | 最初の出現を保持 | Falseとマーク | Trueとマーク |
| 'last' | 最後の出現を保持 | Trueとマーク | Falseとマーク |
| False | すべての重複をマーク | Trueとマーク | Trueとマーク |
▶ サンプル:keepパラメータの比較(難易度 ⭐⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Alice', 'Alice', 'Bob'],
'score': [85, 92, 85, 85, 92]
})
# keep='first'(デフォルト) — 最初の出現は重複とみなされない
print("keep='first':", df.duplicated(keep='first').tolist())
# [False, False, True, True, True]
# keep='last' — 最後の出現は重複とみなされない
print("keep='last':", df.duplicated(keep='last').tolist())
# [True, True, True, False, False]
# keep=False — すべての重複がTrueとマークされる
print("keep=False:", df.duplicated(keep=False).tolist())
# [True, True, True, True, True]
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
4. drop_duplicatesによる重複の削除
(1) 基本的な削除
▶ サンプル:drop_duplicatesによる重複の削除(難易度 ⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Alice', 'Charlie', 'Alice'],
'age': [28, 34, 28, 25, 28],
'city': ['NYC', 'LA', 'NYC', 'Chicago', 'Boston'] # 最後のAliceはcityが異なる
})
# 行全体が一致する重複を削除する
print(df.drop_duplicates())
# name age city
# 0 Alice 28 NYC
# 1 Bob 34 LA
# 3 Charlie 25 Chicago
# 4 Alice 28 Boston ← 保持(cityが0行目と異なる)
# keepパラメータを指定する場合
print(df.drop_duplicates(keep='last'))
# name age city
# 1 Bob 34 LA
# 3 Charlie 25 Chicago
# 4 Alice 28 Boston ← 最後のAliceを保持
# すべての重複を削除する(ユニークな行だけを残す)
print(df.drop_duplicates(keep=False))
# name age city
# 1 Bob 34 LA
# 3 Charlie 25 Chicago
# Aliceの行はすべて削除される(複数存在するため)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) inplaceとインデックスのリセット
▶ サンプル:inplaceとreset_index(難易度 ⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Alice', 'Charlie'],
'score': [85, 92, 85, 78]
})
# 方法1:再代入する(推奨)
df = df.drop_duplicates().reset_index(drop=True)
print(df)
# name score
# 0 Alice 85
# 1 Bob 92
# 2 Charlie 78
# 方法2:inplaceを使う
# df.drop_duplicates(inplace=True)
# df.reset_index(inplace=True, drop=True)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
5. subsetを使った列基準の重複排除
(1) キーカラムだけに着目する
▶ サンプル:subsetを使った列基準の重複排除(難易度 ⭐⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
df = pd.DataFrame({
'student_id': ['S001', 'S001', 'S002', 'S003', 'S002'],
'name': ['Alice', 'Alice Smith', 'Bob', 'Charlie', 'Robert Bob'],
'math_score': [85, 85, 92, 78, 92],
'attempt': [1, 2, 1, 1, 2]
})
# 行全体の重複:なし(nameカラムが異なる)
print(f"行全体の重複: {df.duplicated().sum()}") # 0
# しかしstudent_idはユニークであるべき!
print(f"student_idの重複: {df.duplicated(subset='student_id').sum()}") # 2
# student_idだけを基準に重複を削除する
clean = df.drop_duplicates(subset='student_id', keep='first')
print(clean)
# student_id name math_score attempt
# 0 S001 Alice 85 1
# 2 S002 Bob 92 1
# 3 S003 Charlie 78 1
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) 複数カラムの組み合わせによる重複排除
▶ サンプル:複数カラムの組み合わせによる重複排除(難易度 ⭐⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
# 同じ学生が複数回テストを受けることがある
# しかし、同じ学生 + 同じ科目はユニークであるべき
df = pd.DataFrame({
'student_id': ['S001', 'S001', 'S001', 'S002', 'S002'],
'subject': ['Math', 'English', 'Math', 'Math', 'English'],
'score': [85, 90, 88, 92, 87],
'attempt_date': ['2024-01-10', '2024-01-10', '2024-02-15', '2024-01-12', '2024-01-12']
})
# 重複:student_id + subjectの組み合わせ
dupes = df.duplicated(subset=['student_id', 'subject'], keep=False)
print("重複に関与しているすべての行:")
print(df[dupes])
# S001 + Mathは2回出現する(0行目と2行目)
# 学生 + 科目ごとに最新の受験を保持する
clean = df.drop_duplicates(subset=['student_id', 'subject'], keep='last')
print("\n重複排除後(最新の受験を保持):")
print(clean)
# student_id subject score attempt_date
# 0 S001 English 90 2024-01-10
# 2 S001 Math 88 2024-02-15 ← 最新
# 3 S002 Math 92 2024-01-12
# 4 S002 English 87 2024-01-12
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
6. 重複データを処理する戦略
(1) シナリオ別の判断表
| シナリオ | 戦略 | コード |
|---|---|---|
| 完全に重複した行 | drop_duplicates() | df.drop_duplicates() |
| キーカラムの重複 | subsetで重複排除 | df.drop_duplicates(subset=['id']) |
| 最新を保持 | keep='last' | df.drop_duplicates(subset=['id'], keep='last') |
| 手動レビューが必要 | 削除せずにマーク | df[df.duplicated(keep=False)] |
| 削除せず集約 | groupby + agg | df.groupby('id').agg({'val': 'mean'}) |
▶ サンプル:削除の代わりに集約する(難易度 ⭐⭐⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
# 同じセンサーに対する複数の測定値 — 削除する代わりに平均を取る
df = pd.DataFrame({
'sensor_id': ['T01', 'T01', 'T01', 'T02', 'T02'],
'location': ['Lab A', 'Lab A', 'Lab A', 'Lab B', 'Lab B'],
'temperature': [22.5, 22.7, 22.3, 18.1, 18.3],
'humidity': [45, 47, 43, 55, 57]
})
# 削除する代わりに集約する:平均を保持する
agg = df.groupby('sensor_id').agg({
'location': 'first',
'temperature': 'mean',
'humidity': 'mean'
}).reset_index()
print(agg)
# sensor_id location temperature humidity
# 0 T01 Lab A 22.500 45.0
# 1 T02 Lab B 18.200 56.0
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) 重複排除の判断フロー
graph TB
Q["重複が見つかった?"] -->|いいえ| DONE["✅ データはクリーン"]
Q -->|はい| TYPE{"行全体?キーカラム?"}
TYPE -->|行全体| SIMPLE["drop_duplicates()"]
TYPE -->|キーカラム| SUBSET["drop_duplicates(subset=['id'])"]
SIMPLE --> WHICH["keep='first'/'last'/False?"]
SUBSET --> WHICH
WHICH -->|最初を保持| FIRST["keep='first'"]
WHICH -->|最後を保持| LAST["keep='last'"]
WHICH -->|すべてレビューが必要| REVIEW["duplicated(keep=False) → 手動"]
WHICH -->|値が異なる| AGG["groupby().agg() → 統合"]
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
7. 完全な例:顧客データの完全な重複排除ワークフロー
▶ サンプル:顧客データの完全な重複排除ワークフロー(難易度 ⭐⭐⭐)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
PYTHON
import pandas as pd
# ============================================
# 包括的な例:複数の戦略を使った
# 顧客データの重複排除
# ============================================
# 1. さまざまな重複パターンを含む生データ
df = pd.DataFrame({
'email': ['alice@example.com', 'alice@example.com', 'bob@example.com',
'charlie@example.com', 'bob@example.com', 'alice@example.com',
'david@example.com'],
'name': ['Alice', 'Alice Smith', 'Bob', 'Charlie', 'Bob Jones',
'Alice', 'David'],
'phone': ['555-0100', '555-0100', '555-0200', '555-0300',
'555-0201', '555-0100', '555-0400'],
'signup_date': ['2024-01-15', '2024-02-01', '2024-01-20',
'2024-03-01', '2024-03-15', '2024-01-15',
'2024-04-01']
})
print("=== ステップ1:重複の検出 ===")
print(f"行全体の重複: {df.duplicated().sum()}")
print(f"Emailの重複: {df.duplicated(subset='email').sum()}")
print(f"Email+Phoneの重複: {df.duplicated(subset=['email','phone']).sum()}")
# 2. emailの重複に関与しているすべての行をレビューする
print("\n=== ステップ2:Emailの重複をレビュー ===")
email_dupes = df[df.duplicated(subset='email', keep=False)]
print(email_dupes.sort_values('email'))
# 3. 戦略:emailごとに最新の登録を保持する
df['signup_date'] = pd.to_datetime(df['signup_date'])
clean = df.sort_values('signup_date').drop_duplicates(subset='email', keep='last')
print("\n=== ステップ3:重複排除後(最新を保持) ===")
print(clean[['email', 'name', 'signup_date']])
# 4. 検証する
print(f"\n処理前: {len(df)} 行 → 処理後: {len(clean)} 行")
print(f"ユニークなemail数: {clean['email'].nunique()}")
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して実行してください。実際の値はpandasのバージョンによって若干異なる場合があります。
❓ よくある質問
Q duplicatedとdrop_duplicatesの違いは何ですか?
A duplicated()は重複行をマークしたブール型のSeriesを返します(削除はしません)。検出やレビューに使います。drop_duplicates()は重複を排除したDataFrameを直接返します(元のデータは変更しません)。重複を調べたいときはduplicated、クリーニングしたいときはdrop_duplicatesを使ってください。
Q keep='first'/'last'/Falseはどのように使い分けますか?
A keep='first'は最初の出現を保持します(デフォルトで、ログ形式のデータに適しています)。keep='last'は最後の出現を保持します(更新で上書きするシナリオに適しています)。keep=Falseはすべての重複行をマークします(レビュー用で、重複は1件も保持しません)。drop_duplicates(keep=False)は、一度も重複しなかった行だけを保持します。
Q 単一カラムと複数カラムのsubsetの違いは何ですか?
A subset=['email']はemailカラムだけが重複しているかをチェックし、他のカラムの違いは無視します。subset=['email','phone']は両方のカラムが同時に一致して初めて重複とみなします。単一カラムの重複排除は正当なデータを誤って削除する可能性があります(同じemailでも連絡先が異なる場合)。一方、複数カラムの組み合わせはより精密ですが、バリエーションのある重複を見逃す可能性があります。
Q 重複排除後にインデックスが乱れた場合はどうすればよいですか?
A drop_duplicatesで行を削除すると、元のインデックスが保持されるため、欠番が生じます。
reset_index(drop=True)を使って連続したインデックスにリセットしてください。drop=Trueを指定すると古いインデックスは破棄されます。指定しないと、古いインデックスが'index'という名前の新しいカラムになります。推奨されるチェーン形式はdf.drop_duplicates().reset_index(drop=True)です。Q 重複データは削除すべきですか、それとも集約すべきですか?
A ビジネス上の意味によります。重複がエラーの場合(システムの不具合など)は削除します。重複が繰り返しの測定値の場合は集約(mean/max/last)します。判断がつかない場合は、まずマークしてレビューし、直接削除しないでください。原則として、削除は不可逆ですが、集約は情報を保持します。
Q drop_duplicatesは元のDataFrameを変更しますか?
A いいえ。drop_duplicatesは新しいDataFrameを返し、元のデータは変更されません。inplace=Trueパラメータを使えばその場で変更できますが、Pandas 3.xのCopy-on-Writeモードでは代入形式が推奨されます:
df = df.drop_duplicates()。こちらの方がセマンティクスが明確です。Q 「ほぼ重複」した行はどうやって見つけますか?
A 完全な重複にはduplicatedを使いますが、「Alice」と「Alice Smith」は完全な重複ではありません。戦略としては、まず正規化してから比較します。strip()で空白を除去し、lower()で大文字小文字を統一するか、emailのプレフィックスを部分文字列としてマッチさせます。あいまいマッチングが必要な場合は、recordlinkageやdedupeライブラリを使って確率的に重複排除を行います。
📖 まとめ
- duplicated()は重複行をマークしてブール型のSeriesを返します。drop_duplicates()は直接重複を排除します
- keepパラメータが保持戦略を制御します:first(デフォルト)/ last / False(すべてマーク)
- subsetパラメータは重複を判定するカラムを指定します。単一カラムと複数カラムの組み合わせの両方をサポートします
- 重複排除後はreset_index(drop=True)で連続したインデックスにリセットしてください
- 完全に重複した行 → 直接削除、キーカラムの重複 → subsetで重複排除、値が異なる → 削除せず集約
- まず検出してレビューし(duplicated)、その後で戦略(削除 / 集約 / 手動レビュー)を決定してください
📝 練習問題
- 基礎(難易度 ⭐):完全に重複した行を3行含むDataFrame(合計8行)を作成してください。duplicated().sum()でカウントし、drop_duplicates()でクリーニングして、処理前後の行数を比較してください。
- 中級(難易度 ⭐⭐):学生の成績DataFrame(同じ学生が同じ科目に複数回登場する)を作成してください。subset=['student_id','subject']で重複排除し、keep='last'を使って最新の成績を保持してください。
- チャレンジ(難易度 ⭐⭐⭐):顧客の登録データ(email/name/phone/signup_date)をシミュレートしてください。emailが重複しているがname/phoneが異なるケースも含めてください。次の処理を完成させてください:emailの重複を検出 → 重複行をレビュー → signup_dateでソートしてからkeep='last'で重複排除 → ユニークなemail数を集計して検証。



