3个kee函数深坑,面试必问的避坑指南
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 方法?或者你有其他独门秘籍?评论区交流,看看谁的经验更实战。