3. 单变量特征选择:方差阈值法、卡方检验、互信息法、皮尔逊相关系数

单变量特征选择是特征工程中最基础、最直观的筛选手段。它的核心逻辑是:逐个评估每个特征与目标变量之间的关系强度,然后根据设定的阈值或保留数量,剔除那些“与目标无关”或“自身信息量过低”的特征。这种方法计算效率高、可解释性强,是建模前快速降噪的首选步骤。

本章将深入剖析四种最常用的单变量筛选方法:方差阈值法、卡方检验、互信息法和皮尔逊相关系数。我们将从原理、适用场景、代码实现到常见陷阱,逐一拆解。

3.1 方法总览与对比

在深入细节之前,先通过一张对比表格,建立对这四种方法的宏观认知。

方法 核心原理 适用目标变量 特征变量要求 输出指标 主要陷阱
方差阈值法 衡量特征自身离散程度 无监督(不依赖 y) 连续型 / 离散型 方差值 未归一化时方差不可比;对二值特征不友好
卡方检验 检验特征与目标的独立性 分类(离散) 非负计数 / 类别型 卡方统计量 + p 值 对负值敏感;小样本时 p 值不可靠
互信息法 衡量特征与目标的信息共享量 分类 / 回归 连续型 / 离散型 互信息值 连续变量需离散化;估计偏差
皮尔逊相关系数 衡量线性相关程度 回归(连续) 连续型 相关系数 r 仅捕捉线性关系;对异常值敏感
选型速记口诀:“方差筛常量,卡方筛分类,互信息抓非线性,皮尔逊抓线性。”

3.2 方差阈值法(Variance Threshold)

方差阈值法是最简单的特征筛选方法。它不依赖目标变量,仅通过计算每个特征的方差来评估其信息量。如果一个特征的方差接近零,说明该特征在所有样本中的取值几乎相同,无法提供区分度,可以直接剔除。

3.2.1 核心公式与标准化陷阱

方差计算公式:

Var(X) = (1 / (n - 1)) * Σ (xᵢ - μ)²

其中 μ 为特征均值,n 为样本量。

避坑重点:方差受特征尺度影响极大。例如,“年龄”(0-100)的方差天然远大于“是否吸烟”(0/1)的方差。直接比较未归一化的方差会错误地保留大量低信息量的数值型特征,而剔除有意义的二值特征。必须先对特征进行标准化(如 StandardScaler)或归一化(如 MinMaxScaler),再计算方差。

3.2.2 代码示例

from sklearn.feature_selection import VarianceThreshold
from sklearn.preprocessing import StandardScaler
import pandas as pd

# 假设 X 是原始特征矩阵
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 设置方差阈值为 0.01(标准化后方差接近 0 表示几乎无波动)
selector = VarianceThreshold(threshold=0.01)
X_selected = selector.fit_transform(X_scaled)

# 查看被保留的特征索引
selected_indices = selector.get_support(indices=True)
print(f"保留的特征索引: {selected_indices}")

3.3 卡方检验(Chi-Square Test)

卡方检验用于检验两个分类变量之间的独立性。在特征筛选中,我们用它来评估每个离散特征与离散目标变量之间是否存在显著关联。卡方统计量越大,p 值越小,说明特征与目标越不独立,特征越重要。

3.3.1 适用条件与限制

  • 输入要求:特征必须是非负的计数或类别型数据。对于连续特征,必须先进行分箱离散化。
  • 输出解读:通常选择卡方统计量排名前 K 的特征,或 p 值小于显著性水平(如 0.05)的特征。
  • 主要陷阱:当某个特征与目标变量的列联表中存在理论频数小于 5 的单元格时,卡方检验的近似性会变差,p 值不可靠。此时应使用 Fisher 精确检验作为替代。

3.3.2 代码示例

from sklearn.feature_selection import chi2, SelectKBest
from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# 假设 X_cat 是离散特征矩阵,y 是分类目标
# 卡方要求非负,使用 MinMaxScaler 将特征缩放到 [0,1] 区间
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X_cat)

