"""单一值筛选器.
移除单一值(众数)占比过高的特征。
**参考样例**
>>> from hscredit.core.selectors import ModeSelector
>>> import pandas as pd
>>> X = pd.DataFrame({
... 'a': [1, 1, 1, 1, 2], # 众数(1)占比80%
... 'b': [1, 2, 3, 4, 5], # 众数(1)占比20%
... 'c': [1, 1, 1, 1, 1] # 常量特征,众数占比100%
... })
>>> selector = ModeSelector(threshold=0.8) # 移除众数占比>80%的特征
>>> selector.fit(X)
>>> print(selector.selected_features_)
['a', 'b']
"""
from typing import Union, List, Optional, Dict, Any
import numpy as np
import pandas as pd
from .base import BaseFeatureSelector
def _compute_mode_ratio(series: pd.Series, dropna: bool = True) -> float:
"""计算众数占比。
:param series: 输入序列
:param dropna: 是否排除缺失值
:return: 众数占比
"""
if len(series) == 0:
return 1.0
summary = series.value_counts(dropna=dropna)
if len(summary) == 0:
return 1.0
denominator = int(series.notna().sum()) if dropna else len(series)
if denominator == 0:
return 1.0
return summary.iloc[0] / denominator
def _compute_mode_feature(task):
"""计算单列众数占比并携带特征名返回。"""
feature, series, dropna = task
return feature, _compute_mode_ratio(series, dropna)
[文档]
class ModeSelector(BaseFeatureSelector):
"""单一值筛选器.
移除众数占比高于阈值的特征。
用于过滤掉取值过于集中、区分度低的特征。
**参数**
:param threshold: 单一值占比阈值,默认为0.95
- 0.95: 移除单一值占比超过95%的特征
- 范围: 0-1之间的浮点数
:param dropna: 是否在计算众数占比时排除NaN,默认为True
:param n_jobs: 并行计算的任务数
**参考样例**
::
>>> from hscredit.core.selectors import ModeSelector
>>> import pandas as pd
>>> X = pd.DataFrame({
... 'a': [1, 1, 1, 1, 2],
... 'b': [1, 2, 3, 4, 5],
... 'c': [1, 1, 1, 1, 1]
... })
>>> selector = ModeSelector(threshold=0.8)
>>> selector.fit(X)
>>> print(selector.selected_features_)
['a', 'b']
"""
method_name = "单一值筛选"
def __init__(
self,
threshold: float = 0.95,
dropna: bool = True,
target: str = "target",
include: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
force_drop: Optional[List[str]] = None,
n_jobs: Optional[Union[int, float]] = -1,
binner: Optional[Any] = None,
binning_params: Optional[Dict[str, Any]] = None,
parallel_backend: Optional[str] = None,
parallel_config: Optional[Dict[str, Any]] = None,
):
super().__init__(
target=target,
threshold=threshold,
include=include,
exclude=exclude,
force_drop=force_drop,
n_jobs=n_jobs,
binner=binner,
binning_params=binning_params,
parallel_backend=parallel_backend,
parallel_config=parallel_config,
)
self.dropna = dropna
def _fit_impl(
self,
X: pd.DataFrame,
y: Optional[Union[pd.Series, np.ndarray]],
) -> None:
"""拟合单一值筛选器。
:param X: 输入特征DataFrame
:param y: 目标变量(此筛选器不需要)
"""
self._get_feature_names(X)
self._validate_parallel_configuration()
mode_ratios = X.apply(_compute_mode_ratio, dropna=self.dropna).reindex(X.columns)
self.scores_ = mode_ratios
# 选择众数占比低于阈值的特征
selected_mask = mode_ratios < self.threshold
self.selected_features_ = X.columns[selected_mask].tolist()
self._drop_reason = f"单一值占比 >= {self.threshold:.2%}"