默认 drop_duplicates() 保留首次出现行,若数据按时间升序排列则最新记录被删;应先降序排序再去重,或用 groupby().idxmax() 直接定位每组最新行索引。

用 pandas.DataFrame.drop_duplicates() 时为什么最新记录总被删掉?
默认情况下,drop_duplicates() 保留的是**第一次出现**的行,不是最新的。如果你的数据按时间升序排列(比如 created_at 列从小到大),那最早那条就被留着,最新的一条反被当重复删了。
解决办法很简单:先按时间列**降序排序**,再删重——这样“第一次出现”的就变成最新记录了。
- 假设时间列为
updated_at,且是datetime类型(如果不是,先用pd.to_datetime()转) - 执行顺序必须是:
df.sort_values('updated_at', ascending=False).drop_duplicates(subset=['user_id'], keep='first') -
subset指定判断重复的字段(如['user_id', 'product_id']),不填则整行比对 -
keep='first'是默认值,可省略;但显式写出来更清晰,避免误以为要留最旧的
当数据量大、内存吃紧时,sort_values + drop_duplicates 还够快吗?
对千万级 DataFrame,先排序再去重可能触发内存暴涨甚至 OOM,尤其在低配机器或 Docker 容器里。这不是算法问题,而是 sort_values 默认使用 quicksort,最坏情况 O(n²),且会复制整个 DataFrame。
更稳的替代方案是分组取最大时间索引:
idx = df.groupby('user_id')['updated_at'].idxmax()
result = df.loc[idx].sort_values('updated_at', ascending=False)
这个写法优势在于:
- 不改变原始顺序前提下,直接定位每组最新行的原始索引,避免全量排序
-
idxmax()内部优化好,比sort + drop更省内存 - 如果
updated_at有并列(同一秒多个更新),idxmax()返回第一个遇到的最大值索引——行为可预测,不像sort受稳定/不稳定排序影响
用 groupby().last() 真的能保留最新记录吗?
不能直接依赖。因为 last() 取的是**组内最后一条物理位置的记录**,和时间无关。如果原始数据没按时间排序,last() 返回的很可能不是最新那条。
只有当你明确保证数据已按 updated_at 升序排列,并且想留每组“最后进来的那条”,才可用:
df.sort_values('updated_at').groupby('user_id').last().reset_index()
但这种写法隐含两个风险点:
- 如果排序漏了
inplace=False(默认就是 False),groupby仍作用于未排序原数据 → 结果错 - 一旦后续有人把上游数据源改成乱序插入,这个逻辑就静默失效,很难排查
- 不如显式用
idxmax()直接锚定时间字段,语义清晰、抗干扰强
删除重复时意外丢掉了非关键字段的差异怎么办?
典型场景:两条记录 user_id 和 updated_at 相同,但 status 或 note 不同。用 drop_duplicates(subset=['user_id', 'updated_at']) 会随机留一条,丢失另一条的字段值。
这时不能靠“删重”,得主动聚合:
- 用
groupby(['user_id', 'updated_at']).agg({'status': 'first', 'note': 'last', 'amount': 'sum'})显式定义各字段合并策略 - 如果想保留某字段的非空值(比如
note可能为空),改用agg({'note': lambda x: x.dropna().iloc[0] if not x.dropna().empty else None}) - 注意:聚合后原始索引丢失,如需还原时间戳最大那条的完整原始行,还是回到
idxmax()+loc方案更稳妥
真正难的从来不是“怎么删重复”,而是搞清“重复”的定义是否覆盖了业务上需要保留的字段差异——这点容易在初期被忽略,等上线跑几天才发现状态字段对不上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











