如何在Dask dataframe上实现Leave one out留一编码
Dask DataFrame 实现留一(Leave-One-Out)目标编码
留一编码的核心逻辑不需要逐行遍历排除样本,完全可以通过全局聚合+回表扣减当前行贡献的方式实现,天然适配Dask的分块并行计算模型,哪怕分类类别超过1000、数据量很大也能高效运行,不会出现内存瓶颈。
实现步骤
- 先计算全量目标列
y的全局均值,用于填充仅出现1次的分类(避免计算时除零错误) - 按待编码的分类列分组,统计每个分类下的
y总和、样本总计数:这一步Dask会自动分块执行聚合,且聚合结果的行数等于分类类别数,哪怕上万个类别体量也非常小,可以直接拉到本地内存 - 将聚合统计结果合并回原Dask DataFrame
- 计算留一编码值:
(分类总y和 - 当前行y值) / (分类总样本数 - 1),如果分类仅出现1次,直接用全局y均值填充即可
可直接运行的代码
import dask.dataframe as dd import pandas as pd import numpy as np # 示例数据 df = pd.DataFrame({ "A": [1, 2, 1, 2, 3, 1, 2, 3, 5], "B": ["a", "b", "c", "c", "a", "b", "b", "a", "c"], "y":[0, 10, 2, 1, 4, 1, 6, 12, 11] }) X = dd.from_pandas(df, npartitions=2) # 全局y均值,处理单样本分类的边界情况 global_y_mean = X.y.mean().compute() # 按分类列B聚合统计 group_stats = X.groupby("B").agg( group_y_sum=("y", "sum"), group_count=("y", "count") ).compute() # 合并统计值回原表 X_encoded = X.merge(group_stats, left_on="B", right_index=True, how="left") # 计算留一编码 X_encoded["B_loo_encoded"] = np.where( X_encoded["group_count"] > 1, (X_encoded["group_y_sum"] - X_encoded["y"]) / (X_encoded["group_count"] - 1), global_y_mean ) # 删除临时统计列 X_encoded = X_encoded.drop(columns=["group_y_sum", "group_count"]) # 触发计算查看结果 print(X_encoded.compute())
注意事项
- 该实现完全避免目标泄露:编码值严格排除了当前行的y值,符合留一编码的定义
- 不存在分区偏差:聚合是基于全量数据计算的,不会因为Dask分块出现统计值不准的问题
- 训练/测试集拆分场景下,必须只用训练集数据计算
group_stats和global_y_mean,再映射到验证/测试集,绝对不能把测试集的y信息纳入统计,否则会造成数据泄露 - 如果有多个分类列需要编码,对每个分类列重复执行「聚合-合并-计算编码」的流程即可,单分类的聚合结果体量极小,不会带来额外性能压力
内容的提问来源于stack exchange,提问作者Shawn Brar
相关产品推荐
相关产品推荐

