查看全课大纲(第 22 / 35 节)

过滤式特征选择方法实操

约 7 分钟

小象实战讲义 · 特征工程精讲与实践

在上一节中,我们系统学习了特征选择的基本概念、意义和主要方法分类。本节我们将聚焦于过滤式特征选择方法,通过代码实操演示如何应用方差筛选、相关系数、互信息、卡方检验和单特征评估等多种过滤技术,从原始特征集中筛选出对模型预测最有价值的特征子集。

💡 核心导读

  • 过滤法的核心思想:独立评估每个特征与目标变量的相关性或区分能力,按评分排序选择Top特征。
  • 常用方法实操:方差筛选剔除低方差特征;相关系数法评估各特征与目标变量的相关程度;互信息衡量特征与目标的信息共享程度;卡方检验适用于分类问题的特征筛选。
  • 单特征评估策略:基于每个特征单独建模的预测性能(如AUC、MSE)进行排序选择。
  • 工程实现要点:掌握阈值设定、百分比选择等参数配置,理解不同方法的应用场景与局限性。

过滤法基本原理回顾

过滤式特征选择(Filter Methods)的核心思想是独立评估每个特征与目标变量之间的相关性或区分能力,然后根据评分排序,选择排名靠前的特征。这种方法的特点是:

  1. 计算高效:评估过程独立于后续的机器学习模型,通常只涉及统计计算。
  2. 通用性强:筛选出的特征子集可以用于任何模型。
  3. 局限性:未考虑特征之间的相互作用,可能遗漏那些单独不重要但组合起来有效的特征。

常用的过滤式方法包括:

  • 方差筛选:剔除方差接近0的特征(常量或准常量特征)
  • 相关系数法:基于皮尔逊相关系数筛选特征
  • 互信息:衡量特征与目标变量之间的信息共享量
  • 卡方检验:适用于分类问题,检验特征与类别之间的独立性
  • 单特征评估:基于每个特征单独建模的预测性能进行排序

数据准备与工具库引入

首先,我们引入必要的工具库并准备演示数据。本节我们将使用乳腺癌数据集作为示例,该数据集包含30个特征和1个二分类目标变量。

import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

# 加载乳腺癌数据集
data = load_breast_cancer()
# 转换为DataFrame格式
df = pd.DataFrame(
    np.c_[data['data'], data['target']],
    columns=list(data['feature_names']) + ['target']
)

# 查看数据基本信息
print("数据集形状:", df.shape)
print("\n前5行数据:")
print(df.head())
print("\n特征名称:")
print(list(data['feature_names'])[:10], "...")  # 只显示前10个特征名
数据集形状: (569, 31)

前5行数据:
   mean radius  mean texture  ...  worst fractal dimension  target
0        17.99         10.38  ...                  0.11890     0.0
1        20.57         17.77  ...                  0.08902     0.0
2        19.69         21.25  ...                  0.08758     0.0
3        11.42         20.38  ...                  0.17300     0.0
4        20.29         14.34  ...                  0.07678     0.0

[5 rows x 31 columns]

特征名称:
[np.str_('mean radius'), np.str_('mean texture'), np.str_('mean perimeter'), np.str_('mean area'), np.str_('mean smoothness'), np.str_('mean compactness'), np.str_('mean concavity'), np.str_('mean concave points'), np.str_('mean symmetry'), np.str_('mean fractal dimension')] ...

过滤式特征选择方法实操

1. 方差筛选

方差筛选是最简单的过滤方法,其基本思想是:如果一个特征的方差很小(接近0),说明该特征在不同样本间的取值几乎没有变化,对区分样本没有帮助,可以剔除。

import pandas as pd
# 方差筛选函数实现
def constant_feature_detect(data, threshold=0.98):
    """
    检测常量或准常量特征
    
    参数:
    data: pd.DataFrame, 输入数据
    threshold: float, 判定为准常量的阈值(最大取值占比)
    
    返回:
    list, 准常量特征列表
    """
    quasi_constant_features = []
    
    for feature in data.columns:
        # 计算该特征最主要取值的占比
        value_counts = data[feature].value_counts(normalize=True)
        predominant_ratio = value_counts.iloc[0]  # 最大占比
        
        if predominant_ratio >= threshold:
            quasi_constant_features.append(feature)
    
    return quasi_constant_features

