数据清洗基础方法:缺失值处理、异常值检测与去重策略

各位同行,今天我们来聊聊数据清洗。说实话,我在储能行业摸爬滚打这些年,最头疼的往往不是算法选型,而是数据本身的质量问题。你想想看,一堆脏数据喂进去,再牛的模型也是白搭。今天我就把数据清洗里最核心的三个环节——缺失值、异常值、重复数据——掰开揉碎了讲清楚。

一、缺失值处理:别急着删,先看看情况

缺失值在储能数据里太常见了。传感器掉线、通信中断、存储异常,都会导致数据出现空洞。我个人习惯是,拿到数据先做一次缺失率统计。如果某个字段缺失超过80%,我基本会直接放弃这个字段——留着也是添乱。

1. 删除法:简单粗暴,但有代价

删除缺失值是最直接的办法。但要注意,不是所有情况都适合删。

  • 行删除:如果缺失比例很小(比如<5%),直接删掉这几行数据,不影响大局。
  • 列删除:某个特征缺失太多,或者这个特征本身不重要,直接删列。
⚠️ 我曾经踩过坑:有一次做电池SOC估算,删掉了缺失温度数据的行,结果发现这些行恰好都是高温工况下的数据。删完之后模型在高温场景下完全失效。所以删除前一定要看看缺失数据的分布是否有偏。

2. 填充法:用合理值补上空洞

填充比删除更温和。常用的填充方式有几种:

填充方式 适用场景 我常用的场景
均值/中位数填充 数值型、分布较对称 电压、电流的短时缺失
众数填充 类别型数据 电池型号、运行模式
前向/后向填充 时间序列数据 连续采样的温度数据
固定值填充 有业务含义的默认值 状态标志位缺失填0

举个例子,储能电站的电压数据如果缺失了1-2个采样点,用前后值的均值填充,基本不影响后续分析。但如果是长时间缺失,填充就不靠谱了。

3. 插值法:更精细的补全手段

插值比简单填充更聪明。它会利用数据的变化趋势来估算缺失值。

# 线性插值示例
import pandas as pd
import numpy as np

# 模拟储能电压数据,中间有缺失
data = pd.Series([3.2, 3.25, np.nan, 3.35, 3.38, np.nan, 3.45])

# 线性插值
filled_data = data.interpolate(method='linear')
print(filled_data)
# 输出: 3.20, 3.25, 3.30, 3.35, 3.38, 3.415, 3.45

我个人在电池容量衰减分析中,经常用样条插值。因为电池的衰减曲线不是线性的,样条插值能更好地拟合真实趋势。不过要注意,插值不是万能的——如果缺失段太长,插出来的值基本就是瞎猜了。

💡 我的建议:缺失值处理没有银弹。我一般会先画缺失值分布图,看看缺失是随机的还是有规律的。如果是随机缺失,填充或删除都行;如果是非随机缺失(比如高温时传感器容易掉线),那就得小心了,最好用更复杂的模型来估算。

二、异常值检测:揪出那些「不听话」的数据点

异常值在储能数据里特别常见。电池突然跳变、传感器瞬间漂移、通信误码,都会产生异常值。如果不处理,这些异常点会严重干扰模型训练。

1. 3σ原则:最经典的方法

3σ原则基于正态分布假设。数据落在均值±3倍标准差之外的概率不到0.3%,这些点就可以视为异常。

# 3σ异常检测
def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower_bound = mean - 3 * std
    upper_bound = mean + 3 * std
    outliers = data[(data < lower_bound) | (data > upper_bound)]
    return outliers

# 以储能电池电压为例
voltages = [3.2, 3.21, 3.19, 3.22, 3.18, 4.5, 3.2, 3.21]  # 4.5是异常
outliers = detect_outliers_3sigma(np.array(voltages))
print(f"检测到异常值: {outliers.values}")

但说实话,3σ方法有个硬伤——它对异常值本身很敏感。如果数据里已经有几个很大的异常值,均值和标准差都会被拉偏,导致检测不准。我在项目中遇到过这种情况,后来改用中位数和MAD(中位数绝对偏差)来替代,鲁棒性好很多。

2. IQR方法:更稳健的选择

IQR(四分位距)方法不受极端值影响,我个人更常用。

它的逻辑很简单:计算数据的Q1(25%分位数)和Q3(75%分位数),IQR = Q3 - Q1。然后定义正常范围:

  • 下界:Q1 - 1.5 * IQR
  • 上界:Q3 + 1.5 * IQR

超出这个范围的就是异常值。

