查看: 274|回复: 0

Pandas读取大CSV太慢的六个参数优化与排错实践指南

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
背景:2GB CSV 为什么读得慢
一个 2GB 的 CSV,pd.read_csv() 卡 8 分钟、内存飙到 12GB,这是很多人处理稍大数据时的常见场景。问题通常不在 pandas 本身,而在默认参数偏保守:它会把每列都当成可能的混合类型,先全部读进来,再逐列推断。下面 6 个技巧按改动成本从低到高排列,目标是在不换工具的前提下同时降低耗时和内存。

1. 指定 dtype,别让 pandas 猜类型
这是收益最大、改动最小的一条。
  1. # 慢:让 pandas 推断 30 列的类型
  2. df = pd.read_csv('big.csv')
  3. # 快:直接告诉它每列是什么
  4. df = pd.read_csv('big.csv', dtype={
  5.     'user_id': 'int64',
  6.     'city': 'category',
  7.     'amount': 'float32',
  8. })
复制代码
两个关键点:字符串列中重复值多的列(城市、状态、品类)用 category,内存能从几百 MB 降到几十 MB;浮点数在精度够用时用 float32 代替 float64,直接省约一半内存。不知道有哪些列时,先读小样本探查:
  1. cols = pd.read_csv('big.csv', nrows=5).columns
  2. sample = pd.read_csv('big.csv', nrows=10000)
  3. print(sample.dtypes)
  4. print(sample.memory_usage(deep=True).sum() / 1024**2, 'MB')
复制代码

2. 用 usecols 只读需要的列
如果文件有 50 列而只用 6 列,usecols 可以直接绕过其余 44 列的解析成本:
  1. df = pd.read_csv('big.csv', usecols=['user_id', 'amount', 'city', 'date'])
复制代码
按列位置读通常比按列名读略快,因为省掉一次名字匹配:
  1. df = pd.read_csv('big.csv', usecols=[0, 3, 7, 11])
复制代码

3. 日期不要在读取时用 parse_dates 解析
parse_dates 很方便,但它是逐值调用日期解析器的,在大文件上非常昂贵。更快的做法是先当字符串读进来,再一次性转换:
  1. # 慢
  2. df = pd.read_csv('big.csv', parse_dates=['date'])
  3. # 快:先读字符串,再一次性转换
  4. df = pd.read_csv('big.csv')
  5. df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
复制代码
务必带上 format。不指定格式时 pandas 会逐值猜测;指定格式后才有机会走向量化的快路径。

4. 内存不够时用 chunksize 分块处理
这是内存不足时的标准解法:读成分块迭代器,边算边丢。
  1. total = 0.0
  2. for chunk in pd.read_csv('big.csv', chunksize=200_000,
  3.         usecols=['amount', 'city'],
  4.         dtype={'amount': 'float32', 'city': 'category'}):
  5.     total += chunk.groupby('city', observed=True)['amount'].sum().sum()
  6. print(total)
复制代码
注意两点:chunksize 不是越小越好,太小会放大 Python 循环开销,20 万到 50 万行比较合适;groupby 加 observed=True,否则 category 类型会生成全组合的笛卡尔积行,白占内存。

5. 换 pyarrow 引擎
pandas 2.x 起支持 engine='pyarrow',多线程解析,在多核机器上提速明显:
  1. df = pd.read_csv('big.csv', engine='pyarrow', dtype_backend='pyarrow')
复制代码
没装的话先安装:
  1. pip install pyarrow
复制代码
dtype_backend='pyarrow' 会使用 Arrow 的原生类型(string、int32 等),内存占用通常再降一档。缺点是部分老 API 不兼容;如果导入后要和 scikit-learn 打交道,建议再 .convert_dtypes() 或直接 .to_numpy()。

6. 反复读取同一文件:先把 CSV 转成 Parquet
如果要反复读同一个文件,就别每次都解析 CSV。一次转换后,后续读取通常快 5 到 10 倍:
  1. # 一次性转换
  2. df = pd.read_csv('big.csv', dtype={...})
  3. df.to_parquet('big.parquet', compression='snappy')
  4. # 后续读取:列式存储,只加载需要的列
  5. df = pd.read_parquet('big.parquet', columns=['user_id', 'amount'])
复制代码
Parquet 的三个好处:列式存储让 columns= 能真正跳过不用的列,不用解析整个文件;自带 schema,不用每次推断 dtype;snappy 压缩后体积通常只有原 CSV 的 20% 到 30%。不过文件太小(小于 50MB)时,Parquet 的列式开销反而不划算,直接读 CSV 更快。

附:一个通用提速模板
把上面几条打包成一个函数,日常直接复用:
  1. import pandas as pd
  2. def read_fast(path, usecols=None, dtypes=None, chunksize=None):
  3.     kwargs = dict(
  4.         usecols=usecols,
  5.         dtype=dtypes,
  6.         engine='pyarrow',
  7.     )
  8.     if chunksize:
  9.         return pd.read_csv(path, chunksize=chunksize, **kwargs)
  10.     return pd.read_csv(path, **kwargs)
  11. # 用法
  12. df = read_fast(
  13.     'big.csv',
  14.     usecols=['user_id', 'amount', 'city', 'date'],
  15.     dtypes={'user_id': 'int64', 'amount': 'float32', 'city': 'category'},
  16. )
  17. df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
复制代码

排错清单
报 MemoryError:先加 usecols + category,再上 chunksize,最后考虑换 Parquet。
dtype 指定后报类型冲突:说明该列里有脏值,比如数字列混进了 'NULL' 字符串,可加 na_values=['NULL', '', 'NA']。
转换 Parquet 后读取更慢:文件太小(小于 50MB)时 Parquet 的列式开销反而不划算,CSV 直接读更快。
chunksize 循环里 groupby 结果不对:分块会把同一个 key 切到不同块,需要把每块结果再 concat 后聚合一次,别直接对每块 sum() 后相加。

小结
优先级排序:usecols + dtype(零成本,收益最大)→ 延后日期解析 → 换 pyarrow 引擎 → 分块 → 转 Parquet。大多数 “pandas 太慢” 的场景,光做前两条就能解决,根本用不到换工具。真正需要上 Dask / Polars 的,是那种内存死活放不下的单机极限场景;在那之前,先把 read_csv 的默认参数调明白。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-1 13:02 , Processed in 0.023268 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部