Pandas: 重复数据处理

最后更新:2026-08-26

数据合并、多次采集、系统故障——重复数据无处不在。一条客户记录出现 3 次,统计结果就虚高 3 倍。Pandas 的 duplicated / drop_duplicates 让你精准检测和清理重复,而 subset + keep 参数提供了灵活的控制粒度。本节覆盖从检测到删除的完整流程。

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

1. 你将学到


2. Alice 的咖啡订单重复

(1) 痛点:系统故障导致订单重复

Alice 的咖啡店系统在高峰期故障,3 笔订单被重复提交:

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"Total rows: {len(orders)}")
print(f"Duplicates: {orders.duplicated().sum()}")
# Total rows: 7, Duplicates: 3
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

(2) 解法:drop_duplicates 一行清理

▶ 示例

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]
})

# Remove exact row duplicates
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"Before: {len(orders)} → After: {len(clean)}")
# Before: 7 → After: 4
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

3. duplicated 检测

(1) 基础检测

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:duplicated 检测重复(难度⭐)

PYTHON
import pandas as pd

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
    'age': [28, 34, 28, 25, 35]
})

# Mark duplicates (first occurrence = False, subsequent = True)
print(df.duplicated())
# 0    False  ← first Alice (keep)
# 1    False  ← first Bob (keep)
# 2     True  ← second Alice (duplicate!)
# 3    False  ← first Charlie (keep)
# 4     True  ← Bob but age=35 (NOT duplicate — row is different)

# Filter to see only duplicate rows
print(df[df.duplicated()])
#    name  age
# 2 Alice   28

# Count duplicates
print(f"Duplicate rows: {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

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:keep 参数对比(难度⭐⭐)

PYTHON
import pandas as pd

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Alice', 'Alice', 'Bob'],
    'score': [85, 92, 85, 85, 92]
})

# keep='first' (default) — first occurrence is NOT duplicate
print("keep='first':", df.duplicated(keep='first').tolist())
# [False, False, True, True, True]

# keep='last' — last occurrence is NOT duplicate
print("keep='last':", df.duplicated(keep='last').tolist())
# [True, True, True, False, False]

# keep=False — ALL duplicates are marked 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) 基础删除

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:drop_duplicates 删除重复(难度⭐)

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']  # different city for last Alice
})

# Drop full-row duplicates
print(df.drop_duplicates())
#    name  age     city
# 0 Alice   28      NYC
# 1   Bob   34       LA
# 3 Charlie   25  Chicago
# 4 Alice   28   Boston  ← kept (city is different from row 0)

# With keep parameter
print(df.drop_duplicates(keep='last'))
#    name  age     city
# 1   Bob   34       LA
# 3 Charlie   25  Chicago
# 4 Alice   28   Boston  ← last Alice kept

# Remove ALL duplicates (keep only unique rows)
print(df.drop_duplicates(keep=False))
#    name  age     city
# 1   Bob   34       LA
# 3 Charlie   25  Chicago
# All Alice rows removed (because there are multiple)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

(2) inplace 与重置索引

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:inplace 与 reset_index(难度⭐)

PYTHON
import pandas as pd

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Alice', 'Charlie'],
    'score': [85, 92, 85, 78]
})

# Method 1: assign back (recommended)
df = df.drop_duplicates().reset_index(drop=True)
print(df)
#     name  score
# 0  Alice     85
# 1    Bob     92
# 2 Charlie    78

# Method 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) 只看关键列

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:subset 按列去重(难度⭐⭐)

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]
})

# Full-row duplicate: none (name column differs)
print(f"Full-row duplicates: {df.duplicated().sum()}")  # 0

# But student_id should be unique!
print(f"student_id duplicates: {df.duplicated(subset='student_id').sum()}")  # 2

# Drop duplicates based on student_id only
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

# Same student can have multiple test attempts
# But same student + same subject should be unique
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']
})

# Duplicate: student_id + subject combination
dupes = df.duplicated(subset=['student_id', 'subject'], keep=False)
print("All rows involved in duplicates:")
print(df[dupes])
# S001 + Math appears twice (row 0 and 2)

# Keep latest attempt per student + subject
clean = df.drop_duplicates(subset=['student_id', 'subject'], keep='last')
print("\nAfter dedup (keep last attempt):")
print(clean)
#   student_id  subject  score attempt_date
# 0       S001   English     90   2024-01-10
# 2       S001      Math     88   2024-02-15  ← latest
# 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

# Multiple readings for same sensor — average instead of drop
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]
})

# Instead of dropping, aggregate: keep mean
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) 去重决策流程

100%
graph TB
    Q["Found duplicates?"] -->|No| DONE["✅ Data is clean"]
    Q -->|Yes| TYPE{"Full-row or key-column?"}
    TYPE -->|Full-row| SIMPLE["drop_duplicates()"]
    TYPE -->|Key-column| SUBSET["drop_duplicates(subset=['id'])"]
    SIMPLE --> WHICH["keep='first'/'last'/False?"]
    SUBSET --> WHICH
    WHICH -->|Keep first| FIRST["keep='first'"]
    WHICH -->|Keep last| LAST["keep='last'"]
    WHICH -->|Need all for review| REVIEW["duplicated(keep=False) → manual"]
    WHICH -->|Values differ| AGG["groupby().agg() → merge"]
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

# ============================================
# Comprehensive example: Customer dedup
# with multiple strategies
# ============================================

# 1. Raw data with various duplicate patterns
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("=== Step 1: Detect Duplicates ===")
print(f"Full-row dupes: {df.duplicated().sum()}")
print(f"Email dupes: {df.duplicated(subset='email').sum()}")
print(f"Email+Phone dupes: {df.duplicated(subset=['email','phone']).sum()}")

# 2. Review all rows involved in email duplicates
print("\n=== Step 2: Review Email Duplicates ===")
email_dupes = df[df.duplicated(subset='email', keep=False)]
print(email_dupes.sort_values('email'))

# 3. Strategy: keep the latest signup per email
df['signup_date'] = pd.to_datetime(df['signup_date'])
clean = df.sort_values('signup_date').drop_duplicates(subset='email', keep='last')
print("\n=== Step 3: After Dedup (keep latest) ===")
print(clean[['email', 'name', 'signup_date']])

# 4. Verify
print(f"\nBefore: {len(df)} rows → After: {len(clean)} rows")
print(f"Unique emails: {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 标记所有重复行(用于审查,不保留任何重复)。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 库做概率去重。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建含 3 行完全重复的 DataFrame(8 行),用 duplicated().sum() 计数,用 drop_duplicates() 清理,对比前后行数。
  2. 进阶题(难度⭐⭐):创建学生成绩 DataFrame(同一学生同一科目出现多次),用 subset=['student_id','subject'] 去重,keep='last' 保留最新成绩。
  3. 挑战题(难度⭐⭐⭐):模拟客户注册数据(email/name/phone/signup_date),含 email 重复但 name/phone 不同的情况。完成:检测 email 重复→审查重复行→按 signup_date 排序后 keep='last' 去重→验证唯一 email 数量。

← 上一课:缺失值处理 · 下一课:数据变换 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