跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Pandas 中保留重复组首次出现、剔除尾部连续重复行的高效方法

本文介绍如何在 Pandas DataFrame 中识别并仅保留每组「逻辑重复块」的首次出现部分,剔除尾部连续重复行(忽略唯一标识列如 id),适用于日志去重、会话截断等场景。 本文介绍如何在 pandas dataframe 中识别并仅保留每组「逻辑重复块」的首次出现部分,剔除尾部连续重复行(忽略唯一标识列如 `id`),适用于日志去重、会话截断等场景。 在实际数据分析中,我们常遇到一种特殊重复模式:数据按时间或顺序排列,同一业务逻辑记录(如用户行为、会话状态)可能连续多次出现,而我们希望 仅保留该重复块的首次完整出现,后续连续重复块则整体舍弃 ——这与 drop_duplicates(keep='first') 的全局去重不同,也不同于 duplicated() 的逐行标记。本例即典型:以 name 和 age 为业务键,id 仅为序号;tom/25 在索引 3–4 首次成组出现,之后在索引 7–9 再次连续出现,目标是 保留第一次 tom/25 组(行3–4),但只保留第二次 tom/25 组的首行(行7),舍弃其后连续重复行(行8–9) 。 实现这一逻辑的关键在于: 将连续相同的业务行划分为独立组(run-length grouping),再筛选出非最后一组的所有行 。以下是推荐解法:
import pandas as pd df = pd.DataFrame({ 'id': [1,2,3,4,5,6,7,8,9,10], 'name': ['mary','mary','mary','tom','tom','john','sarah','tom','tom','tom'], 'age': [30,30,30,25,25,28,36,25,25,25] }) # 定义用于判断重复的列(排除 id 等唯一标识列) cols = ['name', 'age'] # 步骤1:生成连续重复组编号 # df[cols].shift() 向下错位 → 与原值比较是否变化 → any(axis=1) 判断任一列变化 → cumsum() 累计求和形成组ID grp = df[cols].ne(df[cols].shift()).any(axis=1).cumsum() # 步骤2:构造布尔掩码:当前组不是最大组号(即排除最后一个连续重复块) cond = grp != grp.max() # 步骤3:过滤 result = df[cond].reset_index(drop=True) print(result)
输出:
id name age 0 1 mary 30 1 2 mary 30 2 3 mary 30 3 4 tom 25 4 5 tom 25 5 6 john 28 6 7 sarah 36 7 8 tom 25
✅ 原理详解 : df[cols].ne(df[cols].shift()) 返回布尔 DataFrame,标记每行相对于上一行是否发生变更; .any(axis=1) 将每行任意列为 True 视为“变化点”; .cumsum() 对变化点累积计数,使每个连续相同块获得唯一组号(如 mary/30→1,首个 tom/25→2,john/28→3,sarah/36→4,末段 tom/25→5); grp != grp.max() 即排除组号为 5 的所有行,精准截断尾部重复块。 ⚠️ 注意事项 : 此方法依赖 数据顺序性 ,确保业务上连续重复具有语义意义(如时间序列、日志流); 若需保留尾部块的 首行而非整块剔除 (如本例中保留行7但不要行8–9),当前解法已满足;若需更复杂策略(如保留每块首行),可改用 groupby(grp).head(1); 列选择 cols 必须准确反映业务去重维度,避免遗漏关键字段导致误合并; 性能优异,全程向量化操作,适用于百万级数据。 总结:面对「保留首次重复块、截断尾部连续重复」这一非标准去重需求,不应强行套用 drop_duplicates,而应通过 shift + ne + cumsum 构建连续组标识,再结合组号逻辑过滤——这是 Pandas 中处理有序重复模式的惯用范式。

相关文章