Pandas 中保留尾部连续重复组的首个出现块(非全局去重)

1次阅读

Pandas 中保留尾部连续重复组的首个出现块(非全局去重)

本文介绍如何在 pandas DataFrame 中识别并仅移除尾部连续重复的行组(基于指定列),保留前面所有重复块及最后一个重复块的首部分,而非简单全局去重;适用于日志、时序或分段数据中需“截断末尾冗余”的场景。

本文介绍如何在 pandas dataframe 中识别并**仅移除尾部连续重复的行组**(基于指定列),保留前面所有重复块及最后一个重复块的首部分,而非简单全局去重;适用于日志、时序或分段数据中需“截断末尾冗余”的场景。

在实际数据分析中,我们常遇到一种特殊去重需求:不是对整张表做 drop_duplicates()(会抹平所有重复),也不是按顺序保留首次出现(keep=’first’),而是希望仅剔除数据末尾连续出现的、完全相同的行组,同时保留前面所有历史重复块——例如传感器日志中最后几条重复上报记录、用户行为流末尾的冗余点击、或分段处理后残留的重复尾帧。

以如下 DataFrame 为例,我们关注 ‘name’ 和 ‘age’ 列组合是否连续重复(忽略唯一标识 ‘id’):

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] })

目标是保留前两组 ‘tom’(第3–4行和第7行),但仅保留最后一组 ‘tom’ 的首行(第7行),剔除其后连续重复的第8–9行,最终得到 8 行结果。

核心思路:识别“连续重复块”并标记尾块

关键在于区分 “全局重复”“局部连续重复”。我们不关心 ‘tom’ 是否曾在前面出现过,而只关注:从某一行开始,后续若干行在指定列上是否与前一行完全一致且形成连续段

解决方案采用三步链式逻辑:

  1. 构造连续块标识:使用 df[cols].ne(df[cols].shift()).any(axis=1) 找出每行是否与上一行不同 → 得到布尔序列;再用 .cumsum() 累计求和,为每个连续相同块分配唯一组号;
  2. 定位尾块:获取最大组号(即最后一组的编号),通过 grp.shift().ne(grp.max()) 判断当前行是否属于“尾块之前的部分”;
  3. 过滤保留:用该布尔条件索引原 DataFrame,即可精准截断尾部连续重复块。

完整代码如下:

cols = ['name', 'age']  # 定义用于判断重复的列(排除 id) grp = df[cols].ne(df[cols].shift()).any(axis=1).cumsum()  # 生成连续块组号 cond = grp.shift().ne(grp.max())  # True 表示:该行不属于尾块,或为尾块首行(因 shift 后错位) 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

关键说明与注意事项

  • grp.shift().ne(grp.max()) 是精髓:它让尾块中除第一行外的所有行对应 False(因为 grp.shift() 在尾块首行返回前一块编号,与 grp.max() 不等 → True;后续行 grp.shift() 返回尾块编号,等于 grp.max() → False),从而自然保留尾块首行、剔除其余。
  • ⚠️ 此方法依赖行序稳定性,不可在未排序的 DataFrame 上直接使用;若原始顺序无意义,请先明确排序依据(如时间戳、id)再执行。
  • ? 若需保留整个尾块的首行 + 前面所有块(即本例效果),此方案最优;若需“仅保留每个连续块的首行”,应改用 df[~df[cols].duplicated()]。
  • ? 可扩展性:cols 支持多列,也兼容含 NaN 的列(ne() 对 NaN 比较返回 True,符合连续块中断预期)。

该技巧填补了 Pandas 原生去重功能的空白,是处理具有局部连续性特征的数据时高效、可读且无需循环的向量化解法。

text=ZqhQzanResources