今天继续pandas的梳理,自从了解和学习python后,自然离不开大数据的处理。有次帮朋友处理一份 10GB 的用户行为数据,他用pd.read_csv直接读取,电脑卡了 20 分钟后弹出 “内存不足”。我接手后调整了三个参数,5 分钟就完成了读取 —— 这就是数据 I/O 的魔力:它不像算法那样需要复杂逻辑,却能直接决定数据处理的效率天花板。
数据 I/O(输入 / 输出)本质上是 “数据搬运”:从文件或数据库里把数据 “搬” 进程序,处理完再 “搬” 出去。就像搬家时,有人乱塞一气导致耗时耗力,有人分类装箱、选对工具,轻松搞定。接下来我们就从 “搬运技巧”“箱子选择”“仓库对接” 三个维度,聊聊怎么把数据搬得又快又稳。

一、文件读取优化
用pandas读文件时,最容易犯的错是 “全量读取”—— 不管需要多少数据,先把整个文件塞进内存。这就像搬家时不管用不用得上,连旧报纸都打包带走,纯属浪费体力。pd.read_csv的三个参数,其实就是给我们的 “高效搬运工具”。
1、usecols
usecols能指定读取的列,就像搬家前先列清单:“只带常用衣物和书籍,旧家具留给下一任租客”。
比如一份用户数据有 20 列,但我们只需要 “用户 ID”“消费金额”“消费时间” 三列,直接指定列名即可:
import pandas as pd
# 只读取需要的3列,跳过其他17列
df = pd.read_csv(
"user_data.csv",
usecols=["user_id", "amount", "pay_time"] # 列名列表或索引列表
)
我早年处理电商数据时,曾遇到过 100 列的日志文件,实际分析只需要 5 列。用usecols后,读取时间从 8 分钟降到 1 分钟,内存占用减少了 90%。这就像搬家用 “只带必需品” 的思路,省去了 90% 的搬运量。
2、dtype
数据类型(dtype)对内存的影响极大。比如 “用户 ID” 是纯数字,但默认会被识别为int64(8 字节),如果实际范围在 0-100 万,用int32(4 字节)就够了 —— 相当于把大箱子换成小箱子,同样的东西占更少空间。
举个例子:
# 自定义数据类型,压缩内存
dtype_dict = {
"user_id": "int32", # 代替默认的int64
"amount": "float32", # 代替默认的float64
"pay_time": "object" # 字符串类型保持不变
}
df = pd.read_csv("user_data.csv", usecols=["user_id", "amount", "pay_time"], dtype=dtype_dict)
这个技巧在处理身份证号时特别有用:身份证是 18 位数字,int类型存不下,默认会转成float(导致末尾数字失真),但用dtype={"id": "string"}指定为字符串类型,既准确又省内存。就像搬家时,把易碎品放进刚好能装下的盒子,既安全又不浪费空间。
3、chunksize
遇到 10GB 以上的超大文件,就算挑着搬、压缩着搬,也可能超出内存承载能力。这时候chunksize就像搬家公司的 “分批运输”:一次拉不完,就分 10 趟拉,每趟只拉 1/10。
用法很简单:
# 每次读取100万行,返回迭代器
chunk_iter = pd.read_csv("big_data.csv", chunksize=1000000)
# 逐批处理
for i, chunk in enumerate(chunk_iter):
# 处理当前批次(比如过滤、计算)
filtered_chunk = chunk[chunk["amount"] > 100] # 只保留消费超100的数据
# 保存结果(避免占内存)
filtered_chunk.to_csv("filtered_data.csv", mode="a", header=(i==0))
我之前处理一份 30GB 的物流数据时,用chunksize分 30 批读取,每批处理完就保存结果并释放内存,原本需要 32GB 内存的任务,在 8GB 内存的电脑上就完成了。这就像用小货车分批次搬家,虽然多跑几趟,但总比租大货车(升级硬件)划算。
二、格式对比
同样的数据,用不同格式存储,就像把东西放进纸箱还是收纳箱 —— 纸箱(CSV)便宜好打开,但装不多、搬不快;收纳箱(Parquet/Feather)贵一点(需要额外库),但能叠放(压缩)、能快速找到东西(列存储)。
我们用一份 100 万行、20 列的用户数据做测试(包含数字、字符串、日期),看看三种格式的差异:
|
操作 |
CSV |
Parquet |
Feather |
|
存储大小 |
100MB |
15MB |
20MB |
|
读取时间 |
8.2 秒 |
0.9 秒 |
0.7 秒 |
|
写入时间 |
5.6 秒 |
1.8 秒 |
1.2 秒 |
|
随机读取某列 |
需读全文件 |
直接读目标列 |
直接读目标列 |
1、 CSV
CSV 是最常见的格式,就像搬家时的纸箱:随处可见(所有工具都支持)、打开方便(用记事本就能看),但缺点明显 —— 体积大(无压缩)、读取时要从头读到尾(行存储)。
适合场景:
- 小文件(100MB 以内)临时传输
- 需要人工查看或编辑数据
- 与不支持其他格式的工具交互
我平时处理日报数据(几千行)时常用 CSV,打开快、分享方便。但超过 1GB 的数据再用 CSV,就像用纸箱搬冰箱 —— 不是不行,就是费劲。
2、Parquet
Parquet 是为大数据设计的格式,它的 “列存储” 就像把衣服按 “上衣、裤子、袜子” 分类装箱,要找袜子时不用翻整个衣柜。加上内置压缩,体积能比 CSV 小 6-10 倍。
适合场景:
- 大数据长期存储(1GB 以上)
- 需要频繁按列查询(比如 “查所有用户的消费金额”)
- 跨平台共享(支持 Python、Java、Spark 等)
之前做用户画像项目时,把 300GB 行为数据转成 Parquet 后,存储成本降了 70%,分析师查数据的时间从小时级降到分钟级。就像把零散的杂物整理成贴了标签的收纳箱,既省空间,找东西又快。
3、Feather
Feather 是 “速度优先” 的格式,读取速度比 Parquet 还快一点,但压缩率稍差。它就像搬家时的手提箱,专门用来装 “马上要用的东西”,不追求极致压缩,但拿取极快。
适合场景:
- Python 和 R 之间传递数据(无缝兼容)
- 程序内临时保存中间结果
- 需要高频读写的中小型数据(100MB-1GB)
我在做模型训练时,常用 Feather 保存预处理后的特征数据,下次启动程序时 1 秒就能加载完,比 CSV 快 10 倍。就像把第二天要用的文件放进手提箱,不用从大箱子里翻。
三、数据库交互
数据处理完总要存到数据库(比如 MySQL、PostgreSQL),这就像把东西搬进仓库 —— 直接往仓库门口堆(全量写入)会堵门,分批次送(批处理)才高效。pandas的to_sql方法加上chunksize,就是对接仓库的 “卸货神器”。
1、坑
直接用df.to_sql("user_table", engine)写入 100 万行数据,很可能遇到:
- 数据库超时(等待太久)
- 内存溢出(程序要一直抱着数据等写入)
- 写入失败后要从头再来(没断点续传)
就像一次性往仓库里塞 1000 个箱子,仓库门被堵住,一旦中间掉了一个,还得全部重新搬。
2、chunksize分批写入
加个chunksize参数,分批次写入:
from sqlalchemy import create_engine
# 连接数据库
engine = create_engine("mysql+pymysql://user:password@localhost/dbname")
# 每批写1万行
df.to_sql(
name="user_table", # 表名
con=engine,
if_exists="append", # 追加模式
chunksize=10000, # 每批1万行
index=False # 不写入索引(节省空间)
)
这个方法有三个好处:
- 内存压力小:写完一批就释放一批数据
- 容错性高:某批失败,只需重写这一批
- 适配数据库:避免单次请求过大被拒绝
我之前往 MySQL 导 500 万行订单数据时,用chunksize=5000分 1000 批写入,全程没超时,中间网络断了一次,重连后从第 300 批继续就行。就像搬家时每次只搬 5 个箱子进仓库,就算掉了一个,也不用全部重来。
3、搭配dtype指定数据库字段类型
数据库字段类型和 Python 类型常有差异(比如 Python 的int对应 MySQL 的INT或BIGINT),用dtype指定可以避免自动转换出错:
from sqlalchemy.types import Integer, String, Float
# 指定数据库字段类型
db_dtype = {
"user_id": Integer(), # 对应MySQL的INT
"name": String(50), # 对应VARCHAR(50),限制长度
"amount": Float(precision=2) # 保留2位小数
}
df.to_sql(
"user_table",
engine,
dtype=db_dtype, # 指定字段类型
chunksize=10000
)
这就像告诉仓库管理员:“这个箱子装的是易碎品(对应 DECIMAL),那个箱子装的是文件(对应 VARCHAR)”,避免放错位置。
最后小结:
前些年用python搞大数据项目时,我发现要想让数据读写效率高,根本不用死记硬背那些参数,关键是养成 “先琢磨再动手” 的习惯:
读数据之前,先瞅瞅自己到底需要哪些数据列(这时候就用 usecols);存数据的时候,先琢磨琢磨用啥格式(小文件用 CSV 就行,大文件选 Parquet 更合适);往数据库里写数据前,算算分多少批写(chunksize 得和自己的电脑硬件匹配)。
这就跟搬家前先规划一样:只带平时用得上的东西、找合适的箱子装、东西多就分几趟搬。数据的读写操作没什么特别高深的技术,但做好了和做不好,效率能差一大截 —— 毕竟有捷径能走,谁也不想费那冤枉劲硬扛着。
数据处理最终都是归结到I/O,想想把所有数据都一股脑塞进电脑内存了?是不是明明能用更合适的格式存数据,结果却用了普通的格式?想明白这些,效率自然而然就提上来了。未完待续........

7320

被折叠的 条评论
为什么被折叠?



