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} 防止批次号被读成浮点数
我的小技巧: 遇到不确定的 CSV,先用 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 编码读,结果就是一堆乱码。

我建议的编码优先级:

  1. 先试 utf-8-sig——兼容带 BOM 和不带 BOM 的 UTF-8,Excel 导出的文件尤其需要这个。
  2. 再试 gbk——国内老系统、Windows 中文环境导出文件。
  3. 最后试 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('未知')
核心原则: 缺失值处理没有银弹。配方数据里,关键标识字段缺失必须删除,数值字段可以填充,但填充逻辑一定要和业务方确认。我曾经因为自作主张用 0 填充缺失的配比,导致一批配方全部作废——教训深刻。

3.4 知识体系:pd.read_csv() 核心流程

下面这张图,是我自己总结的 read_csv 标准流程。每次写导入脚本,我都按这个顺序检查一遍。

pd.read_csv() 标准处理流程 1. 文件路径 2. 指定分隔符 3. 设置编码 4. 确认表头 + 指定数据类型 5. 处理缺失值与脏数据 6. 输出 DataFrame 避坑提醒 先预览前5行 确认分隔符和编码 再正式全量读取 缺失值处理原则 关键字段缺失 → 删除 数值字段缺失 → 填充 填充逻辑需业务确认

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() 就是个「你给它什么参数,它给你什么数据」的工具。参数给对了,数据干干净净;参数给错了,后面全是坑。