# IQR异常检测
def detect_outliers_iqr(data):
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    outliers = data[(data < lower_bound) | (data > upper_bound)]
    return outliers

# 同样的电压数据
outliers_iqr = detect_outliers_iqr(np.array(voltages))
print(f"IQR检测到异常值: {outliers_iqr.values}")

为什么IQR更稳健?因为它用的是分位数,不受极端值影响。你想想看,就算数据里有个1000的异常值,Q1和Q3基本不变,检测结果依然可靠。

3. 孤立森林:高维数据的利器

当数据维度高了(比如同时看电压、电流、温度、SOC),3σ和IQR就不好使了。这时候我推荐孤立森林。

孤立森林的原理很有意思:异常点通常「孤立」得很快——你随机切几刀,异常点很快就被单独分出来了。正常点则需要很多次切割才能分离。

from sklearn.ensemble import IsolationForest

# 假设我们有电压、电流、温度三个维度的数据
X = np.array([
    [3.2, 10, 25],
    [3.21, 10.1, 25.2],
    [3.19, 9.8, 24.8],
    [4.5, 10, 25],    # 电压异常
    [3.2, 10, 80],    # 温度异常
    [3.21, 10.1, 25.1]
])

model = IsolationForest(contamination=0.1, random_state=42)
predictions = model.fit_predict(X)
# -1表示异常,1表示正常
print(f"异常点索引: {np.where(predictions == -1)[0]}")
🔑 关键点:孤立森林不需要假设数据分布,对高维数据效果好。但要注意contamination参数——它表示你预期数据中异常值的比例。设得太高会误杀正常点,设得太低会漏掉异常。我一般先设0.05,然后根据业务反馈调整。

三、重复数据去重:别让同一份数据算两遍

重复数据在储能系统里也很常见。比如数据采集程序重启后重复上报、通信重传机制导致的数据冗余。重复数据会让统计结果偏大,模型训练时也会给某些样本过高的权重。

1. 完全重复去重

最简单的情况:两行数据一模一样。直接用pandas的drop_duplicates()就行。

import pandas as pd

df = pd.DataFrame({
    'timestamp': ['2024-01-01 00:00', '2024-01-01 00:00', '2024-01-01 00:01'],
    'voltage': [3.2, 3.2, 3.21],
    'current': [10, 10, 10.1]
})

# 去除完全重复的行
df_clean = df.drop_duplicates()
print(df_clean)

2. 部分重复去重

更常见的情况是:时间戳相同,但其他字段有微小差异。比如同一时刻上报了两条电压数据,一个3.2V,一个3.21V。这时候需要业务规则来判断保留哪条。

我个人常用的策略:

  • 保留第一条:适用于数据本身可靠,只是通信冗余的场景
  • 保留平均值:适用于传感器噪声较大的场景
  • 保留最新一条:适用于数据有版本更新的场景
# 按时间戳去重,保留平均值
df_dedup = df.groupby('timestamp').agg({
    'voltage': 'mean',
    'current': 'mean'
}).reset_index()

3. 模糊去重

还有一种情况:数据不完全相同,但非常相似。比如两条记录的时间戳差了1毫秒,其他字段几乎一样。这种「准重复」数据也需要处理。

我的做法是:先对时间戳做四舍五入(比如精确到秒),然后再去重。或者用相似度计算,设定一个阈值来判断是否重复。

⚠️ 我曾经犯过的错:有一次做储能电站的日发电量统计,没注意到数据采集程序在整点时刻会重复上报。结果统计出来的日发电量比实际多了将近一倍。后来加了时间戳去重才纠正过来。所以去重之前,一定要先理解数据的生成逻辑。

知识体系总览

下面这张图是我自己总结的数据清洗核心逻辑,你可以对照着看:

数据清洗核心方法体系 数据清洗 缺失值处理 异常值检测 重复数据去重 删除法 填充法 插值法 3σ原则 IQR方法 孤立森林 完全去重 部分去重 模糊去重 核心原则:先理解数据生成逻辑,再选择处理方法

这张图把今天讲的内容串起来了。你仔细看就会发现,三个分支的底层逻辑其实是一样的:先诊断问题类型,再选择对应方法,最后验证处理效果。我在实际项目中,从来不会一上来就用孤立森林或者样条插值——先看看数据长什么样,再决定用什么工具。

好了,数据清洗的基础方法就讲到这里。记住一句话:清洗数据不是目的,让数据能真实反映物理世界才是。下一节我们会聊数据标准化和归一化,到时候再细说。


专注资料整理