# 准备数据(使用硬编码的小样本演示)
demo_data = pd.DataFrame({
    'feature1': [1, 1, 1, 1, 1, 1, 1, 1, 1, 2],  # 90%为1,10%为2
    'feature2': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],  # 方差较大
    'feature3': [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],   # 完全常量
    'target': [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
})

# 应用方差筛选(阈值设为0.9)
quasi_constant = constant_feature_detect(demo_data.drop('target', axis=1), threshold=0.9)
print("准常量特征:", quasi_constant)
print("特征'feature1'的值分布:")
print(demo_data['feature1'].value_counts(normalize=True))
准常量特征: ['feature1', 'feature3']
特征'feature1'的值分布:
feature1
1    0.9
2    0.1
Name: proportion, dtype: float64

2. 相关系数法

相关系数法通过计算特征与目标变量之间的相关性来筛选特征。对于数值型特征和数值型目标,常用皮尔逊相关系数;对于数值型特征和分类型目标,可以使用方差分析(ANOVA)或互信息。

import pandas as pd
import numpy as np
# 相关系数筛选函数实现
def corr_feature_detect(data, target, threshold=0.3):
    """
    基于相关系数筛选特征
    
    参数:
    data: pd.DataFrame, 特征数据
    target: pd.Series, 目标变量
    threshold: float, 相关系数绝对值阈值
    
    返回:
    list, 筛选出的特征列表
    """
    # 计算每个特征与目标的相关性
    correlations = {}
    for feature in data.columns:
        corr = np.corrcoef(data[feature], target)[0, 1]
        correlations[feature] = abs(corr)  # 取绝对值
    
    # 按相关性排序
    sorted_corr = sorted(correlations.items(), key=lambda x: x[1], reverse=True)
    
    # 筛选超过阈值的特征
    selected_features = [feat for feat, corr in sorted_corr if corr >= threshold]
    
    return selected_features, sorted_corr

# 准备演示数据
np.random.seed(42)
demo_data2 = pd.DataFrame({
    'high_corr': np.arange(100) + np.random.normal(0, 2, 100),  # 与目标高相关
    'low_corr': np.random.normal(0, 1, 100),                    # 与目标低相关
    'medium_corr': np.arange(100) * 0.3 + np.random.normal(0, 5, 100),  # 中等相关
    'target': np.arange(100) + np.random.normal(0, 3, 100)
})

# 应用相关系数筛选
selected, all_corr = corr_feature_detect(
    demo_data2.drop('target', axis=1), 
    demo_data2['target'], 
    threshold=0.5
)

print("筛选出的特征(相关系数≥0.5):", selected)
print("\n所有特征与目标的相关性(绝对值):")
for feat, corr in all_corr:
    print(f"{feat}: {corr:.4f}")
筛选出的特征(相关系数≥0.5): ['high_corr', 'medium_corr']

所有特征与目标的相关性(绝对值):
high_corr: 0.9929
medium_corr: 0.8322
low_corr: 0.0046

3. 互信息与卡方检验

互信息(Mutual Information)衡量的是特征与目标变量之间的信息共享量,适用于各种数据类型(数值型、分类型)。卡方检验(Chi-square Test)专门用于分类型特征与分类型目标之间的独立性检验。

import pandas as pd
from sklearn.feature_selection import mutual_info_classif, chi2, SelectKBest

# 准备分类问题演示数据
demo_data3 = pd.DataFrame({
    'feature_A': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C'],
    'feature_B': ['X', 'X', 'Y', 'Y', 'Y', 'Z', 'Z', 'Z', 'Z', 'Z'],
    'feature_C': ['P', 'Q', 'P', 'Q', 'P', 'Q', 'P', 'Q', 'P', 'Q'],
    'target': [0, 0, 1, 1, 0, 1, 1, 1, 0, 1]
})

