4、CSV文件读写:csv模块基础、pandas读取CSV、数据清洗与预处理、批量合并CSV

好,咱们进入第四章。这一章讲的是CSV文件读写,说白了就是怎么跟标定数据打交道。CSV这玩意儿,在咱们标定工程师手里,就跟厨师手里的菜刀一样,天天都得用。

我个人习惯,不管项目大小,先把数据读进来再说。读不进来,后面全是白搭。这一章我会把csv模块和pandas两种方式都讲透,再聊聊数据清洗和批量合并。嗯,都是实战里踩过的坑。

4.1 csv模块基础:轻量级选手

Python自带的csv模块,轻量、简单。如果你只是读个小文件,或者不想装pandas,用它就够了。我在早期做标定的时候,经常用它来快速查看数据。

先看个读文件的例子:

import csv

with open('calibration_data.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

这段代码会把每一行数据读成一个列表。你想想看,如果数据量不大,这样处理挺快的。但有个问题——它不会自动识别表头。你得自己处理第一行。

我建议用 csv.DictReader,它会把第一行当键,后面每行当值,返回字典。这样代码可读性高很多:

import csv

with open('calibration_data.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row['voltage'], row['current'])
小技巧: 写CSV时,用 csv.writer 配合 writerows() 一次性写入多行,比逐行写入快不少。我在处理几百兆的标定日志时试过,效率差距很明显。

4.2 pandas读取CSV:重型武器上场

数据量一大,csv模块就有点吃力了。这时候就该pandas上场。pandas读取CSV,说白了就是一行代码的事:

import pandas as pd

df = pd.read_csv('calibration_data.csv')
print(df.head())

就这么简单。但实际项目中,CSV文件往往没那么规矩。我遇到过各种奇葩情况:编码不对、分隔符不是逗号、表头有乱码……

这里列几个常用参数,你记一下:

参数 作用 我常用的值
sep 指定分隔符 ',''\t'
encoding 指定编码 'utf-8''gbk'
header 指定表头行 0None
skiprows 跳过前N行 常用于跳过注释行
dtype 指定列数据类型 {'voltage': float}

举个例子,如果你遇到一个用制表符分隔、编码是GBK的文件,可以这样读:

df = pd.read_csv('data.tsv', sep='\t', encoding='gbk')
注意: 我曾经因为没指定 dtype,导致一列数字被读成了字符串。后面做计算时全报错,排查了半天才发现。所以,建议对关键列显式指定数据类型。

4.3 数据清洗与预处理:脏活累活

数据读进来了,但往往不干净。标定数据里常见的问题有:缺失值、重复行、异常值、格式不一致。说白了,就是数据需要「洗一洗」才能用。

缺失值处理

pandas里缺失值用 NaN 表示。你可以用 isna() 检查,用 dropna() 删除,或用 fillna() 填充。

# 检查缺失值
print(df.isna().sum())

# 删除包含缺失值的行
df_clean = df.dropna()

# 用均值填充
df['voltage'].fillna(df['voltage'].mean(), inplace=True)

我个人习惯,先看缺失值比例。如果比例小(比如5%以内),直接删除。如果比例大,就得考虑填充了。填充方式取决于业务场景——标定数据里,我常用前向填充(method='ffill'),因为数据往往是连续的。

重复行处理

重复数据会干扰分析结果。用 duplicated() 检查,用 drop_duplicates() 删除:

# 检查重复行
print(df.duplicated().sum())

# 删除重复行
df_unique = df.drop_duplicates()

异常值处理

标定数据里,异常值往往来自传感器故障或通信错误。我一般用3σ原则或IQR(四分位距)来识别:

# 3σ原则
mean = df['value'].mean()
std = df['value'].std()
df_filtered = df[(df['value'] >= mean - 3*std) & (df['value'] <= mean + 3*std)]
避坑指南: 我曾经在处理一批温度标定数据时,直接用3σ删除了「异常值」,结果发现那些「异常值」其实是高温工况下的真实数据。所以,清洗前一定要先理解业务含义,别一刀切。

4.4 批量合并CSV:效率翻倍

标定项目里,数据往往分散在多个CSV文件中。比如一天一个文件,或者一个工况一个文件。手动合并?别傻了,写个脚本几秒钟搞定。

批量合并的核心思路:先找到所有CSV文件,然后逐个读取,最后拼接成一个DataFrame。

import pandas as pd
import glob

# 找到所有CSV文件
files = glob.glob('data/*.csv')

# 逐个读取并合并
df_list = []
for file in files:
    df = pd.read_csv(file)
    df_list.append(df)

# 纵向拼接
df_all = pd.concat(df_list, ignore_index=True)

这里有个细节:ignore_index=True 会重置索引,避免索引重复。如果你不重置,后面操作时可能会出问题。

如果文件结构不一样(比如列名不同),concat 默认会按列名对齐,缺失的列填充为 NaN。这有时候是好事,有时候不是。我建议合并前先检查一下各文件的列名是否一致:

# 检查列名
for file in files:
    df = pd.read_csv(file)
    print(f'{file}: {list(df.columns)}')
小技巧: 如果文件数量特别多(比如上千个),用 glob 加列表推导式会更简洁:df_all = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)。一行搞定,代码看着也清爽。

嗯,这一章的内容就这些。CSV读写是标定数据处理的基石,后面所有高级操作都建立在这之上。下一章我们会聊数据可视化,到时候就能把清洗好的数据画成图,一眼看出问题所在。