引言
一个 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.0
for chunk in pd.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")
没装就先装:
pip install pyarrow
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%。
附:一个通用提速模板
把上面几条打包成一个函数,日常直接复用:
import pandas as pd
def read_fast(path, usecols=none, dtypes=none, chunksize=none):
kwargs = dict(
usecols=usecols,
dtype=dtypes,
engine="pyarrow",
)
if chunksize:
return pd.read_csv(path, chunksize=chunksize, **kwargs)
return pd.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?用的什么办法,评论区交流一下。
到此这篇关于pandas读取大csv太慢的6个实测提速技巧的文章就介绍到这了,更多相关pandas读取大csv太慢提速技巧内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论