数据是燃料:理解数据类型、质量评估与基本预处理
"垃圾进,垃圾出。"(Garbage in, garbage out.)
------在机器学习中,再精妙的算法也无法从劣质数据中炼出黄金。
一、为什么数据比算法更重要?
2009年,Google 的研究员 Alon Halevy、Peter Norvig 和 Fernando Pereira 在一篇题为《The Unreasonable Effectiveness of Data》的经典短文中指出:在大规模高质量数据上使用简单模型,往往优于在小规模数据上使用复杂模型。这一思想深刻影响了后续数据驱动的机器学习发展。
这颠覆了许多人的认知。我们常以为AI的突破来自"更聪明的算法",但现实是:数据才是真正的驱动力。
想象两个厨师:
- 厨师A:用顶级和牛、新鲜松露,配普通煎锅;
- 厨师B:用冷冻合成肉、隔夜蔬菜,配米其林定制厨具。
谁更可能做出美味佳肴?答案不言而喻。
在机器学习中:
- 数据 = 食材
- 算法 = 厨具
- 特征工程 = 刀工与调味
本篇文章将带你系统理解:
✅ 数据的类型与结构;
✅ 如何评估数据质量;
✅ 常见数据问题及预处理方法;
✅ 为后续建模打下坚实"燃料"基础。
二、数据的类型:你的模型能"吃"什么?
并非所有数据都能直接喂给模型。不同类型的变量需要不同的处理方式。
1. 数值型数据(Numerical)
表示可测量的量,分为两类:
| 子类 | 特点 | 示例 | 处理方式 |
|---|---|---|---|
| 连续型(Continuous) | 可取任意实数值 | 年龄、身高、温度、价格 | 直接使用,或标准化 |
| 离散型(Discrete) | 只能取整数值 | 家庭成员数、点击次数 | 可视为连续或分类 |
🔍 注意:虽然"年龄"理论上连续,但数据中常以整数存储,仍按连续处理。
2. 分类型数据(Categorical)
表示类别或标签,无内在顺序。
| 子类 | 特点 | 示例 | 处理方式 |
|---|---|---|---|
| 名义型(Nominal) | 类别无顺序 | 性别、城市、颜色 | One-Hot 编码 |
| 有序型(Ordinal) | 类别有顺序 | 教育程度(小学<中学<大学)、评分(1~5星) | Label 编码或映射为数值 |
⚠️ 错误示例:若将"红=0, 蓝=1, 绿=2"直接输入模型,算法会误以为"绿 > 蓝 > 红",产生错误关联。
3. 文本型数据(Text)
如评论、邮件、新闻文章。
不能直接使用,需转换为数值表示(如词袋、TF-IDF、词嵌入),将在后续文章详述。
4. 时间序列数据(Time Series)
带有时间戳的数据,如股票价格、传感器读数。
需特殊处理(滑动窗口、滞后特征),暂不展开。
5. 图像/音频等非结构化数据
需深度学习模型(CNN、RNN)处理,不属于本阶段重点。
✅ 核心原则 :所有输入模型的数据必须是数值型矩阵(NumPy array 或 Pandas DataFrame)。
三、数据质量的五大维度:如何判断"食材"是否新鲜?
高质量数据应满足以下五个维度。任一维度缺失,都可能导致模型失效。
1. 完整性(Completeness)
定义 :数据是否缺失关键字段或记录。
问题表现:
- 某列大量
NaN(如用户收入未填写); - 某时间段无数据(如传感器故障)。
评估方法:
python
import pandas as pd
df = pd.read_csv('your_data.csv')
print(df.isnull().sum()) # 各列缺失数量
print(df.isnull().mean() * 100) # 各列缺失百分比
案例 :泰坦尼克号数据集中 age 缺失 177/891 ≈ 20%,需处理。
2. 一致性(Consistency)
定义 :数据是否遵循统一格式与逻辑。
问题表现:
- 同一用户在不同表中ID不同;
- 日期格式混用("2023-01-01" vs "01/01/2023");
- 单位混乱(kg vs lbs)。
评估方法:
python
# 检查唯一值
print(df['country'].unique())
# 检查逻辑矛盾
print(df[(df['age'] < 0) | (df['age'] > 120)]) # 不合理年龄
3. 准确性(Accuracy)
定义 :数据是否真实反映现实。
问题表现:
- 用户填写虚假信息(如年龄填999);
- 传感器校准错误(温度恒为25℃);
- 爬虫抓取错误(价格多一个零)。
难点:准确性最难自动检测,常需业务知识或外部验证。
4. 时效性(Timeliness)
定义 :数据是否及时更新。
问题表现:
- 用2010年的房价预测2025年市场;
- 用户兴趣已变,但画像未更新。
应对:建立数据更新机制,监控数据新鲜度。
5. 有效性(Validity)
定义 :数据是否符合预设规则。
问题表现:
- 邮箱字段包含电话号码;
- 订单状态出现"已发货"但物流单号为空。
评估方法:
python
# 正则表达式验证邮箱
import re
email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
invalid_emails = df[~df['email'].str.match(email_pattern, na=False)]
📊 行动建议:在每个新项目开始时,制作一份《数据质量报告》,列出上述五维度的问题清单。
四、常见数据问题及预处理策略
问题1:缺失值(Missing Values)
原因分析
- 用户不愿提供(如收入);
- 系统未采集(如新功能上线前无数据);
- 传输错误(如网络中断)。
处理策略(按优先级)
| 方法 | 适用场景 | 代码示例 |
|---|---|---|
| 删除 | 缺失比例高(>50%)且非关键列 | df.drop(columns=['deck']) |
| 填充均值/中位数 | 数值型,缺失少(<10%),分布近正态 | df['age'].fillna(df['age'].median(), inplace=True) |
| 填充众数 | 分类型 | df['embarked'].fillna(df['embarked'].mode()[0], inplace=True) |
| 前向/后向填充 | 时间序列 | df['price'].fillna(method='ffill') |
| 模型预测填充 | 缺失多但与其他特征强相关 | 用随机森林预测缺失的 age |
💡 黄金法则 :永远不要用0填充数值型缺失值!0是一个有效数值,会扭曲分布。
高级技巧:标记缺失
有时"是否缺失"本身就是信号。例如,未填写收入可能代表低收入群体。
python
df['income_missing'] = df['income'].isnull().astype(int)
df['income'].fillna(df['income'].median(), inplace=True)
问题2:异常值(Outliers)
什么是异常值?
- 统计角度:偏离均值超过3个标准差;
- 业务角度:不符合常识(如年龄200岁)。
检测方法
python
# 方法1:箱线图(IQR)
Q1 = df['fare'].quantile(0.25)
Q3 = df['fare'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['fare'] < Q1 - 1.5*IQR) | (df['fare'] > Q3 + 1.5*IQR)]
# 方法2:Z-score(适用于近正态分布)
from scipy import stats
z_scores = stats.zscore(df['age'].dropna())
outliers = df.iloc[np.where(np.abs(z_scores) > 3)]
处理策略
| 方法 | 说明 |
|---|---|
| 保留 | 异常值真实存在且重要(如金融欺诈) |
| 截断(Winsorizing) | 将极端值替换为95%分位数 |
| 删除 | 明确是错误数据(如年龄=999) |
| 分箱 | 将连续值转为区间(如"高票价"、"低票价") |
⚠️ 警惕:盲目删除异常值可能丢失关键信息!
问题3:重复数据(Duplicates)
python
# 查看完全重复行
print("重复行数:", df.duplicated().sum())
# 删除重复行
df_clean = df.drop_duplicates()
# 按关键列去重(如用户ID)
df_clean = df.drop_duplicates(subset=['user_id'])
问题4:不一致格式
python
# 统一大小写
df['city'] = df['city'].str.lower()
# 清理空格
df['name'] = df['name'].str.strip()
# 标准化日期
df['date'] = pd.to_datetime(df['date'], errors='coerce')
五、特征缩放:让不同尺度的特征公平竞争
为什么需要缩放?
考虑一个房价预测模型,特征包括:
- 房屋面积(单位:平方米,范围 30--300)
- 卧室数量(单位:间,范围 1--5)
若直接输入模型,面积的数值远大于卧室数,导致模型过度关注面积,忽略卧室信息。
📌 大多数基于距离或梯度的算法(KNN、SVM、神经网络、K-Means)对特征尺度敏感 。
决策树类算法(随机森林、XGBoost)则不受影响。
常用缩放方法
1. 标准化(Standardization / Z-score Normalization)
将特征转换为 均值为0,标准差为1 的分布。
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[['area', 'bedrooms']] = scaler.fit_transform(df[['area', 'bedrooms']])
公式:
xscaled=x−μσ x_{\text{scaled}} = \frac{x - \mu}{\sigma} xscaled=σx−μ
✅ 适用:数据近似正态分布;
✅ 优点:保留原始分布形状;
✅ 注意:对异常值敏感。
2. 归一化(Min-Max Scaling)
将特征缩放到 [0, 1] 区间。
python
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df[['area', 'bedrooms']] = scaler.fit_transform(df[['area', 'bedrooms']])
公式:
xscaled=x−xminxmax−xmin x_{\text{scaled}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}} xscaled=xmax−xminx−xmin
✅ 适用:数据分布未知或有边界;
⚠️ 缺点:受异常值影响大(若 max 极大,则其他值被压缩到0附近)。
3. 鲁棒缩放(Robust Scaler)
使用中位数和四分位距,对异常值不敏感。
python
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
df[['area', 'bedrooms']] = scaler.fit_transform(df[['area', 'bedrooms']])
✅ 最佳实践:
- 先处理异常值,再缩放;
- 仅用训练集拟合缩放器,再应用于测试集(避免数据泄露)。
六、分类变量编码:让模型理解"类别"
1. Label Encoding(标签编码)
将类别映射为整数:['red', 'blue', 'green'] → [0, 1, 2]
python
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['color_encoded'] = le.fit_transform(df['color'])
✅ 适用:有序型变量 (如教育程度);
❌ 禁用:名义型变量(会引入虚假顺序)。
2. One-Hot Encoding(独热编码)
为每个类别创建一个二元列:
color → color_red color_blue color_green
red 1 0 0
blue 0 1 0
green 0 0 1
python
# Pandas 方式(推荐)
df_encoded = pd.get_dummies(df, columns=['color'], prefix='color')
# scikit-learn 方式
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(df[['color']])
✅ 适用:名义型变量 ;
⚠️ 注意:类别过多会导致维度爆炸(如用户ID有10万种 → 10万列)。
3. 高基数分类变量处理(高级)
当类别数极大(>50),可采用:
- 目标编码(Target Encoding):用目标变量的均值代替类别;
- 嵌入(Embedding):深度学习方法;
- 聚类分组:将相似类别合并。
📌 本阶段只需掌握 One-Hot 和 Label Encoding。
七、端到端实战:清洗并准备一个真实数据集
我们将使用 加州房价数据集(California Housing),演示完整预处理流程。
步骤1:加载数据
python
from sklearn.datasets import fetch_california_housing
import pandas as pd
import numpy as np
data = fetch_california_housing(as_frame=True)
df = data.frame
print(df.head())
步骤2:检查数据质量
python
print("缺失值:\n", df.isnull().sum()) # 无缺失
print("描述统计:\n", df.describe())
print("数据类型:\n", df.dtypes)
步骤3:处理异常值(房屋年龄 > 50 年?)
python
# 房屋年龄最大为52,合理,保留
# 但 median_house_value 有上限(500,001),可能是截断值
df = df[df['MedHouseVal'] < 500001] # 移除截断点
步骤4:特征工程(创建新特征)
python
# 房间密度 = 总房间数 / 人口
df['rooms_per_person'] = df['AveRooms'] / df['Population']
# 卧室比例
df['bedroom_ratio'] = df['AveBedrms'] / df['AveRooms']
步骤5:划分训练/测试集(防止数据泄露)
python
from sklearn.model_selection import train_test_split
X = df.drop('MedHouseVal', axis=1)
y = df['MedHouseVal']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
步骤6:标准化数值特征
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # 注意:只用 .transform()
✅ 现在,
X_train_scaled已是干净、标准化的数值矩阵,可直接输入任何机器学习模型。
八、数据预处理 checklist(收藏备用)
在开始建模前,逐项核对:
- 缺失值:已识别并处理(删除/填充/标记)?
- 异常值:已检测并决定保留/修正/删除?
- 重复数据:已去重?
- 格式一致性:日期、字符串、单位已统一?
- 分类变量:已正确编码(One-Hot / Label)?
- 特征缩放:对敏感算法已标准化/归一化?
- 数据泄露:预处理(如缩放)仅基于训练集?
- 目标变量:回归任务无异常值?分类任务标签正确?
九、结语:数据预处理不是"脏活",而是"艺术"
很多初学者把数据清洗视为枯燥的体力劳动,急于跳到"炫酷"的建模阶段。但经验丰富的数据科学家知道:
80% 的工作在于理解与准备数据,20% 在于建模。
每一次缺失值填充、每一个异常值判断、每一列特征构造,都是你与数据对话的过程。你越了解它,模型就越能替你说话。
在下一篇文章中,我们将进入机器学习的核心------线性模型。你会发现,即使是最简单的线性回归,也能在良好数据上展现出惊人力量。
行动建议
- 下载一个新数据集(如 Kaggle 的 "Loan Prediction");
- 按照本文 checklist 逐项清洗;
- 记录每一步决策的原因(如"用中位数填充年龄,因分布右偏");
- 将清洗后的数据保存为新文件,供后续建模使用。
记住:高质量的数据,是模型成功的先决条件 。
你今天的耐心,就是明天模型的准确率。