首页 / Pandas 入门教程 / IO 进阶技巧

Pandas 入门教程

IO 进阶技巧

本教程共 54 篇 · 第 16 篇 · 更新于 2026-08-11 · 约 9 分钟阅读

PandasPandas 入门教程编码chunksize压缩IO 性能

本节目标:学会处理乱码、按块读取大文件、提前指定列类型和压缩存储,让数据读写又快又稳。

编码问题:乱码的根源

CSV 是文本文件,文本就有编码。同一个文件用不同编码解读,结果完全不同。pandas 默认按 utf-8 读,碰到 GBK 等编码的中文文件,轻则出现乱码,重则直接报错:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd5 in position 0

解决办法是显式指定 encoding 参数:

import pandas as pd
import numpy as np

# 老中文系统导出的文件常见 GBK 编码
df = pd.read_csv("data.csv", encoding="gbk")

# 指定 utf-8
df2 = pd.read_csv("data.csv", encoding="utf-8")

常用编码速查:

  • utf-8:现代默认,跨平台
  • gbk / gb2312:老中文系统导出文件常见
  • utf-16:部分 Excel 导出文件
  • latin-1:西欧字符编码,容错性极强,什么字节都能读,可用来”抢救”文件

写出时同样可以指定编码。中文文件想用 Excel 打开不乱码,用 utf-8-sig(带 BOM 标记):

import pandas as pd
import numpy as np

df = pd.DataFrame({"姓名": ["张三", "李四"]})
df.to_csv("out.csv", encoding="utf-8-sig", index=False)
Tip

不确定文件编码时,用记事本打开文件”另存为”,看它默认选中的编码;或者用 Python 的 chardet 库自动检测。

dtype:提前告诉 pandas 列的类型

read_csv 默认自己推断每列类型,多数时候没问题,但两个场景建议手动指定:

  1. 身份证号、手机号这类”长得像数字但不是数字”的列,会被读成数值,精度直接丢失
  2. 大文件里字符串列被误判成数值,内存暴涨
import pandas as pd
import numpy as np

df = pd.read_csv("data.csv", dtype={
    "id": "string",     # 强制按文本读,防止精度丢失
    "age": "int64",
    "score": "float64",
})

举个例子:文件里存的是 “001”、“002” 这种编号,自动推断会读成整数 1、2,前面的零全没了。用 dtype 声明成 “string”,原样保留。

在 pandas 3.0 里,字符串列默认就是 str 类型(StringDtype),比旧的 object 类型更规范(如 §04 所述)。已经读进来发现类型不对,也可以事后用 astype 补救(如 §07 所述)。

想一步到位用 Arrow 后端读数据,可以传 dtype_backend="pyarrow",pandas 3.0 的新特性(如 §04 所述),后面讲性能优化时再细说。

read_csv 常用参数速查

前面几节零散讲过一些参数,这里汇总一张表,方便查阅:

参数作用
sep分隔符,默认逗号,制表符用 \t
header第几行当表头,默认 0
encoding文件编码,乱码时先查它
dtype指定每列类型
usecols只读部分列
nrows只读前 N 行
skiprows跳过前 N 行或指定行
na_values把指定值当成缺失值
chunksize分块读取,返回迭代器
compression压缩格式,默认 infer

两个容易忽略但很实用的:

import pandas as pd
import numpy as np

# skiprows:跳过文件开头两行说明文字
# na_values:把 "未知" 和 "N/A" 统一识别为缺失值
pd.read_csv("data.csv", skiprows=2, na_values=["未知", "N/A"])

chunksize:大文件分块读

文件太大、内存装不下时,可以分块读取。read_csv 加上 chunksize 后,返回的不是 DataFrame,而是一个可迭代对象(TextFileReader),每次循环拿到一块:

import pandas as pd
import numpy as np

total = 0
count = 0

# 每 10000 行一块,逐块处理
for chunk in pd.read_csv("big.csv", chunksize=10000):
    total += chunk["value"].sum()
    count += len(chunk)

print("总和:", total)
print("平均:", total / count)

每块处理完就释放内存,峰值内存只跟块大小有关。块越大越快但越吃内存,一般从 10000 到 100000 之间试,目标是每块控制在 50-200MB 左右。

逐块统计的思路都一样:先算每一块的局部结果,最后汇总。比如求最大值:

import pandas as pd
import numpy as np

