hscredit.core.encoders.quantile_encoder 源代码

"""Quantile Encoder (分位数编码器).

基于目标变量分位数对类别特征进行编码。
"""

from typing import Optional, List, Dict, Union, Any
import numpy as np
import pandas as pd

from .base import BaseEncoder


[文档] class QuantileEncoder(BaseEncoder): """分位数编码器. 用目标变量的指定分位数(如中位数)对每个类别进行编码。 适用于回归任务和存在异常值的场景。 **参数** :param cols: 需要编码的列名列表。如果为None,则自动识别所有列(支持类别型和数值型) :param quantile: 分位数,范围[0, 1],默认为0.5(中位数) :param smoothing: 平滑参数,默认为1.0 :param m: 先验权重参数,默认为1.0 :param handle_unknown: 处理未知类别的方式,默认为'value' :param handle_missing: 处理缺失值的方式,默认为'value' :param drop_invariant: 是否删除方差为0的列,默认为False :param return_df: 是否返回DataFrame,默认为True **属性** - mapping_: 分位数编码映射字典,格式为 {col: {category: quantile_value}} - global_quantile_: 全局分位数 **参考样例** >>> from hscredit.core.encoders import QuantileEncoder >>> encoder = QuantileEncoder(cols=['category'], quantile=0.5) >>> X_encoded = encoder.fit_transform(X, y) >>> >>> # 使用第90百分位数 >>> encoder = QuantileEncoder(cols=['category'], quantile=0.9) >>> X_encoded = encoder.fit_transform(X, y) **注意** 以目标分位数(而非均值)编码,对异常值更稳健;通过 ``m`` 先验权重向全局分位数做收缩 (类别样本越少越靠近全局值)以抑制过拟合。同属有监督编码,需训练集 fit、测试集 transform。 **引用** Valdez-Valenzuela, A. et al. (2021). *Measuring the quantile encoder.* https://arxiv.org/abs/2105.13783 ;另见 category_encoders ``QuantileEncoder``: https://contrib.scikit-learn.org/category_encoders/quantile.html """ # global_quantile_ 是 transform 时未知/缺失类别的填充值,须随映射一并序列化 _EXTRA_STATE_ATTRS = ["global_quantile_"] def _get_category_cols(self, X: pd.DataFrame) -> List[str]: """自动识别需要编码的列。 QuantileEncoder支持数值型和类别型列,因此返回所有列。 :param X: 输入数据 :return: 列名列表 """ return X.columns.tolist() def __init__( self, cols: Optional[List[str]] = None, quantile: float = 0.5, smoothing: float = 1.0, m: float = 1.0, handle_unknown: str = 'value', handle_missing: str = 'value', drop_invariant: bool = False, return_df: bool = True, target: Optional[str] = None, n_jobs: Optional[Union[int, float]] = -1, parallel_backend: Optional[str] = None, parallel_config: Optional[Dict[str, Any]] = None, ): """初始化分位数编码器。 :param cols: 需要编码的列名列表 :param quantile: 分位数,范围[0, 1],默认为0.5(中位数) :param smoothing: 平滑参数,默认为1.0 :param m: 先验权重参数,默认为1.0 :param handle_unknown: 处理未知类别的方式,默认为'value' :param handle_missing: 处理缺失值的方式,默认为'value' :param drop_invariant: 是否删除方差为0的列,默认为False :param return_df: 是否返回DataFrame,默认为True :param target: scorecardpipeline风格的目标列名。如果提供,fit时从X中提取该列作为y """ super().__init__( cols=cols, drop_invariant=drop_invariant, return_df=return_df, handle_unknown=handle_unknown, handle_missing=handle_missing, target=target, n_jobs=n_jobs, parallel_backend=parallel_backend, parallel_config=parallel_config, ) self.quantile = quantile self.smoothing = smoothing self.m = m self.global_quantile_: float = 0.0 def _fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None): """拟合分位数编码器。 :param X: 输入数据,shape (n_samples, n_features) :param y: 目标变量 :raises ValueError: 当y为空时抛出 """ if y is None: raise ValueError("QuantileEncoder是有监督编码器,必须提供目标变量y") y = pd.Series(y) global_quantile = y.quantile(self.quantile) self._fit_columns(X, y, shared_state={"global_quantile_": global_quantile}) self.global_quantile_ = global_quantile def _fit_column(self, column, values, y=None): mapping = {} for category in values.dropna().unique(): mask = values == category category_y = y[mask] category_quantile = category_y.quantile(self.quantile) n = len(category_y) smoothed_quantile = ( n * category_quantile + self.m * self.global_quantile_ ) / (n + self.m) mapping[category] = smoothed_quantile if self.handle_missing == 'value': mapping[np.nan] = self.global_quantile_ elif self.handle_missing == 'return_nan': mapping[np.nan] = np.nan if self.handle_unknown == 'value': mapping['__UNKNOWN__'] = self.global_quantile_ elif self.handle_unknown == 'return_nan': mapping['__UNKNOWN__'] = np.nan return {"mapping_": mapping} def _transform(self, X: pd.DataFrame, y: Optional[pd.Series] = None) -> pd.DataFrame: """转换数据。 :param X: 输入数据,shape (n_samples, n_features) :param y: 目标变量(可选) :return: 编码后的数据 """ return self._transform_columns(X, y) def _transform_column(self, column, values, y=None, context=None): mapping = self.mapping_[column] result = values.map(mapping) if self.handle_unknown == 'value': result = result.fillna(self.global_quantile_) elif self.handle_unknown == 'error' and result.isna().any(): raise ValueError(f"列'{column}'包含未知类别") return result