# 选择卡方统计量最高的 10 个特征
chi2_selector = SelectKBest(chi2, k=10)
X_kbest = chi2_selector.fit_transform(X_scaled, y)

# 获取每个特征的卡方统计量和 p 值
chi2_scores, p_values = chi2(X_scaled, y)
feature_scores = pd.DataFrame({
    'feature': feature_names,
    'chi2_score': chi2_scores,
    'p_value': p_values
}).sort_values('chi2_score', ascending=False)
print(feature_scores.head(10))

3.4 互信息法(Mutual Information)

互信息法基于信息论,衡量一个特征中包含的关于目标变量的信息量。与皮尔逊相关系数不同,互信息法能够捕捉任意类型的依赖关系(包括非线性关系),因此在实际应用中非常强大。

3.4.1 核心概念与估计方法

互信息的定义:

I(X; Y) = Σ Σ p(x, y) * log(p(x, y) / (p(x) * p(y)))

对于连续变量,需要估计概率密度,常用的方法包括:

  • k-近邻估计:sklearn 中 mutual_info_classif 和 mutual_info_regression 的默认方法,通过近邻距离估计熵。
  • 核密度估计:计算复杂度较高,但精度可控。
  • 离散化:将连续变量分箱后计算离散互信息,简单但受分箱策略影响大。
实践建议:互信息值没有上限,且受样本量和估计方法影响。通常不设置绝对阈值,而是选择互信息值排名前 K 的特征,或观察互信息值的“肘部”下降点来确定保留数量。

3.4.2 代码示例

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

# 假设 X 是特征矩阵(连续或离散均可),y 是分类目标
# n_neighbors 控制估计的平滑度,默认 3
mi_scores = mutual_info_classif(X, y, random_state=42)

# 选择互信息值最高的 15 个特征
mi_selector = SelectKBest(lambda X, y: mi_scores, k=15)
X_mi_selected = mi_selector.fit_transform(X, y)

# 查看特征得分
feature_mi = pd.DataFrame({
    'feature': feature_names,
    'mutual_info': mi_scores
}).sort_values('mutual_info', ascending=False)
print(feature_mi.head(15))

3.5 皮尔逊相关系数(Pearson Correlation Coefficient)

皮尔逊相关系数是最经典的线性相关性度量,取值范围为 [-1, 1]。在特征筛选中,我们通常关注特征与目标变量之间的相关系数绝对值,绝对值越大,线性关系越强。

3.5.1 使用场景与局限性

  • 适用场景:回归问题,且特征和目标均为连续变量。
  • 输出解读:|r| > 0.5 通常认为中等以上相关;|r| < 0.1 通常认为几乎无关。
  • 主要陷阱:
    • 仅捕捉线性关系:如果特征与目标存在明显的 U 型或周期性关系,皮尔逊相关系数可能接近 0,导致错误剔除。
    • 异常值敏感:单个极端值可能大幅改变相关系数。
    • 多重共线性:皮尔逊相关系数也常用于检测特征之间的共线性,但单变量筛选本身不处理此问题。

3.5.2 代码示例

import pandas as pd
import numpy as np

# 假设 df 包含所有特征和目标列 'target'
correlations = df.corr()['target'].drop('target').abs().sort_values(ascending=False)

# 选择相关系数绝对值大于 0.1 的特征
selected_features = correlations[correlations > 0.1].index.tolist()
print(f"保留的特征数量: {len(selected_features)}")
print(f"Top 5 相关特征:\n{correlations.head(5)}")

3.6 四种方法的选择流程

在实际项目中,单变量特征选择通常不是孤立使用的,而是按照一定的流程组合应用。下面的 SVG 流程图展示了推荐的决策路径。

单变量特征选择方法决策流程 单变量特征选择方法决策流程 原始特征矩阵 是否依赖目标变量 y? 否 方差阈值法 是 进入监督筛选 目标变量类型? 分类 卡方检验 回归 皮尔逊相关系数 或 互信息法(通用) 筛选后的特征子集 ⚠️ 重要提示 互信息法适用于分类和回归两种场景,且能捕捉非线性关系。 当数据量充足时,建议优先使用互信息法作为监督筛选的通用方案。

