3、Pandas读取CSV:pd.read_csv()核心参数详解、指定分隔符与编码、处理缺失值与脏数据
说实话,做配方数据导入这么多年,我见过最惨的翻车现场是什么?
不是数据量太大,也不是服务器崩了。而是——CSV文件读进来全是乱码,或者干脆报错说找不到列。会议室里十几个人等着看演示,你这边终端飘红,那场面,啧。
所以这一章,咱们就把 pd.read_csv() 这个看家本领彻底吃透。你想想看,配方数据里动不动就几十列、上千行,要是连读文件这关都过不好,后面清洗、合并、入库全是白搭。
3.1 核心参数:别只传个文件路径就完事
很多新手用 pd.read_csv() 就只给一个参数——文件路径。然后数据读进来了,一看,列名全乱了,或者多了一行莫名其妙的表头。
我个人习惯,每次写 read_csv 至少带 3 个参数:文件路径、分隔符、编码。这是底线。
| 参数 | 默认值 | 我的推荐用法 | 说明 |
|---|---|---|---|
filepath_or_buffer |
必填 | 原始字符串前加 r |
避免 Windows 路径转义问题 |
sep |
, |
sep='\t' 或 sep=';' |
配方数据常用制表符或分号 |
encoding |
无 | encoding='utf-8-sig' |
兼容带 BOM 的 Excel 导出文件 |
header |
0 |
header=0 或 header=None |
确认第一行是不是真正的列名 |
dtype |
自动推断 | dtype={'批次号': str} |
防止批次号被读成浮点数 |
pd.read_csv(file, nrows=5) 预览前 5 行。确认列名、分隔符、编码都对,再正式全量读取。这招帮我省了不知道多少重跑时间。
3.2 指定分隔符与编码:配方数据的两个大坑
配方数据有个特点——来源特别杂。有的从 MES 系统导出,默认用制表符;有的从实验室仪器导出,用分号;还有的从老系统导出,编码是 GBK。
我曾经接手过一个项目,客户给了 200 多个 CSV 文件,里面混了 3 种分隔符、4 种编码。手动改?不现实。所以必须靠参数搞定。
3.2.1 分隔符:不只是逗号
最常见的几种分隔符场景:
- 逗号
,:标准 CSV,但配方数据里如果配方名称本身带逗号,就会裂开。 - 制表符
\t:MES 系统导出最爱用,用sep='\t'或sep='\s+'(多个空白) - 分号
;:欧洲实验室仪器常见,因为他们的逗号是小数点。 - 竖线
|:一些老系统为了避开所有符号冲突,直接用竖线。
# 制表符分隔的配方文件
df = pd.read_csv('配方数据_2024.tsv', sep='\t', encoding='utf-8-sig')
# 分号分隔,且第一行不是列名
df = pd.read_csv('lab_export.csv', sep=';', header=None, encoding='gbk')
# 自动检测分隔符(不推荐,但应急可用)
df = pd.read_csv('unknown.csv', sep=None, engine='python')
sep=None 会让 Pandas 自动检测分隔符,但速度慢,而且遇到混合分隔符会翻车。我一般只在调试阶段用,正式脚本里一定写死。
3.2.2 编码:乱码的根源
编码问题,说白了就是文件用 A 编码存,你用 B 编码读,结果就是一堆乱码。
我建议的编码优先级:
- 先试
utf-8-sig——兼容带 BOM 和不带 BOM 的 UTF-8,Excel 导出的文件尤其需要这个。 - 再试
gbk——国内老系统、Windows 中文环境导出文件。 - 最后试
latin1——欧洲仪器数据,或者实在不知道编码时的兜底方案。
# 我的标准编码探测流程
encodings_to_try = ['utf-8-sig', 'gbk', 'latin1']
for enc in encodings_to_try:
try:
df = pd.read_csv('配方.csv', encoding=enc, nrows=5)
print(f'编码 {enc} 可用')
break
except UnicodeDecodeError:
continue
3.3 处理缺失值与脏数据:别让一个空单元格毁了整批导入
配方数据里,缺失值太常见了。比如某个原料的配比没填、批次号漏了、或者温度值写了个「N/A」。
默认情况下,Pandas 会把空单元格、NA、null、NaN 都识别为缺失值。但配方数据里经常有自定义的缺失标记,比如 -、.、无、9999。
3.3.1 自定义缺失值标记
# 把常见的脏数据标记统一转为 NaN
df = pd.read_csv('配方.csv',
na_values=['-', '.', '无', 'N/A', 'NULL', '9999', ''])
嗯,这里要注意:na_values 参数接受一个列表,所有匹配的值都会被转为 NaN。这样后面做填充或删除就统一了。
3.3.2 处理缺失值的三种策略
| 策略 | 方法 | 适用场景 |
|---|---|---|
| 删除 | df.dropna() |
关键字段缺失(如配方编号、原料代码) |
| 填充 | df.fillna(value) |
数值型字段(如用均值、中位数、0 填充) |
| 向前/向后填充 | df.fillna(method='ffill') |
时间序列数据,用上一个有效值填充 |
# 实际项目中的处理流程
# 1. 先看看缺失情况
print(df.isnull().sum())
# 2. 关键字段缺失的直接删
df = df.dropna(subset=['配方编号', '原料代码'])
# 3. 数值型字段用中位数填充(避免受异常值影响)
df['配比'] = df['配比'].fillna(df['配比'].median())
# 4. 文本型字段用 '未知' 填充
df['备注'] = df['备注'].fillna('未知')
3.4 知识体系:pd.read_csv() 核心流程
下面这张图,是我自己总结的 read_csv 标准流程。每次写导入脚本,我都按这个顺序检查一遍。
3.5 实战:一个完整的配方导入脚本
最后,给你看看我实际项目里用的配方导入函数。不算复杂,但每个参数都是踩过坑之后定下来的。
import pandas as pd
def load_recipe_csv(filepath):
"""
标准配方 CSV 导入函数
参数:
filepath: CSV 文件路径
返回:
DataFrame,已处理缺失值
"""
# 1. 先预览,确认结构
preview = pd.read_csv(filepath, nrows=3, encoding='utf-8-sig')
print('预览列名:', preview.columns.tolist())
# 2. 正式读取,指定参数
df = pd.read_csv(
filepath,
sep=',', # 根据预览结果调整
encoding='utf-8-sig', # 优先 UTF-8
dtype={
'批次号': str, # 防止批次号变浮点
'配方编号': str,
'原料代码': str
},
na_values=['-', '.', '无', 'N/A', 'NULL', ''], # 统一缺失标记
keep_default_na=True
)
# 3. 处理缺失值
# 关键字段缺失 → 删除
df = df.dropna(subset=['配方编号', '原料代码'])
# 数值字段 → 中位数填充
numeric_cols = df.select_dtypes(include='number').columns
for col in numeric_cols:
df[col] = df[col].fillna(df[col].median())
# 文本字段 → 填充 '未知'
text_cols = df.select_dtypes(include='object').columns
for col in text_cols:
df[col] = df[col].fillna('未知')
print(f'导入完成:{len(df)} 条有效配方数据')
return df
# 使用示例
recipes = load_recipe_csv('2024年配方数据.csv')
utils/data_loader.py 里,每个导入脚本都调它。参数统一,出了问题也好排查。你想想看,要是每个脚本都自己写一遍 read_csv,参数五花八门,后期维护起来得多痛苦。
好了,这一章的核心就这些。说白了,pd.read_csv() 就是个「你给它什么参数,它给你什么数据」的工具。参数给对了,数据干干净净;参数给错了,后面全是坑。