老码农和你一起学AI:Python系列-Pandas的 I/O 高效操作

今天继续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,想想把所有数据都一股脑塞进电脑内存了?是不是明明能用更合适的格式存数据,结果却用了普通的格式?想明白这些,效率自然而然就提上来了。未完待续........

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值