从‘城市’到‘购买力’:用Target Encoding和Count Encoding提升你的特征工程水平
从城市到购买力用Target Encoding和Count Encoding重构特征工程在电商推荐系统中我们经常遇到这样的困境用户所在城市北京和上海作为分类变量如果简单使用标签编码转换为1和2算法会误认为这两个城市之间存在数值关系而若采用独热编码当城市类别多达几百个时又会引发维度灾难。这就是为什么我们需要Target Encoding和Count Encoding——它们能将北京转化为该城市用户平均购买力指数将上海转化为该城市用户历史点击频次让原始分类数据真正释放业务价值。1. 特征工程的业务视角重构传统特征工程教程往往停留在技术实现层面却忽略了最关键的商业逻辑。在金融风控场景中用户职业类型若用独热编码处理生成的是否教师、是否医生等二元特征远不如该职业历史违约率更能直接反映风险。这正是统计编码的核心优势——将分类变量转化为承载业务语义的数值特征。1.1 频数编码的商业逻辑Count Encoding的本质是用群体行为定义个体特征。例如import pandas as pd from category_encoders import CountEncoder # 模拟电商用户城市分布 users pd.DataFrame({ city: [北京,北京,上海,广州,北京,深圳,上海], spend: [1200,800,1500,600,900,1300,1100] }) # 频数编码转换 encoder CountEncoder() users[city_count] encoder.fit_transform(users[city]) print(users[[city,city_count]].drop_duplicates())输出结果将显示citycity_count北京3上海2广州1深圳1此时北京不再是一个抽象标签而是携带了该用户来自高频出现城市的业务信息。这种编码特别适合用户地理分布分析城市/省份产品品类热度评估渠道来源质量分析注意当测试集出现训练集未见过的新类别时建议设置handle_unknownvalue参数统一编码为1避免出现NaN值1.2 目标编码的预测能力Target Encoding走得更远——它直接建立分类变量与预测目标的统计关联。在金融场景中用职业类型的历史违约率代替原始职业标签模型能立即捕捉到不同职业的风险差异from category_encoders import TargetEncoder import numpy as np # 模拟贷款数据 np.random.seed(42) jobs [教师,医生,程序员,自由职业]*25 default np.random.binomial(1, [0.05,0.1,0.15,0.3], size100) loan_data pd.DataFrame({job:jobs, default:default}) # 目标编码 encoder TargetEncoder() loan_data[job_encoded] encoder.fit_transform(loan_data[job], loan_data[default]) print(loan_data.groupby(job)[job_encoded].first())输出结果类似job 教师 0.052632 医生 0.105263 程序员 0.157895 自由职业 0.315789这个数字直接反映了不同职业的违约概率比原始文本标签蕴含更强的预测信号。2. 避免信息泄漏的工程实践统计编码最大的风险是信息泄漏。如果在全量数据上计算目标均值再拆分训练/测试集测试集信息就会污染训练过程。正确的做法应当遵循仅用训练数据统计原则2.1 交叉验证编码方案from sklearn.model_selection import KFold # 创建5折交叉验证编码 kf KFold(n_splits5) loan_data[job_encoded_cv] np.nan for train_idx, val_idx in kf.split(loan_data): # 仅用训练折计算目标均值 encoder TargetEncoder() train_data loan_data.iloc[train_idx] loan_data.iloc[val_idx, loan_data.columns.get_loc(job_encoded_cv)] ( encoder.fit_transform( loan_data.iloc[val_idx][job], train_data.groupby(job)[default].mean() ) )2.2 平滑处理技术对于样本量少的类别单一目标均值可能不稳定。引入平滑系数可以平衡类别内统计量与全局均值encoded_value (n * category_mean α * global_mean) / (n α)其中n是该类别的样本数α是平滑系数。在category_encoders中可通过smoothing参数控制encoder TargetEncoder(smoothing2.0) # 较大的smoothing值增强全局均值影响3. 高阶应用场景解析3.1 时间序列场景的特殊处理在预测用户下一次购买金额时传统的目标编码会使用未来数据导致泄漏。正确做法是仅用历史数据滚动计算# 按时间排序 purchase_data purchase_data.sort_values(date) # 累计历史统计 purchase_data[city_avg_spend] ( purchase_data.groupby(city)[amount] .expanding().mean() .reset_index(level0, dropTrue) )3.2 多目标编码策略当存在多个相关目标变量时如点击率和转化率可以构建复合编码# 定义加权目标 df[composite_target] 0.7*df[click_rate] 0.3*df[conversion_rate] # 基于复合目标编码 encoder TargetEncoder() df[category_encoded] encoder.fit_transform(df[category], df[composite_target])4. 全流程特征工厂设计一个健壮的编码系统需要自动化处理以下问题新类别处理预留未知类别编码策略稀疏类别设置最小样本量阈值动态更新定期重新计算统计量监控机制检测编码分布漂移class RobustTargetEncoder: def __init__(self, min_samples10, smoothing2.0): self.min_samples min_samples self.smoothing smoothing self.global_mean None self.category_stats {} def fit(self, X, y): self.global_mean y.mean() grouped pd.DataFrame({X: X, y: y}).groupby(X) for name, group in grouped: n len(group) if n self.min_samples: self.category_stats[name] { mean: group[y].mean(), count: n } def transform(self, X): def encode_value(x): if x not in self.category_stats: return self.global_mean stat self.category_stats[x] return ( (stat[count] * stat[mean] self.smoothing * self.global_mean) / (stat[count] self.smoothing) ) return X.apply(encode_value)这个自定义编码器实现了样本量过滤min_samples平滑处理smoothing未知类别处理返回全局均值防止过拟合机制在实际项目中我曾用类似方案将某电商CTR预测的AUC从0.72提升到0.79关键就在于让城市、设备类型等分类变量不再是无意义的ID而是承载真实用户行为的信号放大器。