KBinsDiscretizer是scikit-learn官方推荐的分箱工具,支持uniform(等宽)、quantile(等频)、kmeans三种策略,默认输出one-hot编码;需传入二维数组、预处理缺失值、合理设置n_bins以避免空箱。

用 KBinsDiscretizer 做等宽/等频分箱最稳妥
Scikit-learn 从 0.20 版本起,KBinsDiscretizer 是官方推荐的分箱工具,替代了已弃用的 sklearn.preprocessing.Binarizer 和手动写的 pd.cut 混合方案。它统一处理数值型特征的离散化,支持三种策略:uniform(等宽)、quantile(等频)、kmeans(基于聚类的边界)。默认输出是独热编码后的稀疏矩阵,若需原始整数标签,得设 encode='ordinal'。
常见错误是直接传入 DataFrame 而非二维数组——KBinsDiscretizer 只接受 shape 为 (n_samples, n_features) 的 numpy.ndarray 或 scipy.sparse 矩阵。传入 pandas Series 会报 ValueError: Expected 2D array, got 1D array instead。
- 先用
.values.reshape(-1, 1)把单列转成二维(如X_col = df['age'].values.reshape(-1, 1)) - 若要批量处理多列,用
ColumnTransformer配合KBinsDiscretizer,避免手动循环 -
n_bins不宜设得过大(比如 >20),尤其样本量小的时候,某些箱可能为空,触发ValueError: Found array with 0 sample(s)
处理缺失值前必须显式填充或删除
KBinsDiscretizer 不接受 np.nan,遇到就会报错 ValueError: Input contains NaN。它不像 pd.cut 那样自动跳过 NaN,也不提供 na_option 参数。你必须在调用前决定怎么处置缺失值:
- 用
SimpleImputer(strategy='median')填充(适合连续型数值,且不希望引入新类别) - 用
SimpleImputer(strategy='constant', fill_value=-999)单独占一个离散值(后续可设为特殊箱,如 “unknown”) - 彻底删掉含缺失的行(仅当缺失率
注意:填充值不能超出原始数据范围太多,否则 quantile 策略下可能扭曲分位点计算;uniform 下则可能让首尾箱严重不平衡。
立即学习“Python免费学习笔记(深入)”;
用 make_column_transformer 批量分箱多列更安全
单列分箱容易,但实际项目中常需对不同列用不同 n_bins 或不同策略(比如收入用等频、年龄用等宽)。硬写 for 循环拼接结果容易出维度错位,也难复现。正确做法是用 make_column_transformer 构建预处理流水线:
from sklearn.compose import make_column_transformer from sklearn.preprocessing import KBinsDiscretizer <p>ct = make_column_transformer( (KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='quantile'), ['income']), (KBinsDiscretizer(n_bins=4, encode='ordinal', strategy='uniform'), ['age']), remainder='passthrough' # 其他列不动 ) X_discrete = ct.fit_transform(X_df)
关键点:
-
remainder='passthrough'必须显式声明,否则未指定列会被丢弃 - 返回的是 numpy 数组,列顺序与
make_column_transformer中定义顺序一致,不是原始 DataFrame 列序 - 若后续要进
LogisticRegression等模型,建议保持encode='ordinal';若进树模型(如RandomForestClassifier),整数编码本身无问题,无需再做 one-hot
自定义分箱边界只能靠 numpy.digitize 或 pd.cut
KBinsDiscretizer 不支持传入自定义 bin 边界(比如 [0, 18, 35, 60, 100])。想实现年龄分段 “未成年/青年/中年/老年”,就得绕开它:
- 用
numpy.digitize(x, bins=[0, 18, 35, 60, 100]) - 1,返回 0~3 的索引(注意边界左闭右开) - 用
pd.cut(x, bins=[0, 18, 35, 60, 100], labels=False, include_lowest=True),更直观,且能处理NaN为NaN - 务必在 pipeline 中用
FunctionTransformer封装,否则无法和fit_transform流程对齐
这类手动分箱的边界必须在训练集上确定,然后固化下来用于测试集——不能每次 fit_transform 都重新算,否则线上线下不一致。


