max_value = None

for chunk in pd.read_csv("big.csv", chunksize=10000):
    chunk_max = chunk["value"].max()
    if max_value is None or chunk_max > max_value:
        max_value = chunk_max

print("最大值:", max_value)

也可以配合 iterator=True 手动取块:

import pandas as pd
import numpy as np

reader = pd.read_csv("big.csv", iterator=True)
chunk1 = reader.get_chunk(5000)  # 先拿 5000 行
chunk2 = reader.get_chunk(5000)  # 再拿 5000 行
Note

分块只适合”块与块互不影响”的操作,比如求和、计数、筛选。要做全表排序、去重这类跨块操作,分块就不好使了,需要换其他方案。

少读一点:usecols 和 nrows

文件列很多时,可以只读需要的列,大幅减少内存:

import pandas as pd
import numpy as np

df = pd.read_csv("wide.csv", usecols=["id", "value", "date"])

只想先看看数据长什么样,用 nrows 读前几行,不伤内存:

import pandas as pd
import numpy as np

preview = pd.read_csv("big.csv", nrows=1000)

这两个参数在调试阶段特别好用:先小规模读进来确认列名和格式,再放开全量读取。

compression:压缩文件直接读写

pandas 的读写函数大多支持 compression 参数,能直接读 .gz、.bz2、.zip、.xz、.zst 文件,也能压缩着写出去:

import pandas as pd
import numpy as np

# 读压缩文件,infer 表示按后缀自动判断
df = pd.read_csv("data.csv.gz", compression="infer")

# 压缩写入
df.to_csv("data.csv.gz", compression="gzip", index=False)
df.to_csv("data.csv.zst", compression="zstd", index=False)

默认就是 "infer",会按文件后缀猜压缩方式,所以平时不用特意写这个参数。想精细控制压缩级别,可以传字典:

import pandas as pd
import numpy as np

df = pd.DataFrame({"id": [1, 2, 3]})
# compresslevel 越小越快、文件越大;1 表示最快
df.to_csv("data.csv.gz", compression={"method": "gzip", "compresslevel": 1}, index=False)

性能对比:别让 IO 拖后腿

社区里有一组流传很广的基准测试,数据相同、只换存储格式。结果很有参考价值:

格式写入速度读取速度磁盘占用
Feather最快最快
HDF5 fixed
Parquet最小之一
Pickle中(压缩后小)
CSV最大
SQL很慢

结论很简单:追求速度用 Feather,追求体积和通用性用 Parquet,能不用 CSV 就不用 CSV。小文件无所谓,一旦上了百万行,格式的选择就是几分钟和几秒钟的差别。

补充一个 IO 之外的小技巧:读进来之后如果发现某些列类型不合适(比如 int 列其实是小数值),先优化 dtype 再分析,内存能省一大截(如 §53 所述)。

Warning

压缩率不是越高越好。gzip、xz 压缩率高但读写慢;snappy、lz4 压缩率低但速度快。追求性能选 snappy/lz4,追求空间选 gzip/zstd。

常见 IO 报错速查

  • FileNotFoundError:路径写错了,检查文件名和目录
  • UnicodeDecodeError:编码不对,按上面说的方法换 encoding
  • ParserError:文件内容跟参数不符,比如分隔符猜错了
  • ImportError:缺可选依赖,Parquet 要 pyarrow、HDF5 要 tables

报错不可怕,先看异常类型,再对照这个清单,多数问题几分钟能解决。IO 环节的技巧说到底是三件事:把编码搞对、把数据读全、把内存控住。

实在读不出来的文件,可以先用 errors="replace" 抢救一遍:遇到无法解码的字符就用占位符替换,先把数据弄进来,再决定怎么处理脏字符:

import pandas as pd
import numpy as np

df = pd.read_csv("data.csv", encoding="gbk", errors="replace")   # 乱码字符替换为 �

这只是应急手段,正经用法还是先把编码搞清楚。

调试小技巧:从字符串读数据

还没生成文件就想试 read_csv 的各种参数?可以用 io.StringIO 把字符串伪装成文件,不用建临时文件:

import pandas as pd
import numpy as np
import io

data = """id,value
1,100
2,200
3,300"""
df = pd.read_csv(io.StringIO(data))
print(df)

调试 dtype、usecols、na_values 这类参数时,这个技巧能省不少事。