跳转到内容

去重实战:从检测到清理

上一节我们讨论了如何用 duplicated() 检测重复数据。这一节要把检测变成行动——用 drop_duplicates() 做实际的去重操作,并且重点讲解 LLM 场景下最实用的"按质量排序后去重"模式。

去重的核心决策:保留哪一条

当你发现数据中有重复时,最关键的问题不是"要不要删"而是"删掉之后保留哪一份"。drop_duplicates()keep 参数给了你三个选项:

python
import pandas as pd

df = pd.DataFrame({
    'text': ['A', 'B', 'A', 'C', 'B'],
    'quality': [4.5, 3.0, 4.8, 3.9, 2.5],
})

print("保留第一次出现的:")
print(df.drop_duplicates(subset=['text'], keep='first'))

print("\n保留最后一次出现的:")
print(df.drop_duplicates(subset=['text'], keep='last'))

keep='first' 保留每组中第一行出现的记录(默认),keep='last' 保留最后一行。但这两个都是"碰运气"的策略——你无法控制到底保留了质量好的还是差的。

按优先级排序后去重:生产级做法

在实际项目中,你几乎总是应该先排序再去重。这样 keep='first' 就能保证保留的是每组中优先级最高的那条:

python
import pandas as pd
import numpy as np

np.random.seed(42)
n = 100_000
df = pd.DataFrame({
    'id': range(n),
    'prompt': [f'问题{i%500}' for i in range(n)],
    'response': [f'回答{i%200}' for i in range(n)],
    'quality_score': np.round(np.random.uniform(1, 5, n), 1),
    'source': np.random.choice(['api', 'web'], n),
})

def dedup_by_quality(df, group_cols):
    before = len(df)
    
    sorted_df = df.sort_values('quality_score', ascending=False, na_position='last')
    deduped = sorted_df.drop_duplicates(subset=group_cols, keep='first')
    deduped = deduped.reset_index(drop=True)
    
    after = len(deduped)
    print(f"去重: {before:,}{after:,} (去除 {before-after:,}, {(before-after)/before*100:.1f}%)")
    print(f"平均质量分: {df['quality_score'].mean():.2f}{deduped['quality_score'].mean():.2f}")
    
    return deduped

result = dedup_by_quality(df, ['prompt'])

注意输出中平均质量分的变化——因为去重时丢弃了低质量的重复项,保留的全是高质量版本,所以整体均值会上升。这就是"智能去重"的价值:它不仅减少了数据量,还提升了数据质量。

多级去重流水线

实际项目中的去重往往不是一步完成的。你可能需要先做严格去重(完全相同的行),再做内容级去重(prompt+response 相同),最后做 prompt 级去重。每一步的粒度不同,保留策略也可以不同:

python
def multi_stage_dedup(df):
    original = len(df)
    
    stage1 = df.drop_duplicates(keep='first')
    print(f"Stage 1 (全量去重): {len(df):,}{len(stage1):,}")
    
    stage2 = stage1.sort_values('quality_score', ascending=False)\
                  .drop_duplicates(subset=['prompt','response'], keep='first')\
                  .reset_index(drop=True)
    print(f"Stage 2 (内容去重): {len(stage1):,}{len(stage2):,}")
    
    stage3 = stage2.sort_values('quality_score', ascending=False)\
                  .drop_duplicates(subset=['prompt'], keep='first')\
                  .reset_index(drop=True)
    print(f"Stage 3 (Prompt去重): {len(stage2):,}{len(stage3):,}")
    
    total_removed = original - len(stage3)
    print(f"\n总计: {original:,}{len(stage3):,} (去除 {total_removed:,}, {total_removed/original*100:.1f}%)")
    return stage3

result = multi_stage_dedup(df.copy())

多级去重的逻辑是逐级收紧去重条件:第一步去掉完全重复的数据录入错误;第二步去掉内容相同的多版本记录(可能来自不同采集时间);第三步合并同一问题的多次提问(只保留最佳回答)。每一级的粒度都更粗、删除量都更大,但同时也越来越激进——你需要根据具体业务需求决定执行到哪一级。

基于 MIT 许可发布