☰
pandas 读大 CSV 太慢?6 个实测提速技巧(dtype / 分块 / 引擎选择)
2026/10/1 14:40:25 网站建设 项目流程

pandas 读大 CSV 太慢?6 个实测提速技巧(dtype / 分块 / 引擎选择)

关键词前置:pandas、read_csv、大文件、dtype、分块读取、提速

一个 2GB 的 CSV,pd.read_csv()卡了 8 分钟,内存还飙到 12GB——这是很多人第一次处理"稍微大一点"的数据时都会遇到的场面。

问题基本不在 pandas 慢,而在默认参数太保守:它会把每一列都当成可能的混合类型,先全部读进来,再逐个推断。下面 6 个技巧,按改动成本从低到高排列,实测能把耗时和内存同时砍掉一大截。


1. 先指定 dtype,别让 pandas 猜

这是收益最大、改动最小的一条。

# 慢:让 pandas 推断 30 列的类型df=pd.read_csv("big.csv")# 快:直接告诉它每列是什么df=pd.read_csv("big.csv",dtype={"user_id":"int64","city":"category","amount":"float32",})

两个关键点:

  • 字符串列用category:重复值多的列(城市、状态、品类)转 category 后,内存能从几百 MB 降到几十 MB;
  • 浮点数能用float32就别用float64:精度够用的场景直接省一半内存。

不知道有哪些列?先读一小段探一下:

cols=pd.read_csv("big.csv",nrows=5).columns sample=pd.read_csv("big.csv",nrows=10000)print(sample.dtypes)print(sample.memory_usage(deep=True).sum()/1024**2,"MB")

2. 只读需要的列(usecols)

如果文件有 50 列而你只用 6 列,usecols是直接绕过剩下 44 列的解析成本:

df=pd.read_csv("big.csv",usecols=["user_id","amount","city","date"])

用列的位置也比用名字快一点点(省掉一次名字匹配):

df=pd.read_csv("big.csv",usecols=[0,3,7,11])

3. 日期别在读取时解析,读进来再转

parse_dates很方便,但它是逐值调用日期解析器的,在大文件上极其昂贵。

# 慢df=pd.read_csv("big.csv",parse_dates=["date"])# 快:先当字符串读进来,再一次性转df=pd.read_csv("big.csv")df["date"]=pd.to_datetime(df["date"],format="%Y-%m-%d")

务必带上format。不指定格式时 pandas 会逐值猜测,指定了才能走向量化的快路径。


4. 分块处理:chunksize

内存不够时的标准解法。读成分块迭代器,边算边丢:

total=0.0forchunkinpd.read_csv("big.csv",chunksize=200_000,usecols=["amount","city"],dtype={"amount":"float32","city":"category"}):total+=chunk.groupby("city",observed=True)["amount"].sum().sum()print(total)

注意两点:

  • chunksize不是越小越好,太小会放大 Python 循环开销,20 万~50 万行比较合适;
  • groupby加observed=True,否则 category 类型会生成全组合的笛卡尔积行,白占内存。

5. 换引擎:pyarrow 比默认 C 引擎更快

pandas 2.x 起支持engine="pyarrow",多线程解析,在多核机器上提速明显:

df=pd.read_csv("big.csv",engine="pyarrow",dtype_backend="pyarrow")

没装就先装:

pipinstallpyarrow

dtype_backend="pyarrow"会用 Arrow 的原生类型(string、int32 等),内存占用通常再降一档。缺点是部分老 API 不兼容,导入后如果要和 scikit-learn 打交道,建议再.convert_dtypes()或直接.to_numpy()。


6. 终极方案:先把 CSV 转成 Parquet

如果你要反复读同一个文件,那就别每次都解析 CSV 了。一次转换,后续读取快 5~10 倍:

# 一次性转换df=pd.read_csv("big.csv",dtype={...})df.to_parquet("big.parquet",compression="snappy")# 后续读取(列式存储,只加载需要的列)df=pd.read_parquet("big.parquet",columns=["user_id","amount"])

Parquet 的三个好处:

  • 列式存储:columns=能真正跳过不用的列,不用解析整个文件;
  • 自带 schema:不用每次推断 dtype;
  • 体积小:snappy 压缩后通常只有原 CSV 的 20%~30%。

附:一个通用提速模板

把上面几条打包成一个函数,日常直接复用:

importpandasaspddefread_fast(path,usecols=None,dtypes=None,chunksize=None):kwargs=dict(usecols=usecols,dtype=dtypes,engine="pyarrow",)ifchunksize:returnpd.read_csv(path,chunksize=chunksize,**kwargs)returnpd.read_csv(path,**kwargs)# 用法df=read_fast("big.csv",usecols=["user_id","amount","city","date"],dtypes={"user_id":"int64","amount":"float32","city":"category"},)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 的,是那种内存死活放不下的单机极限场景——在那之前,先把默认参数调明白。

你处理过最大的 CSV 是多少 G?用的什么办法,评论区交流一下。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询