首页 / Pandas 入门教程 / 重复数据处理

Pandas 入门教程

重复数据处理

本教程共 54 篇 · 第 26 篇 · 更新于 2026-08-11 · 约 5 分钟阅读

PandasPandas 入门教程重复数据duplicateddrop_duplicates去重

本节目标:用 duplicated 找出重复行,用 drop_duplicates 删除重复,理解 keep 和 subset 参数,并会处理索引标签重复。

重复数据从哪来

导入多次、程序重复写入、多表合并后叠加——重复行在真实数据里太常见了。不处理的话,sum、mean、count 全部失真。

duplicated:标记重复

duplicated 返回布尔 Series,True 表示”这一行在前面出现过”:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "姓名": ["张三", "李四", "张三", "王五", "李四"],
    "年龄": [25, 30, 25, 28, 30],
})
print(df.duplicated())
print(df.duplicated().sum())    # 2 行重复

第一行”张三 25”是首次出现,标 False;第三行和它完全一样,标 True。

keep 参数决定”谁算重复”:

  1. keep=“first”(默认):第一次出现保留,后面的标 True
  2. keep=“last”:最后一次保留,前面的标 True
  3. keep=False:所有重复行(包括第一次)都标 True
print(df.duplicated(keep="last"))
print(df.duplicated(keep=False))

subset 指定只看某些列,比如”姓名相同就算重复”:

print(df.duplicated(subset=["姓名"]))
Note

判断重复比较的是值本身。注意带 NaN 的行:duplicated 把 NaN 视为彼此相等(与 == 的语义不同),所以含 NaN 的行也会被判重、被去重。

drop_duplicates:删除重复

drop_duplicates 直接返回去重后的表,参数和 duplicated 一一对应:

print(df.drop_duplicates())
print(df.drop_duplicates(keep="last"))
print(df.drop_duplicates(keep=False))
print(df.drop_duplicates(subset=["姓名"]))

去重后行号会留下空洞,加 ignore_index=True 恢复连续索引:

print(df.drop_duplicates(ignore_index=True))
Tip

去重后复查:df.duplicated().sum() 应变成 0。去重”删行”,配合 dropna(§24)一起构成清洗的第一步。

先定义什么叫重复

“重复”不是数据自带的属性,是你定义的:

  • 完全重复:所有列都相同,drop_duplicates() 不传 subset 即可
  • 部分重复:只看关键列(比如订单号),其他列允许不同,用 subset 指定

subset 选哪些列,取决于业务上”什么相同就算同一条”。选宽了会误删,选窄了漏删,先想清楚再动手。

场景一:完全重复的行

整行一模一样才删,直接 drop_duplicates() 不传参数。最常见于”同一份数据导入了两遍”。删除前可以先用 value_counts 看看哪几行重复最多:

print(df.value_counts().head())

场景二:按业务键去重,保留最新

同一订单号出现多次,想留日期最新的那条。先按日期排序,再按订单号去重并保留”最后一条”:

orders = pd.DataFrame({
    "订单号": ["O001", "O002", "O001", "O003"],
    "日期": ["2024-01-01", "2024-01-02", "2024-01-05", "2024-01-04"],
    "金额": [100, 200, 150, 300],
})
print(orders.sort_values("日期").drop_duplicates(subset=["订单号"], keep="last"))

O001 出现两次,留下 1 月 5 日那条。

场景三:每组保留最大值

比如每个客户保留消费最高的记录:先按消费降序排序,再按客户去重(每组留第一条):

sales = pd.DataFrame({
    "客户": ["A", "A", "B", "B"],
    "月份": ["01", "02", "01", "02"],
    "消费": [100, 200, 300, 250],
})
print(sales.sort_values("消费", ascending=False).drop_duplicates(subset=["客户"]))

先排序、再去重、keep 定取舍——这个组合拳能解决大部分”每组挑一条”的需求。

场景四:多列组合键

单列判断不够时,subset 传多个列名,组合相同才算重复。比如”客户 + 产品”重复才是真正的重复订单:

df2 = pd.DataFrame({
    "客户": ["A", "A", "A", "B"],
    "产品": ["手机", "手机", "电脑", "手机"],
    "金额": [5000, 5000, 8000, 6000],
})
print(df2.drop_duplicates(subset=["客户", "产品"]))

去重前后的统计对比

去重为什么重要,看一个数字就够了。同一份订单被算了两次,总金额直接虚高:

print("去重前总金额:", orders["金额"].sum())
orders_clean = orders.sort_values("日期").drop_duplicates(subset=["订单号"], keep="last")
print("去重后总金额:", orders_clean["金额"].sum())

先看数字差异,再决定要不要删——差异大说明重复确实在污染统计结果。

只标记不删除

有些场景不想删,只想留个标记(比如导出前人工复核)。duplicated 的结果可以直接存成一列:

df3 = df.copy()
df3["是否重复"] = df3.duplicated(keep=False)
print(df3)

索引标签重复

行数据不重复,但索引标签重复(比如两行都叫 “b”),同样会捣乱:loc[“b”] 返回两行,reindex 直接报错(§23)。

s = pd.Series([0, 1, 2], index=["a", "b", "b"])
print(s.index.is_unique)              # False
print(s.index.duplicated())           # [False False True]
print(s.loc[~s.index.duplicated()])   # 保留第一条

或者把同标签的行合并:s.groupby(level=0).mean()。想从源头禁止重复,用 set_flags(allows_duplicate_labels=False)(§23 有完整例子)。

Warning

去重前先搞清楚重复的原因。同一订单号对应不同金额时,直接删可能删掉正确的那条。先 df[df.duplicated(subset=["订单号"], keep=False)] 把重复的全部捞出来看一遍,再决定保留策略。

小结

duplicated 标记、drop_duplicates 删除,keep 决定留哪条,subset 定义”什么算重复”。先对比去重前后的统计数字,再决定删不删——重复不一定是脏数据,可能是”重复”的粒度没定义对。