drop_duplicates在千万行上卡住是因为默认全字段哈希+全内存索引构建,引发哈希冲突、内存碎片及object类型逐元素比较;应先压缩dtype、裁剪列、规整字符串、转category,或改用分块+set、pyarrow、polars等高效方案。

drop_duplicates 为什么在千万行上会卡住
直接调用 df.drop_duplicates() 处理千万级 DataFrame 时,内存暴涨、耗时几分钟甚至 OOM,根本不是函数“慢”,而是它默认走全字段哈希 + 全内存索引构建。Pandas 底层用 Python dict 做去重键,一旦行数超 500 万,哈希冲突和内存碎片就会明显拖慢速度,且无法流式处理。
常见错误现象:MemoryError、CPU 占满但进度条不动、df.drop_duplicates(subset=['id']) 比预期慢 10 倍以上。
- 别对未设置索引的宽表(列 > 50)直接去重,列越多哈希键越长,内存开销指数上升
-
keep='last'比keep='first'多一次完整扫描,千万行下差 2–3 倍时间 - 字符串列若含空值或长文本(如 JSON 片段),
drop_duplicates会触发 object 类型的逐元素比较,彻底失去向量化优势
先用 dtype 和列裁剪压低内存水位
去重前不压缩数据类型,等于拿 8 字节 int64 存 ID、用 object 存纯数字字符串——这会让哈希表体积翻 3–5 倍。必须在 read_csv 或构造 DataFrame 阶段就控制住。
实操建议:
- 读取时强制指定
dtype:ID 列用pd.Int32Dtype()或uint32,布尔列用boolean,避免默认object - 删掉去重无关列:
df = df[['id', 'email']].copy(),别留着 20 列只用其中 2 列 - 字符串列提前规整:
df['email'] = df['email'].str.strip().str.lower(),避免因空格/大小写导致本该去重的没去成 - 对重复率高的列(如状态码、分类标签),可转为
category类型:df['status'] = df['status'].astype('category')
分块读取 + set 增量去重更稳
当内存确实扛不住单次加载(比如机器只有 16GB RAM,数据占 12GB+),硬靠 Pandas 内存去重就是死路。这时应放弃“一个 DataFrame 走到底”的思路,改用文件流式 + Python 原生 set 记录已见 key。
核心逻辑:按固定 chunksize 读 CSV → 提取去重字段元组 → 判重并写入新文件 → 跳过重复行。
seen = set()
with open('output.csv', 'w') as fout:
for chunk in pd.read_csv('input.csv', chunksize=50000):
for _, row in chunk.iterrows():
key = (row['id'], row['email']) # 注意类型一致,None 会报错
if key not in seen:
seen.add(key)
fout.write(','.join(map(str, row)) + '\n')
注意点:
-
key必须是不可变类型,list不行,tuple才行;含NaN的字段要先 fillna('NULL') -
seen集合最终会吃掉约 1.5× 去重后 key 的内存(Python set 有负载因子),千万级唯一 key 约占 500MB+ - 不要用
chunk.drop_duplicates()后拼接——块间重复仍存在,得全局判重
真要强依赖 Pandas,就换 backend:PyArrow + Polars
如果业务强绑定 Pandas API(比如后续链路全是 .groupby、.merge),又卡在 drop_duplicates,可以临时切 backend:用 PyArrow 表做去重,再转回 Pandas。
原因:PyArrow 的 distinct() 是 C++ 实现,支持零拷贝哈希,千万行去重通常在 10 秒内完成,且内存峰值比 Pandas 低 40%+。
import pyarrow as pa import pyarrow.compute as pc <p>table = pa.Table.from_pandas(df)</p><h1>只对 id 和 email 去重,保持原始顺序(keep='first')</h1><p>unique_table = table.group_by(['id', 'email']).first() df_unique = unique_table.to_pandas() </p>
或者一步到位用 Polars(语法更接近 Pandas):
import polars as pl df_pl = pl.from_pandas(df) df_unique = df_pl.unique(subset=['id', 'email'], keep='first').to_pandas()
注意:
- PyArrow
group_by(...).first()默认不保序,如需严格等价keep='first',得加maintain_order=True(Polars 默认保序) - Polars 对字符串去重比 Pandas 快 3–5 倍,但对混合类型列(如 string + list)支持弱,得先 flatten
- 别在 Pandas DataFrame 上直接调
.to_arrow()再操作——序列化开销大,应从源头读取时就用pa.csv.read_csv()
实际去重最耗神的往往不是算法本身,而是你没意识到哪些列真正参与判重、哪些空值正在悄悄破坏哈希一致性、以及内存是否真的被 dtype 和冗余列无声吃掉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











