
本文介绍如何在 Pandas DataFrame 中识别并仅移除末尾连续出现的重复行组(基于指定列),同时保留所有前面的重复行(包括非连续重复),实现“只截断尾部冗余”的精准去重。
本文介绍如何在 pandas dataframe 中识别并仅移除**末尾连续出现的重复行组**(基于指定列),同时保留所有前面的重复行(包括非连续重复),实现“只截断尾部冗余”的精准去重。
在数据分析中,drop_duplicates() 是最常用的去重工具,但它默认按全局唯一性处理——要么保留首次出现的所有重复(keep='first'),要么全部删除(keep=False)。然而,当需求是仅删除 DataFrame 尾部连续重复的若干行(即:最后一批完全相同的记录),而保留前面所有重复(哪怕它们也成组出现)时,标准方法便不再适用。
例如,给定以下数据:
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]
})我们希望基于 'name' 和 'age' 判断逻辑重复(忽略 'id'),并仅移除最后连续出现的 ('tom', 25) 三行中的后两行,保留第一个 ('tom', 25)(即第7行,id=8),最终得到前8行结果。这本质上是识别「最后一个重复块」并截断其后续行。
✅ 核心思路:用累积分组标记连续重复段
关键在于将 DataFrame 按逻辑重复性划分为连续段(blocks),而非全局去重。步骤如下:
- 定义比较列:cols = ['name', 'age']
- 检测行变化:df[cols].ne(df[cols].shift()) 生成布尔矩阵,标识每列是否与上一行不同;.any(axis=1) 得到每行是否“与前一行不同”;
- 生成段ID:对上述布尔序列做 cumsum(),即可为每个连续相同块分配唯一整数标签(即“段号”);
- 定位末尾段:获取最大段号 grp.max(),然后用 grp.shift().ne(grp.max()) 构造掩码——该条件为 True 的行,表示不属于末尾段的首行及其之前所有行(即保留至末尾段的第一行为止)。
完整代码如下:
cols = ['name', 'age'] grp = df[cols].ne(df[cols].shift()).any(axis=1).cumsum() cond = grp.shift().ne(grp.max()) 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
? 中间过程解析(辅助理解)
| id | name | age | grp | grp.shift() | cond (grp.shift() != 5) |
|---|---|---|---|---|---|
| 1 | mary | 30 | 1 | NaN | True |
| 2 | mary | 30 | 1 | 1.0 | True |
| 3 | mary | 30 | 1 | 1.0 | True |
| 4 | tom | 25 | 2 | 1.0 | True |
| 5 | tom | 25 | 2 | 2.0 | True |
| 6 | john | 28 | 3 | 2.0 | True |
| 7 | sarah | 36 | 4 | 3.0 | True |
| 8 | tom | 25 | 5 | 4.0 | True |
| 9 | tom | 25 | 5 | 5.0 | False |
| 10 | tom | 25 | 5 | 5.0 | False |
可见:grp.max() == 5 对应最后一组 ('tom', 25);grp.shift() 将段号下移一行,因此 grp.shift() == 5 仅出现在第9、10行(即末尾段的第2、3行),cond 为 False 即被过滤。
⚠️ 注意事项
- 此方法依赖行序,务必确保 DataFrame 的索引/顺序符合业务逻辑(如已按时间排序);
- grp.shift() 会引入首个 NaN,但 NaN != grp.max() 恒为 True,故首行总被保留,符合预期;
- 若 DataFrame 为空或仅含1行,该逻辑仍安全(grp.max() 返回标量,shift() 处理正常);
- 如需扩展至更多列,只需更新 cols 列表即可,无需修改核心逻辑。
该方案以向量化操作高效实现“尾部连续块裁剪”,兼顾性能与可读性,是 drop_duplicates() 的重要补充策略。










