第4章:数据清洗基础
数据清洗,说白了就是给原始数据「洗澡」。我做了这么多年储能系统,见过太多「脏数据」把模型带沟里的案例。你想想看,如果原始数据本身就有问题,再牛的算法也白搭。这一章,咱们就聊聊数据清洗的几个核心操作。
4.1 缺失值处理
储能系统的数据采集,经常会出现传感器掉线、通信中断的情况。结果就是——数据表里出现一堆空值。我个人习惯,拿到数据第一件事就是检查缺失情况。
4.1.1 删除缺失值
最简单粗暴的方法,就是直接删。但要注意,不是所有情况都适合删。
import pandas as pd
import numpy as np
# 加载储能数据
df = pd.read_csv('battery_data.csv')
# 查看缺失情况
print(df.isnull().sum())
# 删除含有缺失值的行
df_dropped = df.dropna()
# 删除指定列有缺失的行
df_dropped_col = df.dropna(subset=['voltage', 'current'])
⚠️ 注意:我曾经在一个项目中,直接删除了30%的缺失行,结果模型训练出来完全不能用。为什么?因为缺失不是随机的,而是集中在高温时段——传感器过热导致。删除后,高温工况的数据全没了。
什么时候适合删除?
- 缺失比例很小(比如<5%)
- 缺失是随机发生的
- 数据量足够大,删了不影响分析
4.1.2 填充缺失值
大多数情况下,我更推荐填充。毕竟储能数据很宝贵,删了可惜。
# 用均值填充
df['voltage_filled'] = df['voltage'].fillna(df['voltage'].mean())
# 用中位数填充(对异常值更鲁棒)
df['current_filled'] = df['current'].fillna(df['current'].median())
# 前向填充(适合时间序列)
df['soc_filled'] = df['soc'].fillna(method='ffill')
# 插值法(我个人最常用)
df['temperature_filled'] = df['temperature'].interpolate(method='linear')
💡 小技巧:对于储能电池的SOC数据,我建议用前向填充。因为SOC是缓慢变化的,前一时刻的值往往比均值更靠谱。我在某储能电站的项目中,用前向填充后,模型精度提升了8%。
4.2 异常值检测
异常值,就是那些「看着就不对」的数据。比如电池电压突然跳到1000V,或者SOC一夜之间从50%变成100%。这些数据如果不处理,会严重干扰分析结果。
4.2.1 3σ原则
这个方法基于正态分布。简单说,如果数据偏离均值超过3个标准差,就认为是异常。
def detect_outliers_3sigma(data, column):
mean = data[column].mean()
std = data[column].std()
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
outliers = data[(data[column] < lower_bound) |
(data[column] > upper_bound)]
return outliers
# 检测电压异常
voltage_outliers = detect_outliers_3sigma(df, 'voltage')
print(f'发现 {len(voltage_outliers)} 个异常值')
📌 核心要点:3σ原则假设数据服从正态分布。但储能数据往往不是严格正态的——比如充放电电流的分布就是双峰的。所以,这个方法更适合做初步筛查。
4.2.2 IQR方法
IQR(四分位距)方法不依赖正态分布假设,我个人更常用。它基于数据的四分位数来判定异常。
def detect_outliers_iqr(data, column):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = data[(data[column] < lower_bound) |
(data[column] > upper_bound)]
return outliers
# 检测温度异常
temp_outliers = detect_outliers_iqr(df, 'temperature')
print(f'IQR方法发现 {len(temp_outliers)} 个异常值')
💡 实战经验:我在处理某锂电池储能数据时,3σ方法把很多正常的充放电峰值误判为异常。换成IQR方法后,误判率降低了60%。所以,对于储能数据,我建议优先用IQR。
4.3 重复数据处理
重复数据在储能系统中很常见。比如数据采集程序bug,同一时刻记录了两次。或者数据合并时,不同来源的数据有重叠。
# 检查重复行
print(f'重复行数: {df.duplicated().sum()}')
# 查看重复的具体行
duplicates = df[df.duplicated(keep=False)]
print(duplicates.head())
# 删除重复行(保留第一个)
df_unique = df.drop_duplicates()
# 基于特定列去重
df_unique_col = df.drop_duplicates(subset=['timestamp', 'battery_id'])
⚠️ 注意:我曾经遇到过一个坑——数据看起来重复,但其实是不同传感器在同一时刻的独立测量。如果盲目去重,会丢失有效信息。所以,去重前一定要确认「重复」的定义。
4.4 数据类型转换
数据类型不对,很多操作会报错。比如时间戳存成了字符串,没法做时间序列分析。或者数值列存成了对象类型,没法做数学运算。
# 查看数据类型
print(df.dtypes)
# 转换时间戳
df['timestamp'] = pd.to_datetime(df['timestamp'])
# 转换数值类型
df['voltage'] = pd.to_numeric(df['voltage'], errors='coerce')
df['current'] = df['current'].astype(float)
# 转换分类变量
df['status'] = df['status'].astype('category')
# 自定义转换函数
def convert_soc(value):
if isinstance(value, str):
return float(value.replace('%', '')) / 100
return value
df['soc'] = df['soc'].apply(convert_soc)
💡 小技巧:我习惯在数据加载后,第一时间用dtypes检查类型。很多问题其实在第一步就能发现。比如某次项目中,电流列被读成了object类型,原因是数据里混入了几个字母「N/A」。用errors='coerce'参数,把这些非法值转为NaN,问题就解决了。
知识体系总览
下面这张图,概括了本章的核心内容。你可以把它当作数据清洗的「检查清单」。
数据清洗没有银弹。不同的数据、不同的场景,处理方法都不一样。我的建议是:先理解数据,再动手清洗。多问自己几个「为什么」——为什么缺失?为什么异常?为什么重复?搞清楚原因,处理起来才有方向。
公众号:蓝海资料掘金营,微信deep3321