pandasのduplicated()とdrop_duplicates()の使い方|重複行の抽出・削除とkeep・subsetの違い
pandasで重複行を調べるにはdf.duplicated()、消すにはdf.drop_duplicates()を使います。どちらもsubsetで判定に使う列を、keepで「最初・最後・すべて」のどれを重複扱いにするかを指定します。
この記事では、duplicated()で重複を見つける方法、keep=Falseで重複グループを丸ごと確認する方法、drop_duplicates()で削除する方法を、実行結果つきで説明します。コードはpandas 3.0.6(2026年10月15日確認の最新版)で動かしています。
duplicated()とdrop_duplicates()の違い
2つのメソッドは判定のルールが同じで、返すものだけが違います。
| メソッド | 返すもの | 主な用途 |
|---|---|---|
duplicated() |
行ごとのTrue/False(bool型のSeries) | 重複している行を探す・数える・抽出する |
drop_duplicates() |
重複行を取り除いたDataFrame | 重複を消して1件ずつにする |
引数は共通で、公式リファレンス(DataFrame.duplicated)とDataFrame.drop_duplicatesに書かれているとおり次の意味です。
| 引数 | 既定値 | 意味 |
|---|---|---|
subset |
すべての列 | 重複の判定に使う列(1列なら文字列、複数ならリスト) |
keep |
'first' |
'first':最初の1行を残す(重複扱いしない)/'last':最後の1行を残す/False:重複している行をすべて対象にする |
ignore_index |
False |
(drop_duplicatesのみ)結果のインデックスを0から振り直す |
inplace |
False |
(drop_duplicatesのみ)元のDataFrameを書き換える |
duplicated()で重複している行を調べる
例として、会員の購入記録を使います。0行目と2行目は全列が同じ値です。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
print(df)
print(df.duplicated())
会員ID 名前 購入日
0 101 佐藤 10/01
1 102 鈴木 10/01
2 101 佐藤 10/01
3 103 高橋 10/02
4 102 鈴木 10/05
5 104 佐藤 10/05
0 False
1 False
2 True
3 False
4 False
5 False
dtype: bool
既定のkeep='first'では、同じ内容の行のうち最初の行(0行目)はFalse、2回目以降(2行目)がTrueになります。全列が一致している行だけが重複と判定されるため、会員IDが同じでも購入日が違う4行目はFalseです。
重複行だけを抽出する・件数を数える
結果はbool型のSeriesなので、そのままdf[...]に渡せば重複行だけを取り出せます。sum()するとTrueの数、つまり重複行の件数になります。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
print(df[df.duplicated()])
print("重複行の数:", df.duplicated().sum())
会員ID 名前 購入日
2 101 佐藤 10/01
重複行の数: 1
keepの3つの値を並べて比べる
subset="会員ID"として会員IDだけで判定し、keepの違いを1つの表にしました。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
result = pd.DataFrame({
"first": df.duplicated(subset="会員ID"),
"last": df.duplicated(subset="会員ID", keep="last"),
"False": df.duplicated(subset="会員ID", keep=False),
})
print(result)
first last False
0 False True True
1 False True True
2 True False True
3 False False False
4 True False True
5 False False False
first:会員101・102の2回目(2行目・4行目)がTruelast:反対に1回目(0行目・1行目)がTrue。最後の行を「本物」とみなすFalse:重複している行はすべてTrue。一度しか出てこない103・104だけがFalse
keep=Falseで重複グループを丸ごと確認する
重複を消す前に「どの行とどの行がかぶっているのか」を目で確認したいときは、keep=Falseで抽出して並べ替えるのが便利です。df.duplicated(keep=False)で検索してくる人が多いのも、この使い方のためだと思われます。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
# 会員IDが重複している行を、最初の出現も含めてすべて表示
print(df[df.duplicated(subset="会員ID", keep=False)].sort_values("会員ID"))
会員ID 名前 購入日
0 101 佐藤 10/01
2 101 佐藤 10/01
1 102 鈴木 10/01
4 102 鈴木 10/05
会員102は購入日が違うので、同じ人が2回買っただけかもしれません。重複を消すかどうかは、こうして中身を見てから決めます。
drop_duplicates()で重複行を削除する
drop_duplicates()は、duplicated()がTrueになる行を取り除いたDataFrameを返します。keepの意味もそのままです。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
print(df.drop_duplicates())
print(df.drop_duplicates(subset="会員ID", keep="last"))
print(df.drop_duplicates(subset="会員ID", keep=False))
会員ID 名前 購入日
0 101 佐藤 10/01
1 102 鈴木 10/01
3 103 高橋 10/02
4 102 鈴木 10/05
5 104 佐藤 10/05
会員ID 名前 購入日
2 101 佐藤 10/01
3 103 高橋 10/02
4 102 鈴木 10/05
5 104 佐藤 10/05
会員ID 名前 購入日
3 103 高橋 10/02
5 104 佐藤 10/05
1つ目は全列が一致した2行目だけが消えています。2つ目は会員IDごとに最後の行を残し、3つ目(keep=False)は会員IDが重複している行をすべて消して、一度しか出てこない会員だけを残しています。
複数の列で判定し、インデックスを振り直す
subsetにリストを渡すと「名前と購入日の組み合わせ」のように複数列で判定できます。削除するとインデックスが飛び飛びになるので、ignore_index=Trueで0から振り直しておくと後の処理が楽です。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102, 104],
"名前": ["佐藤", "鈴木", "佐藤", "高橋", "鈴木", "佐藤"],
"購入日": ["10/01", "10/01", "10/01", "10/02", "10/05", "10/05"],
})
print(df.drop_duplicates(subset=["名前", "購入日"], ignore_index=True))
会員ID 名前 購入日
0 101 佐藤 10/01
1 102 鈴木 10/01
2 103 高橋 10/02
3 102 鈴木 10/05
4 104 佐藤 10/05
実務の定番:キーごとに最新の1行だけ残す
「会員ごとに最新の購入だけ残したい」という処理は、日付で並べ替えてからkeep='last'で消すと1行で書けます。日付を文字列のまま並べると「10/1」と「9/30」の順序がおかしくなるので、pd.to_datetime()で日付型にしておきます。
import pandas as pd
df = pd.DataFrame({
"会員ID": [101, 102, 101, 103, 102],
"購入日": pd.to_datetime(["2026-10-01", "2026-10-03", "2026-10-09", "2026-10-02", "2026-09-28"]),
})
# 会員ごとに「最新の購入日」の行だけ残す
latest = df.sort_values("購入日").drop_duplicates(subset="会員ID", keep="last")
print(latest.sort_values("会員ID"))
会員ID 購入日
2 101 2026-10-09
1 102 2026-10-03
3 103 2026-10-02
Seriesの重複を扱う
Seriesにも同じduplicated()とdrop_duplicates()があります。値の種類だけ欲しいならunique()、出現回数も見たいならvalue_counts()が向いています。
import pandas as pd
s = pd.Series(["A", "B", "A", "C", "B"])
print(s.duplicated().tolist())
print(s.drop_duplicates().tolist())
print(s.unique())
print(s.value_counts())
[False, False, True, False, True]
['A', 'B', 'C']
<StringArray>
['A', 'B', 'C']
Length: 3, dtype: str
A 2
B 2
C 1
Name: count, dtype: int64
pandas 3.0からは、文字列の列が既定で専用のstr型になりました(pandas 3.0.0のリリースノート)。そのためunique()の結果はStringArrayと表示されます。リストで欲しいときはdrop_duplicates().tolist()のようにします。ユニーク数を数える方法は「pandas nunique()の使い方」で詳しく扱っています。
重複がうまく見つからない・消えないとき
空白や大文字・小文字の違いは別の値になる
duplicated()は値が完全に一致したときだけ重複とみなします。末尾の空白や全角・半角、大文字・小文字が違うと別の値です。判定用の列を整えてから調べます。
import pandas as pd
df = pd.DataFrame({
"名前": ["佐藤", "佐藤 ", "さとう", "Sato", "sato"],
})
print(df.duplicated(keep=False).tolist())
key = df["名前"].str.strip().str.lower()
print(key.duplicated(keep=False).tolist())
[False, False, False, False, False]
[True, True, False, True, True]
「佐藤」と「佐藤 」、「Sato」と「sato」が重複として見つかるようになりました。ひらがなの「さとう」は別の値のままです。表記の揺れをどこまでそろえるかは、データの性質に合わせて決めてください。
リストが入った列はTypeErrorになる
公式リファレンスの注記にあるとおり、subsetの列はハッシュ可能な型である必要があります。セルにリストが入っているとエラーになるので、タプルに変換した列で判定します。
import pandas as pd
df = pd.DataFrame({"会員ID": [101, 102], "タグ": [["a", "b"], ["a", "b"]]})
try:
df.drop_duplicates()
except TypeError as e:
print("TypeError:", e)
df["タグ_key"] = df["タグ"].apply(tuple)
print(df.drop_duplicates(subset="タグ_key"))
TypeError: unhashable type: 'list'
会員ID タグ タグ_key
0 101 [a, b] (a, b)
NaN同士は同じ値として扱われる
比較演算ではNaN同士は等しくなりませんが、duplicated()ではNaN同士は一致とみなされます。欠損値の行がまとめて消えるのを防ぎたいときは、先に欠損値を処理しておきます。
import pandas as pd
import numpy as np
df = pd.DataFrame({"会員ID": [101, np.nan, np.nan], "点数": [80, 70, 70]})
print(df.duplicated().tolist())
[False, False, True]
結果を代入しないと元のDataFrameは変わらない
drop_duplicates()は新しいDataFrameを返すだけです。inplace=Trueにすると元が書き換わる代わりに戻り値はNoneになるので、df = df.drop_duplicates(inplace=True)と書くとdfがNoneになってしまいます。通常はdf = df.drop_duplicates()と代入する書き方がわかりやすいです。
import pandas as pd
df = pd.DataFrame({"会員ID": [101, 102, 101], "名前": ["佐藤", "鈴木", "佐藤"]})
df2 = df.drop_duplicates()
print(len(df), len(df2))
r = df.drop_duplicates(inplace=True)
print(r, len(df))
3 2
None 2
よくある質問
Q. duplicated()とdrop_duplicates()はどちらを使えばいいですか?
重複がどこにあるか確認したいときはduplicated()、確認したうえで消すときはdrop_duplicates()です。いきなり消さずに、duplicated(keep=False)で重複グループを一度見てから消すと事故が減ります。
Q. keep=Falseにするとどうなりますか?
duplicated()では重複しているすべての行がTrueになり、drop_duplicates()では重複している行がすべて削除されて、一度しか出てこない行だけが残ります。
Q. 特定の列だけで重複を判定するには?
subset引数に列名を渡します。1列ならsubset=”会員ID”、複数列ならsubset=[“名前”, “購入日”]のようにリストで指定します。
Q. Pythonのリストの重複を消したいときもpandasを使いますか?
リストだけならpandasは不要です。順番を保つならlist(dict.fromkeys(リスト))、順番が不要ならset()で消せます。
リストの重複の扱いは「Pythonでリストの重複を削除する方法」にまとめています。
まとめ
- 重複を調べるのは
duplicated()、消すのはdrop_duplicates() subsetで判定する列、keepで残す行(first・last・False)を決める- 消す前に
keep=Falseで重複グループを確認する - 「キーごとに最新の1行」は並べ替え+
keep='last' - 空白・大文字小文字の揺れ、リスト型の列、NaNの扱いに注意する
pandas|抽出・選択の関連記事
loc・ilocや条件指定で、必要な行・列だけを取り出す操作の記事です。
- MultiIndexから任意の行・列を自在に選択・抽出する方法
- 特定の条件を満たす要素数をカウントする方法(全体・行・列ごと)
- Pandas DataFrame query()メソッドで条件抽出を簡単に!使い方とサンプルコード集
- pandasのfilter()の使い方|列名をitems・like・regexで抽出、値で絞るときとの違い
- DataFrameの行番号・列番号を自在に取得するテクニック
- Pandasで自由自在!インデックス指定で行・列を抽出する方法
pandas全体の使い方は「pandasの使い方を初心者向けに解説」でまとめています。



