Pandas数据去重实战:8个高阶技巧解决90%业务场景

数据清洗是数据分析过程中最耗时却又最关键的环节。在实际业务中,我们遇到的重复数据远比教科书中的示例复杂——可能是跨多列的复合重复,需要根据时间戳保留最新记录,或是按特定业务规则筛选唯一值。本文将带你突破drop_duplicates()的基础用法,掌握一套应对真实场景的系统化解决方案。

1. 理解重复数据的本质分类

在开始技术操作前,我们需要建立对重复数据的类型学认知。根据业务场景不同,重复数据至少可分为三种典型情况:

  • 完全重复:所有列值完全相同的记录(可用df.duplicated()检测)
  • 关键字段重复:业务主键相同但其他字段不同的记录(如用户ID相同但行为数据不同)
  • 逻辑重复:业务含义相同但表现形式不同的记录(如"iPhone13"和"苹果手机13")
# 完全重复检测示例
import pandas as pd
data = {
    'order_id': [1001, 1002, 1001, 1004],
    'product': ['A', 'B', 'A', 'C'],
    'price': [299, 399, 299, 199]
}
df = pd.DataFrame(data)
print(df[df.duplicated(keep=False)])  # keep=False标记所有重复项

输出结果将显示order_id为1001的完全重复记录。但现实情况往往更复杂:

重复类型 检测方法 处理难点
完全重复 duplicated() 容易处理,但可能误伤
关键字段重复 subset参数 需要确定业务主键
逻辑重复 模糊匹配 需要自定义规则

2. 多列组合去重的进阶策略

当需要基于多列组合判断重复时,subset参数只是起点。更专业的做法是:

# 电商订单去重案例:同一用户同一天购买同一商品算重复
ecommerce_data = {
    'user_id': [101, 101, 102, 103, 101],
    'product_id': [2001, 2001, 2003, 2004, 2001],
    'order_date': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03'],
    'quantity': [1, 2, 1, 3, 1]
}
df = pd.DataFrame(ecommerce_data)

# 方法1:标准去重(保留第一条)
basic_dedupe = df.drop_duplicates(subset=['user_id', 'product_id', 'order_date'])

# 方法2:自定义保留逻辑(保留购买数量最大的记录)
advanced_dedupe = df.sort_values('quantity', ascending=False)\
                   .drop_duplicates(subset=['user_id', 'product_id', 'order_date'])

提示:当处理时间序列数据时,建议先用sort_values按时间排序,再配合keep='last'保留最新记录

3. 阈值控制与条件去重

某些业务场景需要保留出现一定次数的重复项,这时就需要引入阈值控制:

# 用户行为日志分析:只保留访问超过3次的记录
user_logs = {
    'user_id': [101, 101, 101, 102, 102, 103, 104, 104, 104, 104],
    'page_url': ['/home', '/product', '/cart', '/home', '/product', '/home', '/login', '/profile', '/settings', '/logout']
}
df = pd.DataFrame(user_logs)

# 计算每用户出现次数
df['count'] = df.groupby('user_id')['user_id'].transform('count')

# 阈值过滤
threshold = 3
filtered_df = df[df['count'] >= threshold].drop(columns=['count'])

更优雅的实现方式是使用groupby+filter

filtered_df = df.groupby('user_id').filter(lambda x: len(x) >= threshold)

4. 处理近似重复数据

真实数据中常存在非精确重复的情况,如:

  • 同一商品不同写法("MacBook Pro 13''" vs "13-inch MacBook Pro")
  • 地址缩写差异("St." vs "Street")
  • 数值四舍五入差异(19.99 vs 20.0)

这时需要模糊匹配技术:

from fuzzywuzzy import fuzz

products = ['iPhone 13', 'iphone13', 'Apple iPhone 13', 'Samsung Galaxy']

# 构建相似度矩阵
similarity_matrix = [[fuzz.ratio(p1, p2) for p1 in products] for p2 in products]

# 转换为DataFrame
sim_df = pd.DataFrame(similarity_matrix, index=products, columns=products)
print(sim_df)

输出结果将显示各产品名称间的相似度百分比,可设定阈值(如>80%)判定为重复。

5. 分组去重与聚合运算

有时我们需要在去重前后进行聚合计算:

# 销售数据:保留每个产品的最高销售额记录
sales_data = {
    'product': ['A', 'A', 'B', 'B', 'C'],
    'sales': [100, 150, 200, 180, 90],
    'date': ['2023-01-01', '2023-01-15', '2023-01-10', '2023-01-20', '2023-01-05']
}
df = pd.DataFrame(sales_data)

# 方法1:先排序再去重
df.sort_values('sales', ascending=False).drop_duplicates('product')

# 方法2:使用groupby+idxmax
max_indices = df.groupby('product')['sales'].idxmax()
df.loc[max_indices]

两种方法对比:

方法 优点 缺点
排序去重 代码简洁 需要全表排序
groupby+idxmax 性能更好 需要额外索引操作

6. 处理时间序列重复数据

时间序列数据的去重需要特殊处理:

# 传感器数据清洗:保留每个时间点的最新读数
sensor_data = {
    'timestamp': ['2023-01-01 08:00:00', '2023-01-01 08:00:05', 
                 '2023-01-01 08:00:05', '2023-01-01 08:00:10'],
    'sensor_id': [1, 1, 1, 2],
    'value': [23.5, 23.7, 23.6, 42.1]
}
df = pd.DataFrame(sensor_data)
df['timestamp'] = pd.to_datetime(df['timestamp'])

# 关键步骤:先按时间倒序排序
df = df.sort_values('timestamp', ascending=False)

# 保留每个传感器在每个时间点的第一条记录
clean_df = df.drop_duplicates(['sensor_id', 'timestamp'])

注意:处理时间数据时务必先转换为datetime类型,否则字符串比较可能导致意外结果

7. 内存优化型去重技巧

处理大型数据集时,标准去重方法可能内存不足。这时可采用:

分块处理法

chunk_size = 100000
unique_records = pd.DataFrame()

for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    chunk = chunk.drop_duplicates(subset=['key_column'])
    unique_records = pd.concat([unique_records, chunk])
    unique_records = unique_records.drop_duplicates(subset=['key_column'])

哈希去重法

# 为每行生成唯一哈希值
df['hash'] = df.apply(lambda row: hash(tuple(row)), axis=1)

# 基于哈希值去重
df = df.drop_duplicates('hash').drop(columns=['hash'])

8. 验证去重结果的完整性

去重后必须验证业务逻辑完整性:

# 验证示例:确保关键业务ID仍保持唯一
original_count = df['customer_id'].nunique()
deduped_count = clean_df['customer_id'].nunique()

assert original_count == deduped_count, "去重操作导致客户ID丢失!"

# 验证数据完整性
validation_results = {
    '指标': ['原始行数', '去重后行数', '重复行数', '唯一值比例'],
    '值': [len(df), len(clean_df), len(df)-len(clean_df), 
          round(len(clean_df)/len(df)*100, 2)]
}
pd.DataFrame(validation_results)

在实际项目中,我通常会建立一套完整的验证流程:

  1. 业务主键唯一性检查
  2. 关键指标统计值对比(如总和、平均值)
  3. 时间范围一致性验证
  4. 抽样检查典型重复案例

掌握这些技巧后,面对各种复杂的业务数据,你都能游刃有余地处理重复问题。记住,好的数据清洗不是简单地删除重复行,而是根据业务需求智能地保留最有价值的信息。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