

大家好,我是正在实战各种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'] })
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], 'price': [6999, 4999, 6999, 9999, 4999] })
orders_simple = orders.drop_duplicates(subset=['order_id'])
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') })
%timeit df.drop_duplicates()
%timeit df.drop_duplicates(subset=['id'])
%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')
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)
df_dedup = df.drop_duplicates().reset_index(drop=True)
|
❌ 坑2:忽略NaN的重复检测
1 2 3 4 5 6 7
| df = pd.DataFrame({'A': [1, np.nan, 1, np.nan], 'B': [2, 2, 2, 2]}) print(df.duplicated())
|
实战案例:清洗用户注册数据
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()}")
df1 = df.drop_duplicates() print(f"\n完全去重后: {len(df1)} 行")
df2 = df.drop_duplicates(subset='email', keep='first') print(f"按email去重后: {len(df2)} 行")
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 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
duplicates = df[df.duplicated(keep=False)] print(f"重复记录数: {len(duplicates)}")
for col in ['email', 'phone', 'id_card']: dup_count = df.duplicated(subset=col).sum() print(f"{col}重复: {dup_count}条")
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)
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
| df = pd.read_csv('data.csv') assert df['id'].is_unique, "ID列有重复值!"
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 飞速搞定数据分析与处理(图灵出品)》
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!