github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

今天继续数据清洗的话题——处理重复值

重复数据会导致统计结果失真,在分析前必须处理。Pandas提供了强大的工具,让你轻松找出并清理重复项。


创建示例数据

1
2
3
4
5
6
7
8
9
10
import pandas as pd

df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '张三', '李四', '赵六'],
'年龄': [25, 30, 35, 25, 30, 28],
'城市': ['北京', '上海', '广州', '北京', '上海', '深圳'],
'薪资': [15000, 20000, 18000, 15000, 22000, 16000]
})

print(df)

检测重复值

完全重复的行

1
2
3
4
5
6
7
8
9
10
11
12
13
# 标记重复行(保留第一个)
print(df.duplicated())

# 统计重复行数量
print(df.duplicated().sum())

# 查看重复的行
duplicates = df[df.duplicated()]
print(duplicates)

# 包括所有重复(不只是后面的)
all_duplicates = df[df.duplicated(keep=False)]
print(all_duplicates)

基于特定列判断重复

1
2
3
4
5
6
7
8
# 只看姓名是否重复
print(df.duplicated(subset=['姓名']))

# 看姓名+年龄组合是否重复
print(df.duplicated(subset=['姓名', '年龄']))

# 统计每个姓名的出现次数
print(df['姓名'].value_counts())

删除重复值

方法1:保留第一个(默认)

1
2
3
4
5
# 删除完全重复的行,保留第一次出现的
df_clean = df.drop_duplicates()

# 等价于
df_clean = df.drop_duplicates(keep='first')

方法2:保留最后一个

1
2
# 保留最后一次出现的,删除前面的
df_clean = df.drop_duplicates(keep='last')

方法3:全部删除

1
2
# 只要有重复就全部删除(一个不留)
df_clean = df.drop_duplicates(keep=False)

基于特定列去重

1
2
3
4
5
# 按姓名去重(只看姓名,其他列不管)
df_clean = df.drop_duplicates(subset=['姓名'])

# 按多列组合去重
df_clean = df.drop_duplicates(subset=['姓名', '城市'])

实战场景

场景1:用户注册数据去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
users = pd.DataFrame({
'user_id': [1, 2, 3, 1, 4, 2],
'username': ['alice', 'bob', 'charlie', 'alice', 'david', 'bob_new'],
'email': ['alice@example.com', 'bob@example.com',
'charlie@example.com', 'alice@example.com',
'david@example.com', 'bob2@example.com'],
'register_time': ['2024-01-01', '2024-01-02', '2024-01-03',
'2024-01-05', '2024-01-06', '2024-01-07']
})

# 按user_id去重,保留最新的记录
users['register_time'] = pd.to_datetime(users['register_time'])
users = users.sort_values('register_time') # 先排序
users_clean = users.drop_duplicates(subset=['user_id'], keep='last')

print("去重后:")
print(users_clean)

场景2:订单数据合并重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
orders = pd.DataFrame({
'order_id': ['A001', 'A002', 'A001', 'A003', 'A002'],
'product': ['iPhone', 'iPad', 'iPhone', 'MacBook', 'iPad'],
'quantity': [1, 2, 1, 1, 3], # 注意:A002的数量不同
'price': [6999, 4999, 6999, 9999, 4999]
})

# 策略1:简单去重(可能丢失信息)
orders_simple = orders.drop_duplicates(subset=['order_id'])

# 策略2:合并重复(累加数量)
orders_merged = orders.groupby(['order_id', 'product', 'price'])['quantity'].sum().reset_index()

print("原始数据:")
print(orders)
print("\n合并后:")
print(orders_merged)

高级技巧

标记重复但不删除

1
2
3
4
5
# 添加一列标记是否重复
df['is_duplicate'] = df.duplicated(subset=['姓名', '年龄'], keep=False)

# 查看重复组的大小
df['dup_count'] = df.groupby(['姓名', '年龄'])['姓名'].transform('count')

查找最完整的记录

1
2
3
4
5
6
7
8
# 有时重复行的某些字段有缺失,想保留最完整的

# 计算每行非空值数量
df['non_null_count'] = df.notnull().sum(axis=1)

# 按姓名分组,保留非空值最多的行
df_clean = (df.sort_values('non_null_count', ascending=False)
.drop_duplicates(subset=['姓名'], keep='first'))

模糊匹配去重

1
2
3
# 姓名可能有细微差别(如空格、大小写)
df['姓名_clean'] = df['姓名'].str.strip().str.lower()
df_clean = df.drop_duplicates(subset=['姓名_clean'])

完整清洗流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def clean_duplicates(df, subset=None, strategy='first'):
"""
清洗重复值的完整函数

Parameters:
df: DataFrame
subset: 用于判断重复的列列表
strategy: 'first', 'last', False(全部删除)

Returns:
清洗后的DataFrame和报告
"""
original_count = len(df)

# 检测重复
if subset:
dup_mask = df.duplicated(subset=subset, keep=False)
else:
dup_mask = df.duplicated(keep=False)

dup_count = dup_mask.sum()

# 删除重复
df_clean = df.drop_duplicates(subset=subset, keep=strategy)

# 生成报告
report = {
'原始行数': original_count,
'重复行数': dup_count,
'删除行数': original_count - len(df_clean),
'剩余行数': len(df_clean)
}

return df_clean, report

# 使用
df_clean, report = clean_duplicates(df, subset=['姓名', '年龄'], strategy='first')
print("清洗报告:", report)

性能对比:不同去重方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
import numpy as np

df = pd.DataFrame({
'id': np.random.randint(1, 5000, 100000),
'value': np.random.randn(100000),
'date': pd.date_range('2024-01-01', periods=100000, freq='min')
})

