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

1次阅读

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 中处理有序重复模式的惯用范式。

text=ZqhQzanResources