数据探索与预处理:数据清洗、缺失值处理、异常值检测、描述性统计分析

各位同学,欢迎来到第四章。说实话,这一章可能是整个课程里最「脏」、最累,但也最关键的环节。我见过太多人,模型建得花里胡哨,结果一上线就崩,为什么?因为数据没洗干净。你想想看,垃圾进,垃圾出,这是铁律。

我个人习惯,拿到任何数据集,第一件事不是跑模型,而是先「摸」一遍数据。怎么摸?就是本章要讲的:清洗、补缺、揪异常、做统计。这四步走完,你才算真正认识你的数据。

核心观点:数据预处理占整个建模工作量的60%-80%。别嫌烦,这一步省下的时间,后面会加倍还回来。

4.1 数据清洗:把「脏东西」筛出去

数据清洗,说白了就是处理那些格式不对、内容乱码、重复记录的问题。我在项目中遇到过最离谱的一次,客户给的报价表里,价格字段居然混着「电话号」和「备注」,因为Excel单元格合并导致数据错位了。

常见的脏数据有这几类:

  • 格式不一致:比如日期有的是"2024-01-01",有的是"01/01/2024",还有的是"2024年1月1日"。
  • 重复记录:同一个客户、同一个产品、同一个时间点,出现了两条一模一样的报价。
  • 无效字符:数字字段里混入了字母、空格、换行符。
  • 逻辑错误:比如折扣率大于100%,或者成本价高于售价。

嗯,这里要注意:清洗不是删除所有「看起来不对」的数据,而是要基于业务逻辑判断。我曾经因为手滑删了一批「异常折扣」的数据,结果那是大客户的战略合作价,差点出事故。

# 一个简单的清洗示例(Python伪代码)
import pandas as pd

df = pd.read_csv('quote_data.csv')

# 1. 去除重复行
df = df.drop_duplicates()

# 2. 统一日期格式
df['quote_date'] = pd.to_datetime(df['quote_date'], errors='coerce')

# 3. 去除价格字段中的非数字字符
df['price'] = df['price'].str.replace(r'[^0-9.]', '', regex=True)
df['price'] = pd.to_numeric(df['price'], errors='coerce')

# 4. 过滤逻辑错误:折扣率不能大于1
df = df[df['discount_rate'] <= 1.0]

4.2 缺失值处理:别急着删,先问问为什么

缺失值,是数据预处理里最让人头疼的问题。很多人一上来就用均值填充,或者直接删掉有缺失的行。我建议你先停一下,想想:这个数据为什么缺失?

缺失的原因通常有三种:

  1. 随机缺失:比如客户忘记填某个字段,纯属偶然。这种可以用均值、中位数或众数填充。
  2. 非随机缺失:比如高价值订单的「折扣率」字段经常为空,因为销售故意不填。这种填充会引入偏差,需要单独建模处理。
  3. 结构性缺失:比如新产品没有历史报价数据。这种不能填充,只能标记为「无历史」。

我的经验:对于报价数据,如果缺失率超过30%,我一般会把这个字段单独拎出来,作为一个「是否缺失」的特征加入模型。有时候缺失本身就是一个信号。

# 缺失值处理示例
# 查看缺失率
print(df.isnull().mean())

# 数值型字段:用中位数填充(抗异常值)
df['price'].fillna(df['price'].median(), inplace=True)

# 分类型字段:用众数填充
df['product_category'].fillna(df['product_category'].mode()[0], inplace=True)

# 时间序列字段:用前向填充
df['last_quote_price'].fillna(method='ffill', inplace=True)

4.3 异常值检测:揪出那些「离谱」的数据点

异常值,就是那些明显偏离正常范围的数据点。比如一个普通产品的报价是100元,突然冒出来一个100000元,这大概率是录入错误。

检测异常值的方法有很多,我常用的有三种:

方法 适用场景 优点 缺点
Z-Score(3σ原则) 数据近似正态分布 简单直观 对非正态分布效果差
IQR(四分位距法) 偏态分布、有离群点 不受极端值影响 阈值选择较主观
孤立森林 高维数据、非线性关系 无需假设分布 计算量较大

我个人习惯先用IQR法快速扫一遍,再用孤立森林做二次确认。为什么?因为IQR法在报价数据上表现稳定,不容易误伤。

# IQR法检测异常值
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['price'] < lower_bound) | (df['price'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")

警告:异常值不一定是错误!它可能是真实的「黑天鹅事件」,比如大促期间的超低价。处理前一定要跟业务方确认。我曾经把一次「清仓甩卖」的数据当异常删了,结果模型完全预测不出促销效果。

4.4 描述性统计分析:给你的数据「画像」

做完清洗、补缺、揪异常,接下来就是描述性统计了。这一步的目的是让你对数据有一个全局的、量化的认识。

我通常会看这几个维度:

  • 集中趋势:均值、中位数、众数。报价数据我特别关注中位数,因为它不受极端值影响。
  • 离散程度:标准差、方差、极差、四分位距。标准差大,说明报价策略差异大,可能需要细分市场。
  • 分布形态:偏度、峰度。偏度为正说明大部分报价偏低,少数偏高;峰度高说明报价集中在某个区间。
  • 频数统计:分类字段的分布,比如哪个产品类别报价最多,哪个客户询价最频繁。
# 描述性统计示例
# 数值型字段概览
print(df.describe())

# 分类字段频数统计
print(df['product_category'].value_counts())

# 偏度与峰度
print(f"价格偏度: {df['price'].skew():.2f}")
print(f"价格峰度: {df['price'].kurtosis():.2f}")

你看,做完这一步,你对数据的「画像」就清晰了。比如你发现价格的中位数是500,但均值是800,说明有少量高价订单在拉高平均价。这时候你就要想:这些高价订单是正常的VIP客户,还是异常值?

知识体系总览

下面这张图,是我自己总结的数据预处理全流程。你可以把它当作一个检查清单,每次做项目时对照着来。

数据预处理知识体系 原始数据集 数据清洗 去重 · 格式统一 · 逻辑校验 缺失值处理 填充 · 删除 · 标记 异常值检测 Z-Score · IQR · 孤立森林 描述性统计 均值 · 中位数 · 分布形态 干净、完整、可建模的数据集 四步走完,数据才能「喂」给模型 关键检查指标 ✔ 缺失率 < 5% 可填充,> 30% 需单独处理 ✔ 异常值先确认业务含义,再决定是否剔除 ✔ 描述性统计要关注中位数 vs 均值的差异

好了,这一章的内容就到这里。数据预处理没有太多花哨的技巧,核心就是「细心」和「业务理解」。你只要把这四步走扎实了,后面的建模工作会顺畅很多。


专注资料整理