3.7 常见陷阱与避坑清单

单变量特征选择看似简单,但在实际应用中容易踩坑。以下清单总结了最常见的错误及其解决方案。

陷阱编号 陷阱描述 后果 解决方案
陷阱 1 方差阈值法未标准化直接使用 保留大量尺度大的噪声特征,剔除有意义的二值特征 先标准化(StandardScaler),再计算方差
陷阱 2 卡方检验输入包含负值 计算报错或结果无效 使用 MinMaxScaler 缩放到 [0,1] 或进行绝对值变换
陷阱 3 互信息法对连续变量直接使用默认参数 估计偏差大,结果不稳定 调整 n_neighbors 参数(5-15),或先离散化
陷阱 4 皮尔逊相关系数用于非线性关系 漏掉强非线性相关特征 先绘制散点图观察;改用互信息法或距离相关系数
陷阱 5 单变量筛选后直接建模,忽略特征交互 丢失组合特征的信息增益 单变量筛选作为初筛,后续结合包装法或嵌入法
陷阱 6 对高基数类别特征直接使用卡方检验 自由度极高,p 值失真 对类别进行合并,或使用基于模型的特征重要性
核心原则:单变量特征选择是“快速降噪”工具,不是“终极筛选”方案。它擅长剔除明显无用的特征,但无法发现特征组合的协同效应。建议将单变量筛选作为特征工程流水线的第一道关卡,后续再结合递归特征消除(RFE)或基于模型的重要性排序进行精细筛选。

3.8 综合案例:构建单变量筛选流水线

下面展示一个完整的 Python 流水线,将四种方法组合使用,并输出筛选结果对比。

import pandas as pd
import numpy as np
from sklearn.datasets import make_classification
from sklearn.feature_selection import VarianceThreshold, chi2, SelectKBest
from sklearn.feature_selection import mutual_info_classif
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 生成模拟数据
X, y = make_classification(n_samples=1000, n_features=50, n_informative=10,
                           n_redundant=10, n_repeated=5, random_state=42)
feature_names = [f'feat_{i}' for i in range(X.shape[1])]

# 1. 方差阈值法(无监督初筛)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
var_selector = VarianceThreshold(threshold=0.01)
X_var = var_selector.fit_transform(X_scaled)
var_mask = var_selector.get_support()
print(f"方差阈值法保留特征数: {X_var.shape[1]} / {X.shape[1]}")

# 2. 卡方检验(监督筛选,需非负)
scaler_mm = MinMaxScaler()
X_mm = scaler_mm.fit_transform(X_var)  # 在初筛后的特征上操作
chi2_selector = SelectKBest(chi2, k=15)
X_chi2 = chi2_selector.fit_transform(X_mm, y)
chi2_mask = chi2_selector.get_support()
print(f"卡方检验保留特征数: {X_chi2.shape[1]}")

# 3. 互信息法(监督筛选,通用)
mi_scores = mutual_info_classif(X_var, y, random_state=42)
mi_mask = np.argsort(mi_scores)[::-1][:15]  # 取前15个
print(f"互信息法 Top 15 特征索引: {mi_mask}")

# 4. 汇总筛选结果
results_df = pd.DataFrame({
    'feature': [f for i, f in enumerate(feature_names) if var_mask[i]],
    'var_retained': True,
    'chi2_retained': chi2_mask,
    'mi_top15': [i in mi_mask for i in range(X_var.shape[1])]
})
print("\n筛选结果汇总(前10行):")
print(results_df.head(10))
流水线设计建议:先使用方差阈值法剔除常量特征(通常可减少 5%-20% 的特征量),再使用互信息法或卡方检验进行监督筛选。这种“无监督初筛 + 监督精选”的组合策略,在保证效果的同时能显著提升计算效率。