# 方式1:完全去重
%timeit df.drop_duplicates()

# 方式2:基于列去重
%timeit df.drop_duplicates(subset=['id'])

# 方式3:保留最后一个
%timeit df.drop_duplicates(subset=['id'], keep='last')

进阶用法

条件去重

1
2
3
4
5
6
7
8
9
10
11
12
# 每个用户保留最新一条记录
df_latest = df.sort_values('date').drop_duplicates(subset='user_id', keep='last')

# 每个用户保留金额最大的一条
df_max = df.sort_values('amount', ascending=False).drop_duplicates(subset='user_id', keep='first')

# 用agg实现更灵活的去重
df_dedup = df.groupby('user_id').agg({
'amount': 'sum', # 汇总金额
'date': 'last', # 最新日期
'status': 'first' # 最早状态
}).reset_index()

模糊去重

1
2
3
4
5
6
7
8
# 名字相似但不完全相同的记录
from difflib import get_close_matches

names = df['name'].unique()
for name in names:
matches = get_close_matches(name, names, n=3, cutoff=0.8)
if len(matches) > 1:
print(f"可能重复: {matches}")

避坑指南

❌ 坑1:去重后索引不连续

1
2
3
4
5
df_dedup = df.drop_duplicates()
print(df_dedup.index) # [0, 1, 3, 5, 7, ...] 不连续!

# 重置索引
df_dedup = df.drop_duplicates().reset_index(drop=True)

❌ 坑2:忽略NaN的重复检测

1
2
3
4
5
6
7
# NaN与NaN不被认为是相同的
df = pd.DataFrame({'A': [1, np.nan, 1, np.nan], 'B': [2, 2, 2, 2]})
print(df.duplicated())
# 0 False
# 1 False ← NaN行不算重复
# 2 True
# 3 False ← NaN行不算重复

实战案例:清洗用户注册数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import pandas as pd
import numpy as np

np.random.seed(42)
# 模拟含重复的用户数据
base_users = pd.DataFrame({
'email': [f'user{i}@example.com' for i in range(3000)],
'name': [f'用户{i:04d}' for i in range(3000)],
'phone': [f'138{i:08d}' for i in range(3000)],
'city': np.random.choice(['北京', '上海', '广州', '深圳'], 3000)
})

# 随机添加重复行
duplicates = base_users.sample(500, replace=True)
df = pd.concat([base_users, duplicates], ignore_index=True)
df = df.sample(frac=1).reset_index(drop=True) # 打乱顺序

print(f"原始数据: {len(df)} 行")
print(f"重复行数: {df.duplicated().sum()}")

# 1. 完全重复的去重
df1 = df.drop_duplicates()
print(f"\n完全去重后: {len(df1)} 行")

# 2. 按email去重(保留最早注册的)
df2 = df.drop_duplicates(subset='email', keep='first')
print(f"按email去重后: {len(df2)} 行")

# 3. 查看重复的email
dup_emails = df[df.duplicated(subset='email', keep=False)]
print(f"\n重复email详情:")
print(dup_emails.sort_values('email').head(10))

重复值产生的常见原因

  1. 数据采集重复:日志系统重复采集
  2. 用户重复提交:前端没有防重复提交
  3. 多源数据合并:同一用户在不同表中都有记录
  4. 时间窗口重叠:昨天和今天的数据有交叉

重复值的深层分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 不只是简单去重,还要分析重复的原因

# 1. 查看重复模式
duplicates = df[df.duplicated(keep=False)]
print(f"重复记录数: {len(duplicates)}")

# 2. 按字段组合检查重复
for col in ['email', 'phone', 'id_card']:
dup_count = df.duplicated(subset=col).sum()
print(f"{col}重复: {dup_count}条")

# 3. 重复记录的时间分布
if 'created_at' in df.columns:
df['created_at'] = pd.to_datetime(df['created_at'])
dup_by_date = df[df.duplicated(keep=False)].groupby(df['created_at'].dt.date).size()
print("重复记录的日期分布:")
print(dup_by_date)

# 4. 完全重复 vs 部分重复
full_dup = df.duplicated().sum() # 所有字段都重复
partial_dup = df.duplicated(subset=['email']).sum() # 部分字段重复
print(f"完全重复: {full_dup}, 部分重复(email): {partial_dup}")

重复值处理决策指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
发现重复值

├── 完全重复(所有字段一样)→ 直接删除

├── 部分重复(关键字段一样)
│ ├── 保留哪条?
│ │ ├── 保留最新 → keep='last'
│ │ ├── 保留最早 → keep='first'
│ │ └── 都不保留 → keep=False
│ └── 删除前先分析原因

└── 模糊重复(相似但不完全一样)
├── 名字拼写差异 → 标准化后去重
├── 地址格式差异 → 统一格式后去重
└── 编码差异 → 映射表统一后去重

重复值的预防

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 读取时指定唯一键约束
df = pd.read_csv('data.csv')
assert df['id'].is_unique, "ID列有重复值!"

# 2. 写入前检查重复
def safe_to_csv(df, path, unique_cols=None):
if unique_cols:
dups = df.duplicated(subset=unique_cols)
if dups.any():
print(f"警告: {dups.sum()}条重复记录")
df = df.drop_duplicates(subset=unique_cols)
df.to_csv(path, index=False)

下节预告

下一课我们将学习数据类型转换,掌握如何正确处理不同类型的数据。

👉 继续阅读:Pandas数据清洗-类型转换与异常值处理


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: Pandas数据清洗-处理缺失值

下一篇: Pandas数据清洗-类型转换与异常值处理


PS:重复值处理看似简单,但要根据业务场景选择合适的策略。建议总是先备份数据再操作。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!