本节摘要:两柜专用调料——SciPy 的 scipy.stats 管统计检验类的现成函数,scikit-learn 的 preprocessing 管建模前的编码与缩放。本节按"函数用法"讲 zscore、pearsonr、各路 Scaler、OneHotEncoder 与 Pipeline 的防泄漏机制,不展开统计理论。承接 7.2 的灶膛,通往 7.4 的大灶台。
「Pipeline」,流水线——scikit-learn 世界里它不只是比喻,而是一个类:把缩放、编码、模型串成一条链,fit 一次全线生效。它反复出现的原因是替你守住一条 2.5 立过的纪律:预处理参数只能从训练数据学。调料柜里的每样东西都自带"学"与"用"两个动作(fit 与 transform),分开才防泄漏,Pipeline 把分开变成自动化。本节开两柜:SciPy 柜取现成的统计函数,sklearn 柜取预处理的正规军。往前接 7.2 的数组功夫,往后 7.4 的 Dask 也借用了类似的接口设计。
SciPy 柜:scipy.stats.zscore(数据, nan_policy='omit') 一行标准化,nan_policy 必须显式声明否则遇缺失整组变 NaN;scipy.stats.pearsonr(x, y) 与 spearmanr 一次给出相关系数与显著性两个返回值;scipy.stats.ttest_ind(a, b, equal_var=False) 做两组比较,返回统计量与 p 值;scipy.stats.boxcox(正数列) 做幂变换压偏态。sklearn 柜:StandardScaler、MinMaxScaler、RobustScaler 三兄弟(2.5 已给几何直觉,这里看接口);OneHotEncoder(handle_unknown='ignore', sparse_output=False) 编码类别且能接住没见过的取值;OrdinalEncoder 保序编码有序类别;fit 与 transform 必须分两步、训练与预测共用同一个实例。
import numpy as np from scipy import stats x = np.array([88, 92, 79, 85, 90, 84.0]) y = np.array([3.1, 3.4, 2.8, 3.0, 3.3, 2.9]) r, p = stats.pearsonr(x, y) # 相关系数与显著性一次拿全 print(round(r, 3), round(p, 4)) # 0.992 0.0 <- 高度正相关 z = stats.zscore(x) # 手写标准化交给现成函数 print(z.round(2)) # [ 0.62 1.5 -1.28 -0.14 0.98 -0.67]
场景:把"城市、收入"两列喂给下游模型——类别编码、数值缩放,用 ColumnTransformer 与 Pipeline 串好,训练集 fit、测试集 transform,一步不乱。
import pandas as pd from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, StandardScaler train = pd.DataFrame({ "城市": ["上海", "北京", "上海", "广州"], "收入": [12000.0, 9800.0, 15500.0, 7600.0], }) test = pd.DataFrame({ "城市": ["北京", "深圳"], # 深圳:训练时没见过的类别 "收入": [10200.0, 8800.0], }) pre = ColumnTransformer([ ("城市编码", OneHotEncoder(handle_unknown="ignore", sparse_output=False), ["城市"]), ("收入缩放", StandardScaler(), ["收入"]), ]) pipe = Pipeline([("预处理", pre)]) train_x = pipe.fit_transform(train) # 只在训练集 fit test_x = pipe.transform(test) # 测试集只 transform print(train_x.shape, test_x.shape) # (4, 4) (2, 4) 列对列严格对齐
深堔这个没见过的城市没有让管道报错——handle_unknown='ignore' 把它编成全零行,这正是 OneHotEncoder 比一次性 get_dummies 更适合生产的原因。
**翻车一:在全量数据上 fit。**scaler.fit(全量) 再切分,测试集信息渗进均值——2.5 的老坑,柜子里再次点名的意思是:Pipeline 的意义就是把"只能训练集 fit"变成结构约束,绕开 Pipeline 手工 fit 时要格外警醒。**翻车二:transform 之后列名丢了。**fit_transform 的输出是 ndarray,列名、索引全无——套 set_output(transform="pandas") 或事后重建列名,否则接下游时对不上列。**翻车三:LabelEncoder 用在特征上。**它是给"目标标签"用的编码器,拿去编特征会把"城市名"编成"0、1、2"这种伪有序,模型白学一套假次序——特征编码用 OneHotEncoder 或 OrdinalEncoder。**翻车四:ttest 的 equal_var 默认假设方差齐。**两组方差差异明显时不关掉它,结论会跑偏——本册不展开检验理论,但参数的默认值必须知道在赌什么。
轻量场景不必进柜:zscore 手写一行(4.2 的 (x 减均值) 除以标准差),编码用 pandas 的 get_dummies(2.5),相关系数用 DataFrame 的 corr 方法一眼出矩阵——柜子的价值在于"接口统一、可入管线、防泄漏",单步手工更快,多步成链就该进柜。类别特别多的高基数列,独热会撑爆列数,换目标编码类工具或先做频次分桶;检验类需求超出 t 检验与相关系数时,scipy.stats 还有整层货架,用到再取。
单机厨房全部看完,最后一间是真正的大灶台:7.4 的 Dask 与 PySpark,数据装不下内存时怎么继续出餐。