第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,问题就解决了。

知识体系总览

下面这张图,概括了本章的核心内容。你可以把它当作数据清洗的「检查清单」。

数据清洗知识体系 数据清洗 缺失值处理 删除缺失行/列 均值/中位数填充 前向填充/插值 异常值检测 3σ原则(正态分布) IQR方法(推荐) 重复数据处理 检查重复行 基于列去重 数据类型转换 时间戳转换 数值/分类转换

数据清洗没有银弹。不同的数据、不同的场景,处理方法都不一样。我的建议是:先理解数据,再动手清洗。多问自己几个「为什么」——为什么缺失?为什么异常?为什么重复?搞清楚原因,处理起来才有方向。


公众号:蓝海资料掘金营,微信deep3321