数据清洗基础:缺失值处理与异常值检测

数据清洗这件事,说白了就是给数据「洗澡」。我在智慧农业项目里接手过不少传感器数据,那叫一个脏——缺胳膊少腿的、数值离谱的、重复记录的,什么都有。今天咱们就聊聊最基础的三个活儿:缺失值怎么处理、异常值怎么揪出来、重复数据怎么去重。

你想想看,传感器在田里风吹日晒,信号断一下、电池掉个电,数据就缺了一块。这很正常。关键是你得知道怎么补,或者干脆扔掉。

缺失值处理策略

缺失值,就是数据表里那些空着的格子。我习惯先问三个问题:缺了多少?为什么缺?缺的值重要吗?

1. 直接删除

最简单粗暴的方法。但别乱删,得看情况。

  • 行删除:某条记录缺了关键字段,直接删掉整行。比如土壤pH值缺失,这条数据基本废了。
  • 列删除:某个字段超过70%都是空的,那这个字段留着也没意义。我在处理某大棚温度数据时,有个「地下50cm温度」字段,90%都是空值,直接砍掉。
注意:删除前一定要确认数据量够用。如果总共就100条记录,你删掉30条,那分析结果可能就偏了。我曾经因为删太多,导致后续模型训练直接崩了——样本量不够。

2. 填充法

不删,那就填。填什么?得有点依据。

填充方式 适用场景 我踩过的坑
均值/中位数填充 数值型数据,分布较对称 某次用均值填充土壤湿度,结果把干旱期的数据全拉高了,模型误判
众数填充 分类数据,比如天气类型 还行,但注意类别要均衡
前向/后向填充 时间序列数据,比如每5分钟的温度 传感器断连时,用前一条数据填充,比瞎猜靠谱
固定值填充 你知道缺失值代表什么含义 比如「未检测到」填0,但得确认0不是有效值
我的习惯:先画个缺失值分布图。如果缺失是随机的,填充效果还行;如果是系统性的(比如某个传感器总在中午断),那填充就得小心了。

3. 插值法

插值比填充更「聪明」一点。它不是简单填个平均值,而是根据前后数据的变化趋势来推算。

常用的有线性插值和样条插值。我举个线性插值的例子:

# 假设时间点1温度20度,时间点3温度26度
# 时间点2缺失,用线性插值:
# (20 + 26) / 2 = 23度

# 用pandas实现
import pandas as pd
df['temperature'].interpolate(method='linear', inplace=True)

嗯,这里要注意:插值只适合数据变化比较平滑的情况。如果数据忽高忽低,插值反而会引入误差。我在处理风速数据时就吃过这个亏——风速变化太随机,插出来的值完全不对。

异常值检测方法

异常值,就是那些「离谱」的数据。比如土壤温度突然蹦到80度,那肯定是传感器抽风了。怎么揪出来?我常用两种方法。

1. 3σ 法则

这个法子基于正态分布。简单说:数据落在均值±3个标准差之外的概率只有0.3%,那这些点就可能是异常。

import numpy as np

def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower = mean - 3 * std
    upper = mean + 3 * std
    outliers = data[(data < lower) | (data > upper)]
    return outliers

但有个前提:数据得大致符合正态分布。我在处理大棚二氧化碳浓度数据时,发现它偏态严重,3σ 法则直接误判了20%的正常数据。所以,用之前先看看数据分布。

2. IQR 方法

IQR 是四分位距,不依赖正态分布,更鲁棒。它的逻辑是:

  • 计算 Q1(25%分位数)和 Q3(75%分位数)
  • IQR = Q3 - Q1
  • 正常范围:Q1 - 1.5×IQR 到 Q3 + 1.5×IQR
  • 超出这个范围的就是异常
def detect_outliers_iqr(data):
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    outliers = data[(data < lower) | (data > upper)]
    return outliers
对比一下:3σ 适合数据干净、分布对称的场景;IQR 更抗造,适合大多数农业数据。我个人更常用 IQR,因为它不容易被极端值带偏。

重复数据去重

重复数据,就是一模一样的记录出现了多次。原因很多:传感器重复上报、数据采集程序bug、人工录入手抖等等。

去重其实不复杂,但有个坑:你得先定义什么叫「重复」。

  • 完全重复:所有字段都一样。直接删掉多余的。
  • 部分重复:关键字段一样,但其他字段不同。比如同一时间点的温度记录了两条,数值略有差异。这时候得看业务逻辑——取平均值?取最新?还是取置信度高的?
# 完全去重
df.drop_duplicates(inplace=True)

# 按指定列去重,保留第一条
df.drop_duplicates(subset=['timestamp', 'sensor_id'], keep='first', inplace=True)

我曾经遇到过一个案例:某大棚的湿度数据,同一个时间点出现了3条记录,数值都不同。后来发现是传感器和网关之间的通信协议有bug,导致数据重复上报。最后我们按时间戳去重,取中位数作为最终值。

避坑指南:去重前一定要备份原始数据。我曾经手滑把整个表去重了,结果发现有些「重复」其实是不同传感器在同一时刻的正常读数——它们只是数值恰好一样。从那以后,我每次去重前都会先看一眼重复数据的分布。

知识体系总览

下面这张图,把咱们今天讲的内容串起来了。你可以把它当成一个决策流程:拿到脏数据,先看缺不缺,再看有没有异常,最后查重。

数据清洗基础流程 数据清洗基础流程 原始数据 缺失值处理 直接删除 填充法 插值法 异常值检测 3σ 法则 IQR 方法 重复数据去重 顺序:先处理缺失值 → 再检测异常值 → 最后去重

这张图里,我特意把顺序标出来了。为什么先处理缺失值?因为缺失值会影响后续的异常值检测——比如你用3σ法则时,缺失值太多会把均值和标准差带偏。去重放最后,是因为填充或插值后,可能会产生新的重复记录,一次性处理更干净。

好了,数据清洗的基础就这些。说白了就是三板斧:缺了就补、离谱就揪、重复就删。但每一板斧都有讲究,用对了事半功倍,用错了数据越洗越脏。下次你拿到传感器数据,不妨先按这个流程走一遍,心里就有底了。