Python 处理 CSV 文件:从 csv 模块到 pandas,选对工具少走弯路
CSV 文件几乎是每一个 Python 开发者最早遇到的数据格式之一。导出报表、导入用户数据、做实验记录、和别的系统交换数据,绕来绕去最后往往都落到一个 .csv 文件上。它看起来简单到不需要学习:逗号分隔、按行排列,用记事本都能打开。可真到了实际项目里,CSV 的坑一点也不少——字段里藏着逗号、引号没有闭合、某列混进了空字符串和 NA、几十万行数据一读就把内存撑爆。工具选对了,这些问题大半都能提前避开;选错了,就会把时间花在反复救火上。本文就从 Python 的两个主流选择说起:内置的 csv 模块和 pandas,再聊聊缺失值处理和选型建议。
一、先看标准库 csv 模块:轻、稳、零依赖
Python 内置的 csv 模块最大的优点是不用装任何东西,行为也完全可控。读一个小文件、按行处理、写回一个新文件,它是最顺手的选择。
import csv
with open('users.csv', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['age'])
有几个细节值得养成习惯。第一,打开文件时传 newline=‘’,把换行处理交给 csv 模块自己负责,否则在 Windows 上容易多出空行。第二,显式声明 encoding,例如 utf-8,遇到带 BOM 的文件就用 utf-8-sig。第三,写入时用 csv.DictWriter 并先写表头,字段顺序就不会乱。
csv 模块是逐行、流式的:它一次只在内存里放一行,文件再大也不会把内存吃光。对于读入、清洗、写出这类 ETL 小任务,它往往是最省心的写法。它的短板也很明显:没有表格的概念,取一列、筛几行、做聚合都要自己写循环,代码一长就容易变得啰嗦。
二、再看 pandas:表格思维,一步到位
当任务从搬运数据变成分析数据,pandas 的优势就出来了。它把 CSV 读成 DataFrame,列操作、筛选、分组、统计都是一行代码的事。
import pandas as pd
df = pd.read_csv('sales.csv')
print(df.head())
print(df.dtypes)
print(df.describe())
read_csv 提供了大量实用参数:sep 可以改成分号或制表符;usecols 只读需要的列,省内存;parse_dates 直接把日期列解析成时间类型;dtype 可以提前锁定列的类型,避免整数列因为混进一个空值就被推断成浮点数。写回文件时 to_csv 配合 index=False 可以避免多出一列无用的行号。
pandas 的代价是依赖重、启动慢、内存占用高。一个几十 MB 的 CSV 读进来,DataFrame 占用的内存经常是文件大小的好几倍。如果只是做简单的行级处理,用 pandas 就属于杀鸡用牛刀。
三、缺失值:CSV 里最常见的暗坑
CSV 没有真正的空值类型,缺失值在文件里表现为何种形式,完全取决于写文件的一方:可能是空字符串,可能是 NA、N/A、null、NULL,也可能是一个不起眼的空格。把这些形式统一识别出来,是做统计之前最重要的一步。
import pandas as pd
def count_missing(path, extra_na=None):
na_values = ['NA', 'N/A', 'null', 'NULL', 'None', '']
if extra_na:
na_values += extra_na
df = pd.read_csv(path, na_values=na_values, keep_default_na=True)
missing = df.isna().sum()
return missing
result = count_missing('data.csv')
print(result)
print('缺失比例:')
print((df.isna().mean() * 100).round(2))
只用 csv 模块同样可以统计,适合不想引入 pandas 的场景:
import csv
from collections import Counter
def count_missing_stdlib(path):
counts = Counter()
with open(path, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
for key, value in row.items():
if value is None or value.strip() == '':
counts[key] += 1
return counts
统计之后再决定怎么处理:缺失比例很小的列可以直接丢弃对应行;比例高的列要先问一句这一列还值不值得留;金额、数量这类字段则要谨慎,不能随便用 0 填充,因为没有记录和数值是 0 是两回事。真实业务里,很多错误结论都源于把缺失值当成了正常值。
四、文件一大,选型就变了
一两万行数据,csv 模块和 pandas 随便选,差异主要在写代码的手感上。数据量上到百万行级别,选型就必须考虑内存和速度。pandas 可以分块读取:
import pandas as pd
total = 0
for chunk in pd.read_csv('big.csv', chunksize=50000):
total += len(chunk)
print('总行数:', total)
而 csv 模块天生就是流式读取,配合生成器可以做到几乎不占内存。还有一种常见做法是先用 csv 模块做清洗和过滤,把数据量降下来,再把干净的小文件交给 pandas 做分析。两个工具不是非此即彼,组合使用往往最划算。
五、选型建议:按场景选,别按习惯选
第一,写一次性脚本、做简单导入导出,或者运行环境不方便安装第三方库,选 csv 模块,依赖少、部署稳。第二,做数据清洗、统计分析、画图建模,选 pandas,表格操作能省掉大量样板代码。第三,处理大文件,先看内存预算:内存充足可以用 pandas 分块读,内存紧张就用 csv 模块流式处理。第四,要进生产环境长期跑的任务,优先考虑可维护性:类型明确、异常可控、日志完善,比少写几行代码更重要。
另外还有两条通用经验。先用 head 命令或者只读前几行看一眼数据,别上来就全量读取;统一编码和缺失值口径,把它们写在脚本开头,而不是散落在各处。数据格式的坑大多不在代码里,而在默认假设里。
结语
csv 模块和 pandas 没有高下之分,只有合适与不合适的区别。小文件讲求轻快,大文件讲求稳妥,分析任务讲求表达力,生产任务讲求可维护性。理解了每种工具的边界,处理 CSV 就不再是反复踩坑的体力活:先花几分钟看数据、定口径、选工具,后面能省下几小时的调试时间。希望这篇文章能帮你在下一次打开 CSV 文件之前,先做出那个正确的选择。
- 点赞
- 收藏
- 关注作者
评论(0)