# 将分类特征转换为数值(使用简单映射)
feature_map = {'A': 0, 'B': 1, 'C': 2, 'X': 0, 'Y': 1, 'Z': 2, 'P': 0, 'Q': 1}
X_demo = demo_data3[['feature_A', 'feature_B', 'feature_C']].replace(feature_map)
y_demo = demo_data3['target']

# 互信息计算
mi_scores = mutual_info_classif(X_demo, y_demo, random_state=42)
mi_df = pd.DataFrame({
    'feature': X_demo.columns,
    'mutual_info': mi_scores
}).sort_values('mutual_info', ascending=False)

print("互信息得分:")
print(mi_df)

# 卡方检验
chi_scores, p_values = chi2(X_demo, y_demo)
chi_df = pd.DataFrame({
    'feature': X_demo.columns,
    'chi2_score': chi_scores,
    'p_value': p_values
}).sort_values('chi2_score', ascending=False)

print("\n卡方检验结果:")
print(chi_df)

# 使用SelectKBest选择Top2特征(基于互信息;固定random_state保证可复现且与上面的得分一致)
from functools import partial
score_fn = partial(mutual_info_classif, random_state=42)
selector = SelectKBest(score_fn, k=2)
selector.fit(X_demo, y_demo)
selected_features = X_demo.columns[selector.get_support()].tolist()
print(f"\n基于互信息选择的Top2特征: {selected_features}")
互信息得分:
     feature  mutual_info
1  feature_B     0.771825
2  feature_C     0.302659
0  feature_A     0.000000

卡方检验结果:
     feature  chi2_score   p_value
1  feature_B    1.551282  0.212946
2  feature_C    0.833333  0.361310
0  feature_A    0.742424  0.388885

基于互信息选择的Top2特征: ['feature_B', 'feature_C']

4. 单特征评估法

单特征评估法为每个特征单独构建一个简单的预测模型(如决策树),根据模型在验证集上的性能(如AUC、MSE)对特征进行排序。

import numpy as np
from sklearn.model_selection import train_test_split
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score

def univariate_roc_auc_evaluation(X_train, y_train, X_test, y_test, threshold=0.6):
    """
    基于单特征ROC-AUC评估特征重要性
    
    参数:
    X_train, X_test: 训练/测试特征
    y_train, y_test: 训练/测试目标
    threshold: AUC阈值,高于此值的特征被保留
    
    返回:
    pd.Series, 特征AUC得分
    """
    auc_scores = {}
    
    for feature in X_train.columns:
        # 使用单个特征训练决策树
        clf = DecisionTreeClassifier(max_depth=3, random_state=42)
        clf.fit(X_train[[feature]], y_train)
        
        # 预测概率
        if len(np.unique(y_test)) > 2:
            # 多分类情况
            y_pred = clf.predict_proba(X_test[[feature]])
            auc = roc_auc_score(y_test, y_pred, multi_class='ovr')
        else:
            # 二分类情况
            y_pred = clf.predict_proba(X_test[[feature]])[:, 1]
            auc = roc_auc_score(y_test, y_pred)
        
        auc_scores[feature] = auc
    
    # 转换为Series并排序
    auc_series = pd.Series(auc_scores).sort_values(ascending=False)
    
    # 筛选高于阈值的特征
    selected = auc_series[auc_series > threshold]
    
    print(f"AUC阈值: {threshold}")
    print(f"保留特征数: {len(selected)}/{len(auc_series)}")
    
    return auc_series, selected

# 准备演示数据(使用乳腺癌数据集的小样本)
from sklearn.datasets import load_breast_cancer
data_small = load_breast_cancer()
X_small = pd.DataFrame(data_small.data[:, :5],  # 只取前5个特征演示
                      columns=data_small.feature_names[:5])
y_small = data_small.target

# 划分训练测试集
X_train_small, X_test_small, y_train_small, y_test_small = train_test_split(
    X_small, y_small, test_size=0.3, random_state=42
)

# 应用单特征AUC评估
all_auc_scores, selected_features = univariate_roc_auc_evaluation(
    X_train_small, y_train_small, 
    X_test_small, y_test_small,
    threshold=0.85
)

print("\n所有特征的AUC得分:")
print(all_auc_scores)
print("\n筛选出的特征:")
print(selected_features)
AUC阈值: 0.85
保留特征数: 3/5

