数据预处理
清洗、特征工程、Train/Test 划分。
35 分钟
数据预处理
这一课你将能做到
- 处理缺失值和异常值:isna 排查、SimpleImputer 填充、分位数截断
- 用 OneHotEncoder 编码类别特征,用 StandardScaler 标准化数值特征,并说清为什么这么干
- 正确划分训练/测试集,理解并规避「数据泄漏」这个最隐蔽的错误
为什么要学
模型不挑食的前提是你把饭做好。真实做题记录里充满缺失(用户中途退出没记录用时)、异常(挂后台 99999 秒)、类别(「循环」「集合」没法直接进公式)。预处理做错一步,轻则精度下降,重则「测试集表现虚高」——训练时偷看了答案(数据泄漏),上线直接翻车。
面试里「项目效果为什么线上掉那么多」,一半的答案藏在数据泄漏里。这一课教你把饭做干净。
先建立图像
原始数据 预处理流水线 干净数据
┌─────────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ seconds: NaN │ │ 1 缺失值 → 中位数填充 │ │ 数值已标准化 │
│ seconds: 99999 │──▶│ 2 异常值 → 截断 │──▶│ 类别已 One-Hot │
│ topic: "多线程" │ │ 3 类别 → One-Hot │ │ 无缺失 │
│ attempts: 5 │ │ 4 数值 → 标准化 │ │ 已切分 │
│ ... │ │ 5 切分 → train/test │ └──────────────┘
└─────────────────┘ └──────────────────────┘
跟着做
步骤 1:造一份「脏」的做题数据
import pandas as pd
import numpy as np
df = pd.DataFrame({
"topic": ["循环", "集合", "多线程", "循环", "集合", "多线程"],
"seconds": [42, np.nan, 99999, 38, 66, 145],
"attempts": [12, 8, 3, 15, 9, 2],
"correct": [1, 1, 0, 1, 1, 0],
})
print(df.isna().sum()) # 每列缺失几个
print(df.describe()) # seconds 最大 99999,一眼假
步骤 2:缺失值——先问为什么缺,再决定怎么填
from sklearn.impute import SimpleImputer
imp = SimpleImputer(strategy="median") # 用时这种右偏数据用中位数
df["seconds"] = imp.fit_transform(df[["seconds"]])
print(df["seconds"].tolist()) # NaN 变成 60.0
填均值还是中位数:用时、收入这类长尾分布用中位数(抗极端值);接近正态的用均值。类别列用 strategy="most_frequent" 或单独加一列「是否缺失」——缺失本身往往就是信号(比如超时退出)。
步骤 3:异常值——用业务规则截断
upper = 300 # 业务上限:3 分钟还没答完就当挂后台
df["seconds"] = df["seconds"].clip(upper=upper)
print(df["seconds"].max()) # 300
真实数据建议双管齐下:业务规则(如单题最长 30 分钟)+ 统计规则(3σ 或 1% 分位截断)。注意:异常值不一定是错,可能是「这道题真的很难」,截断前先想业务。
步骤 4:类别编码——把「多线程」变成数字
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(sparse_output=False)
topic_oh = enc.fit_transform(df[["topic"]])
print(enc.get_feature_names_out()) # ['topic_多线程' 'topic_循环' 'topic_集合']
df_oh = pd.DataFrame(topic_oh, columns=enc.get_feature_names_out())
df = pd.concat([df.drop(columns="topic"), df_oh], axis=1)
print(df)