大家好,我是正在实战各种AI项目的程序员晚枫。
今天继续数据清洗的话题——处理重复值。
重复数据会导致统计结果失真,在分析前必须处理。Pandas提供了强大的工具,让你轻松找出并清理重复项。
创建示例数据12345678910import pandas as pddf = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '张三', '李四', '赵六'], '年龄': [25, 30, 35, 25, 30, 28], '城市': ['北京', '上海', '广州', '北京', '上海', '深圳'], '薪资': [15000, 20000, 18000, 15000, 22000, 16000]})print(df)检测重复值完全重复的行12345678910111213# 标记重复行(保留第一个)print(df.duplicated())# 统计重复行数量print(df.duplicated().sum())# 查看重复的行duplicates = df[df.duplicated()]print(duplicates)# 包括所有重复(不只是后面的)all_duplicates = df[df.duplicated(keep=False)]print(all_duplicates)基于特定列判断重复12345678# 只看姓名是否重复print(df.duplicated(subset=['姓名']))# 看姓名+年龄组合是否重复print(df.duplicated(subset=['姓名', '年龄']))# 统计每个姓名的出现次数print(df['姓名'].value_counts())删除重复值方法1:保留第一个(默认)12345# 删除完全重复的行,保留第一次出现的df_clean = df.drop_duplicates()# 等价于df_clean = df.drop_duplicates(keep='first')方法2:保留最后一个12# 保留最后一次出现的,删除前面的df_clean = df.drop_duplicates(keep='last')方法3:全部删除12# 只要有重复就全部删除(一个不留)df_clean = df.drop_duplicates(keep=False)基于特定列去重12345# 按姓名去重(只看姓名,其他列不管)df_clean = df.drop_duplicates(subset=['姓名'])# 按多列组合去重df_clean = df.drop_duplicates(subset=['姓名', '城市'])实战场景场景1:用户注册数据去重1234567891011121314151617users = 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:订单数据合并重复1234567891011121314151617orders = 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)高级技巧标记重复但不删除12345# 添加一列标记是否重复df['is_duplicate'] = df.duplicated(subset=['姓名', '年龄'], keep=False)# 查看重复组的大小df['dup_count'] = df.groupby(['姓名', '年龄'])['姓名'].transform('count')查找最完整的记录12345678# 有时重复行的某些字段有缺失,想保留最完整的# 计算每行非空值数量df['non_null_count'] = df.notnull().sum(axis=1)# 按姓名分组,保留非空值最多的行df_clean = (df.sort_values('non_null_count', ascending=False) .drop_duplicates(subset=['姓名'], keep='first'))模糊匹配去重123# 姓名可能有细微差别(如空格、大小写)df['姓名_clean'] = df['姓名'].str.strip().str.lower()df_clean = df.drop_duplicates(subset=['姓名_clean'])完整清洗流程1234567891011121314151617181920212223242526272829303132333435363738def 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)性能对比:不同去重方式1234567891011121314151617import pandas as pdimport numpy as npdf = 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')进阶用法条件去重123456789101112# 每个用户保留最新一条记录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()模糊去重12345678# 名字相似但不完全相同的记录from difflib import get_close_matchesnames = 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:去重后索引不连续12345df_dedup = df.drop_duplicates()print(df_dedup.index) # [0, 1, 3, 5, 7, ...] 不连续!# 重置索引df_dedup = df.drop_duplicates().reset_index(drop=True)❌ 坑2:忽略NaN的重复检测1234567# 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行不算重复实战案例:清洗用户注册数据1234567891011121314151617181920212223242526272829303132import pandas as pdimport numpy as npnp.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. 查看重复的emaildup_emails = df[df.duplicated(subset='email', keep=False)]print(f"\n重复email详情:")print(dup_emails.sort_values('email').head(10))重复值产生的常见原因数据采集重复:日志系统重复采集用户重复提交:前端没有防重复提交多源数据合并:同一用户在不同表中都有记录时间窗口重叠:昨天和今天的数据有交叉重复值的深层分析12345678910111213141516171819202122# 不只是简单去重,还要分析重复的原因# 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}")重复值处理决策指南123456789101112131415发现重复值 │ ├── 完全重复(所有字段一样)→ 直接删除 │ ├── 部分重复(关键字段一样) │ ├── 保留哪条? │ │ ├── 保留最新 → keep='last' │ │ ├── 保留最早 → keep='first' │ │ └── 都不保留 → keep=False │ └── 删除前先分析原因 │ └── 模糊重复(相似但不完全一样) ├── 名字拼写差异 → 标准化后去重 ├── 地址格式差异 → 统一格式后去重 └── 编码差异 → 映射表统一后去重重复值的预防123456789101112# 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 编程实战课 帮你从零上手!
👉 免费试看:网盘链接,免费试看前3讲,先看看适不适合自己👉 - 👉 课程报名:点击这里报名,现在报名还送书📖