所有特征的AUC得分:
mean perimeter     0.971781
mean radius        0.961567
mean area          0.945988
mean texture       0.741623
mean smoothness    0.684965
dtype: float64

筛选出的特征:
mean perimeter    0.971781
mean radius       0.961567
mean area         0.945988
dtype: float64

📝 动手练一练

  1. 方法对比分析:在单特征评估法中,我们使用了决策树和AUC指标。如果改用线性回归和MSE(均方误差)作为评估指标,应该如何修改代码?请写出修改后的核心代码片段。
👉 点击查看参考答案
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

def univariate_mse_evaluation(X_train, y_train, X_test, y_test, threshold=0.5):
    """
    基于单特征MSE评估特征重要性(适用于回归问题)
    """
    mse_scores = {}
    
    for feature in X_train.columns:
        # 使用单个特征训练线性回归
        model = LinearRegression()
        model.fit(X_train[[feature]], y_train)
        
        # 预测并计算MSE
        y_pred = model.predict(X_test[[feature]])
        mse = mean_squared_error(y_test, y_pred)
        
        # 注意:MSE越小越好,我们可以取倒数或负值来排序
        mse_scores[feature] = -mse  # 取负值,这样值越大表示特征越好
    
    # 转换为Series并排序
    mse_series = pd.Series(mse_scores).sort_values(ascending=False)
    
    # 筛选(注意阈值需要相应调整)
    selected = mse_series[mse_series > -threshold]  # 假设threshold是MSE的阈值
    
    return mse_series, selected

关键修改点:

  1. 将分类器 DecisionTreeClassifier 改为回归器 LinearRegression
  2. 将评估指标 roc_auc_score 改为 mean_squared_error
  3. 注意MSE是越小越好,而AUC是越大越好,因此排序方向需要调整
  4. 阈值设定逻辑需要相应调整
  1. 阈值选择思考:在方差筛选中,我们使用了0.9作为准常量特征的阈值。如果某个特征有90%的样本取值为A,10%取值为B,这个特征应该被剔除吗?请结合具体业务场景分析。
👉 点击查看参考答案

是否剔除取决于具体业务场景:

应该保留的情况:

  • 如果这10%的异常值(B取值)对应着重要的业务事件(如欺诈交易、设备故障等)
  • 如果特征与目标变量有强相关性,即使分布不均衡
  • 在风控、异常检测等场景中,少数类可能正是我们关注的重点

可以考虑剔除的情况:

  • 如果这10%的取值是数据采集错误或噪声
  • 在需要简化模型、减少过拟合风险的场景
  • 当特征维度非常高,需要大幅降维时

实践建议:

  • 不要盲目使用固定阈值,可以先观察剔除前后模型性能的变化
  • 对于重要业务场景,即使特征分布不均衡,也应先尝试纳入模型
  • 可以结合领域知识判断特征的业务意义

在实际工作中,通常建议:

  1. 先使用较高的阈值(如0.99)进行初步筛选
  2. 观察被剔除特征的业务含义
  3. 通过交叉验证比较剔除前后的模型性能
  4. 最终根据业务目标和模型表现做出决策

本章小结

本节我们深入学习了过滤式特征选择的多种方法及其代码实现。过滤法作为特征选择的第一道关卡,以其计算高效、实现简单的特点,在实际工程中有着广泛的应用。

📋 行动清单

学完本节内容,你可以立即实践:

  • 尝试方差筛选:在自己的数据集上应用方差筛选,观察有多少准常量特征,思考这些特征为何方差小
  • 对比不同相关性指标:对同一份数据分别计算皮尔逊相关系数和互信息,比较两种方法筛选出的特征有何异同
  • 实现单特征评估流水线:编写一个完整的函数,能够自动应用多种过滤方法并汇总结果,为后续的包裹法和嵌入法打下基础

过滤式方法虽然简单有效,但也有其局限性——它只考虑特征与目标的单独关系,忽略了特征间的相互作用。在下一节中,我们将学习更复杂的包裹式特征选择方法,它通过考虑特征组合来寻找最优特征子集。

—— 小象教研组