Pandas: 重复数据处理
最后更新:2026-08-26
数据合并、多次采集、系统故障——重复数据无处不在。一条客户记录出现 3 次,统计结果就虚高 3 倍。Pandas 的 duplicated / drop_duplicates 让你精准检测和清理重复,而 subset + keep 参数提供了灵活的控制粒度。本节覆盖从检测到删除的完整流程。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ duplicated 检测重复
- ❷ drop_duplicates 删除重复
- ❸ subset 按列去重
- ❹ keep 保留策略
- ❺ 多列组合去重与实战策略
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) 去重决策流程
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 库做概率去重。
📖 小节
- 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 数量。