3个kee函数深坑,面试必问的避坑指南
官方文档翻了三遍还是晕?别慌,keep 这个概念在数据处理里太容易踩雷了。很多后端和算法岗面试必问,答不上来直接减分。
坑的现象:数据莫名消失或重复
做数据清洗时,你是不是遇到过这种崩溃瞬间:明明用 keep 去重,结果数据行数不对,或者关键列的值被错误覆盖?
典型报错场景:
- 静默失败:代码没报错,但输出数据量比预期少,且没有日志提示。
- 逻辑混乱:同一个键值对,
keep='first'和keep='last'结果完全相反,甚至和drop_duplicates行为不一致。 - 性能雪崩:在百万级数据上,简单的
keep操作导致内存溢出或耗时激增。
我在掘金技术社区看到不少老哥吐槽,说 Pandas 的 drop_duplicates 默认行为是 keep='first',但很多人误以为是 keep='last',导致线上数据错位。
根本原因:索引与值的混淆
90% 的坑,都源于没搞清楚 keep 到底作用在行上还是列上,以及它和索引的关系。
核心误区:
- 误区1:认为
keep是全局去重,实际它是基于指定列(或所有列)的局部去重。 - 误区2:忽略索引重置。如果 DataFrame 索引不是默认的
RangeIndex,keep操作后索引可能不连续,导致后续合并或切片出错。 - 误区3:混淆
keep和unique。unique返回唯一值数组,keep是保留完整行。
技术细节:
keep 参数在 Pandas 中主要用于 drop_duplicates,其逻辑是:
keep='first':保留每组重复数据中第一次出现的行。keep='last':保留每组重复数据中最后一次出现的行。keep=False:删除所有重复行,只保留完全唯一的行。
注意:这里的“组”是由 subset 参数定义的列组合决定的。如果没指定 subset,则是基于所有列判断唯一性。
正确写法对比:代码即真理
错误写法:盲目依赖默认值,不检查索引状态
import pandas as pd# 构造测试数据:注意索引是乱序的
df = pd.DataFrame({'id': [1, 2, 2, 3, 3, 3],'name': ['Alice', 'Bob', 'Bob', 'Charlie', 'Charlie', 'Charlie'],'score': [80, 90, 95, 70, 75, 80]
}, index=[5, 1, 2, 3, 4, 0]) # 故意打乱索引print("原始数据索引:")
print(df.index.tolist())# 错误:直接去重,不重置索引
df_dropped = df.drop_duplicates(subset=['id'], keep='first')print("去重后数据:")
print(df_dropped)
print("去重后索引:", df_dropped.index.tolist())# 潜在问题:索引不连续,后续如果按索引操作会出错
正确写法:显式指定参数,重置索引,验证结果
import pandas as pd# 构造相同测试数据
df = pd.DataFrame({'id': [1, 2, 2, 3, 3, 3],'name': ['Alice', 'Bob', 'Bob', 'Charlie', 'Charlie', 'Charlie'],'score': [80, 90, 95, 70, 75, 80]
}, index=[5, 1, 2, 3, 4, 0])# 正确步骤1:显式指定 keep 和 subset
# 正确步骤2:重置索引,确保后续操作安全
df_clean = df.drop_duplicates(subset=['id'], keep='first').reset_index(drop=True)print("清洗后数据:")
print(df_clean)
print("清洗后索引:", df_clean.index.tolist())# 进阶:如果需要保留特定分数的最大值,不能只用 keep
# 正确做法:使用 groupby + agg
df_best = df.sort_values('score', ascending=False).drop_duplicates(subset=['id'], keep='first').reset_index(drop=True)
print("每个ID最高分:")
print(df_best)
关键差异:
- 索引处理:
reset_index(drop=True)是防坑关键,避免索引错位。 - 业务逻辑:
keep只能按“出现顺序”保留,不能按“值大小”保留。如果需要保留最大/最小值,必须结合sort_values。 - 显式参数:永远不要依赖默认参数,面试中问“默认行为是什么”,答错就是硬伤。
复现与修复代码:实战避坑清单
场景1:日志数据去重,保留最新记录
import pandas as pd
from datetime import datetime# 模拟日志数据:同一用户多次登录
log_data = {'user_id': [101, 101, 102, 102, 103],'login_time': ['2023-01-01 10:00', '2023-01-01 12:00', '2023-01-01 11:00', '2023-01-01 13:00', '2023-01-01 09:00'],'ip': ['192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4', '192.168.1.5']
}df_log = pd.DataFrame(log_data)
df_log['login_time'] = pd.to_datetime(df_log['login_time'])# 错误:直接 keep='last',但数据没排序,'last' 是行序最后,不是时间最后
# 正确:先按时间排序,再 keep='last'
df_latest = df_log.sort_values('login_time', ascending=True).drop_duplicates(subset=['user_id'], keep='last').reset_index(drop=True)print("每个用户最新登录记录:")
print(df_latest)
场景2:多维数据唯一性校验
# 场景:订单表,需要确保 (user_id, product_id, order_time) 组合唯一
orders = pd.DataFrame({'user_id': [1, 1, 2, 2, 2],'product_id': [10, 10, 20, 20, 30],'order_time': ['2023-05-01', '2023-05-01', '2023-05-01', '2023-05-02', '2023-05-02']
})# 错误:只按 user_id 去重,导致不同商品被误删
# 正确:指定 subset 为多列
df_unique = orders.drop_duplicates(subset=['user_id', 'product_id', 'order_time'], keep='first').reset_index(drop=True)print("唯一订单记录:")
print(df_unique)# 验证:检查是否还有重复
duplicates = df_unique[df_unique.duplicated(subset=['user_id', 'product_id', 'order_time'], keep=False)]
print("剩余重复数:", len(duplicates))
规避建议:面试与实战双保险
面试高频问法:
- 问:
drop_duplicates中keep参数有哪些值?默认值是什么? 答:'first','last',False。默认是'first'。 - 问:如果数据无序,
keep='last'能保证保留时间最新的记录吗? 答:不能。必须先按时间字段排序,再执行drop_duplicates。 - 问:
keep=False和drop_duplicates默认行为有什么区别? 答:keep=False删除所有重复行,只保留完全唯一的行;默认keep='first'保留每组第一次出现的行。
实战最佳实践:
- 永远重置索引:去重后加
reset_index(drop=True),避免索引污染。 - 先排序再去重:如果业务需要保留“最大/最小/最新”值,必须先
sort_values。 - 显式指定 subset:不要依赖全列去重,除非你100%确定所有列都参与唯一性判断。
- 日志记录:在关键去重步骤前后,打印行数变化,便于排查数据丢失。
- 单元测试:针对
keep参数编写边界测试用例,覆盖索引乱序、空值、多列组合等场景。
性能优化技巧:
- 对于超大内存数据,
drop_duplicates是内存密集型操作。建议先抽样测试,确认逻辑正确后再全量执行。 - 如果数据量超过内存限制,考虑使用 Dask 或 Spark 的
drop_duplicates,逻辑类似,但分布式执行。 - 避免在循环中调用
drop_duplicates,这会显著降低性能。
常见陷阱清单:
- ❌ 依赖默认
keep='first'而不确认业务需求。 - ❌ 数据未排序就直接用
keep='last'期望保留最新记录。 - ❌ 忽略索引重置,导致后续
loc或iloc操作出错。 - ❌ 在包含
NaN的数据上直接去重,NaN的相等性判断可能导致意外结果。 - ❌ 误以为
keep可以基于值大小保留,实际需要结合sort_values。
掘金技术社区 多位作者强调,数据处理中“隐式行为”是最大的坑。Pandas 的许多函数默认值看似合理,但在复杂业务场景下容易引发隐蔽错误。养成显式指定参数的习惯,是避免踩坑的关键。
你更常用哪种写法? 是习惯先排序再去重,还是依赖 groupby 的 agg 方法?或者你有其他独门秘籍?评论区交流,看看谁的经验更实战。