pandasの重複には、行の内容が同じ、業務上のキーが同じ、indexのラベルが同じ、という別々の問題がある。まず何を一意にしたいのかを決め、duplicatedの対象を指定して調べる。測定値の重複を見つけても、再測定か転記ミスかを判断する前に削除しない方がよい。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import pandas as pd
df = pd.DataFrame({"sample_id": ["A", "A", "B", "B", "B"],
"time": [0, 0, 1, 2, 2],
"value": [1., 2., 3., 4., 4.]},
index=[10, 10, 20, 30, 40])
whole = df.duplicated(keep=False)
key = df.duplicated(subset=["sample_id", "time"], keep=False)
labels = df.index.duplicated(keep=False)
print("identical rows:", whole.tolist())
print("duplicate keys:", key.tolist())
print("duplicate labels:", labels.tolist())
print("key candidates:")
print(df.loc[key].to_string())
reset = df.reset_index(drop=True)
print("unique index after reset:", reset.index.is_unique)
print("key duplicates after reset:", int(reset.duplicated(
["sample_id", "time"], keep=False).sum()))
assert whole.tolist() == [False, False, False, True, True]
assert key.tolist() == [True, True, False, True, True]
assert labels.tolist() == [True, True, False, False, False]
assert not df.index.is_unique and reset.index.is_unique
assert len(df) == len(reset) == 5
実行結果
identical rows: [False, False, False, True, True]
duplicate keys: [True, True, False, True, True]
duplicate labels: [True, True, False, False, False]
key candidates:
sample_id time value
10 A 0 1.0
10 A 0 2.0
30 B 2 4.0
40 B 2 4.0
unique index after reset: True
key duplicates after reset: 4
同じ行と同じキーを区別する
DataFrame.duplicated()は指定した列の値を比較する。subsetを省略すれば全列が対象になり、indexは含まれない。この例の最後の2行は値がすべて同じなので重複である。一方、最初の2行はsample_idとtimeが同じでもvalueが異なるため、全列の比較では同じ行とは判定されない。
試料と時刻の組が一意であるべき表なら、subset=[“sample_id”, “time”]を指定する。すると最初の2行も確認対象になる。同じ試料を同じ時刻に2回測った可能性があるなら、繰り返し番号などをキーへ加える設計も考えられる。一意性はライブラリが決めるものではなく、1行が何を表すかから決まる。
keep=Falseで候補を全部表示する
duplicatedの標準動作では、最初の出現を残して後続をTrueにする。調査段階ではkeep=Falseとして同じグループの全行を取り出す方が、値の食い違いを比較しやすい。例でもAの同時刻測定を両方表示し、1と2の違いを確認できるようにした。
drop_duplicatesをすぐ使うと、最初に現れた行を残すという規則を暗黙に採用することになる。入力ファイルの順番が変われば残る値が変わる場合もある。どの記録が正式かを確認し、平均するなら平均してよい理由を決め、修正の履歴を残してから重複を解消する。
indexの重複は別の検査である
df.index.duplicated()は行ラベルの重複を調べる。例ではラベル10が2回あるが、ラベル30と40の行は内容が同じでも重複ラベルではない。pandasは重複indexを許すため、作成時に必ず例外が出るわけではない。一つのラベルで複数行が選ばれ、予想と違うshapeが返る原因になる。
reset_index(drop=True)は一意な連番へ振り直す操作で、行の内容や複合キーの重複を解消しない。例ではindexが一意になっても、キー重複の候補は4行のままである。エラーが消えたからデータの重複もなくなった、と判断しないようにしたい。
結合や再整列の前に点検する
表を結合するとき、結合先のキーが一意だと思い込んでいると行数が増える。再整列では重複ラベルにより対応を決められない場合がある。これらの処理の前に、キーの欠測、キーの重複、indexの一意性をそれぞれ確認すると原因を追いやすい。重複を許す表なら、その粒度に合う結合条件を選ぶ。
文字列IDの表記揺れも重要である。AとAの後ろに空白が付いたものは、正規化前には別の値として残る。反対に、大文字小文字を無条件に統一すると本来別IDのものをまとめる可能性がある。何を同一と扱うかを入力仕様で決め、確認対象を抽出する段階と、レコードを実際に変更する段階を分離しよう。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
