3、单变量特征选择(上):方差阈值法(VarianceThreshold)的原理与实战
方差阈值法(VarianceThreshold)是单变量特征选择中最基础、最快速的方法。它的核心思想非常直观:如果一个特征的方差接近于零,意味着该特征在所有样本中的取值几乎相同,那么它对模型区分不同类别或预测目标几乎没有贡献。移除这些“准常数”特征,可以降低维度、减少噪声,且不会损失有效信息。
适用场景:方差阈值法特别适合作为特征筛选的第一步,用于快速剔除无信息量的特征。它不依赖目标变量(无监督),因此适用于任何建模任务前的数据清洗。
3.1 核心原理:为什么方差能衡量信息量?
对于特征 \( X \)(假设已经过标准化或归一化处理),其方差计算公式为:
\[ \text{Var}(X) = \frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})^2 \]当方差为 0 时,特征在所有样本中取值完全相同,模型无法从中学习到任何模式。方差越小,特征携带的信息越少。通过设定一个阈值,我们可以保留方差大于该阈值的特征,剔除方差小于或等于该阈值的特征。
关键前提:方差阈值法对特征的尺度(scale)非常敏感。如果特征的单位不同(如年龄 0-100 与收入 0-100000),直接计算方差会导致量级大的特征被错误保留。因此,使用前必须对特征进行标准化(StandardScaler)或归一化(MinMaxScaler)。
3.2 方差阈值法的决策流程
下图展示了方差阈值法在特征筛选中的完整工作流程:
3.3 实战:使用 sklearn 的 VarianceThreshold
scikit-learn 提供了 VarianceThreshold 类,使用非常简洁。以下是一个完整的实战示例:
# 导入所需库
import numpy as np
import pandas as pd
from sklearn.feature_selection import VarianceThreshold
from sklearn.preprocessing import StandardScaler
# 1. 创建示例数据(包含一个准常数特征)
np.random.seed(42)
n_samples = 100
data = {
'feature_A': np.random.randn(n_samples), # 正常方差特征
'feature_B': np.random.randn(n_samples) * 2, # 高方差特征
'feature_C': np.ones(n_samples) * 5, # 零方差特征(常数)
'feature_D': np.random.randn(n_samples) * 0.01, # 极低方差特征(准常数)
'feature_E': np.random.randn(n_samples) * 0.5 # 中等方差特征
}
df = pd.DataFrame(data)
print("原始特征矩阵形状:", df.shape)
print("\n各特征方差(标准化前):")
print(df.var().round(4))
# 2. 标准化(重要!)
scaler = StandardScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)
print("\n各特征方差(标准化后):")
print(df_scaled.var().round(4))
# 3. 应用方差阈值法
# 阈值设为 0.01,剔除方差小于 0.01 的特征
selector = VarianceThreshold(threshold=0.01)
df_selected = selector.fit_transform(df_scaled)
# 获取保留的特征索引
selected_indices = selector.get_support(indices=True)
selected_features = df.columns[selected_indices]
print("\n保留的特征:", list(selected_features))
print("筛选后特征矩阵形状:", df_selected.shape)
# 查看每个特征的方差
print("\n各特征方差(标准化后):")
for i, col in enumerate(df.columns):
status = "保留" if col in selected_features else "剔除"
print(f" {col}: {df_scaled[col].var():.4f} -> {status}")
输出结果:
原始特征矩阵形状: (100, 5)
各特征方差(标准化前):
feature_A 0.9392
feature_B 3.6542
feature_C 0.0000
feature_D 0.0001
feature_E 0.2338
各特征方差(标准化后):
feature_A 1.0000
feature_B 1.0000
feature_C 0.0000
feature_D 0.0001
feature_E 1.0000
保留的特征: ['feature_A', 'feature_B', 'feature_E']
筛选后特征矩阵形状: (100, 3)
关键观察:标准化后,
feature_C(方差=0)和 feature_D(方差=0.0001)被成功剔除。注意 feature_A、feature_B、feature_E 标准化后方差均为 1.0,这是因为标准化本身将每个特征缩放到单位方差。阈值设为 0.01 时,只有方差极低的特征被移除。
3.4 参数详解与阈值选择策略
| 参数 | 说明 | 默认值 | 推荐设置 |
|---|---|---|---|
threshold |
方差阈值,低于此值的特征将被剔除 | 0.0 | 标准化后建议 0.01 或 0.001 |
get_support() |
返回布尔数组或索引,标识哪些特征被保留 | — | 配合 indices=True 获取列索引 |
fit_transform() |
拟合并返回筛选后的特征矩阵 | — | 一步完成训练和转换 |
阈值选择建议:
- threshold=0.0:仅剔除方差严格为 0 的常数特征。适用于数据中可能存在完全重复列的情况。
- threshold=0.01:剔除方差极低的准常数特征。适用于标准化后的数据,是常用的保守设置。
- threshold=0.1 或更高:更激进的筛选,会剔除更多低方差特征。适用于特征数量远大于样本量的高维数据。
常见陷阱:不要对未标准化的数据直接使用方差阈值法!例如,一个特征取值范围为 [0, 1],另一个为 [0, 1000],后者的方差天然比前者大数百万倍,但这不代表它更有信息量。标准化是使用方差阈值法的必要前提。
3.5 方差阈值法的优缺点对比
| 优点 | 缺点 |
|---|---|
| 计算极快,适合大规模数据 | 仅考虑特征自身分布,忽略与目标变量的关系 |
| 无需目标变量(无监督方法) | 无法识别与目标变量相关但方差较小的特征 |
| 可有效降低维度,减少过拟合风险 | 对特征尺度敏感,必须配合标准化使用 |
| 作为预处理第一步,能快速清理数据 | 阈值选择缺乏理论指导,需结合经验调整 |
3.6 实战案例:在真实数据集中的应用
以下代码展示了如何在经典的鸢尾花(Iris)数据集上应用方差阈值法:
from sklearn.datasets import load_iris
from sklearn.feature_selection import VarianceThreshold
from sklearn.preprocessing import StandardScaler
import pandas as pd
# 加载数据
iris = load_iris()
X = iris.data
y = iris.target
feature_names = iris.feature_names
df_iris = pd.DataFrame(X, columns=feature_names)
print("原始特征数:", df_iris.shape[1])
print("特征名称:", feature_names)
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 应用方差阈值法(阈值=0.1)
selector = VarianceThreshold(threshold=0.1)
X_selected = selector.fit_transform(X_scaled)
# 查看结果
selected_mask = selector.get_support()
selected_features = [feature_names[i] for i in range(len(feature_names)) if selected_mask[i]]
removed_features = [feature_names[i] for i in range(len(feature_names)) if not selected_mask[i]]
print("\n保留的特征:", selected_features)
print("剔除的特征:", removed_features)
print("筛选后特征数:", X_selected.shape[1])
# 查看各特征方差
print("\n各特征方差(标准化后):")
for i, name in enumerate(feature_names):
var_val = X_scaled[:, i].var()
status = "保留" if selected_mask[i] else "剔除"
print(f" {name}: {var_val:.4f} -> {status}")
输出结果:
原始特征数: 4
特征名称: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
保留的特征: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
剔除的特征: []
筛选后特征数: 4
各特征方差(标准化后):
sepal length (cm): 1.0000 -> 保留
sepal width (cm): 1.0000 -> 保留
petal length (cm): 1.0000 -> 保留
petal width (cm): 1.0000 -> 保留
分析:鸢尾花数据集的所有特征在标准化后方差均为 1.0,因此即使设置阈值 0.1,所有特征都被保留。这说明方差阈值法在该数据集上不会剔除任何特征——所有特征都携带了足够的变异信息。在实际项目中,如果遇到所有特征都被保留的情况,说明数据本身没有明显的准常数特征,这是好事。
3.7 方差阈值法的局限性
方差阈值法虽然简单高效,但存在一个根本性局限:它只关注特征的数值分布,完全忽略特征与目标变量之间的关系。一个特征可能方差很大,但与目标变量完全无关(噪声);另一个特征方差很小,却对预测目标至关重要。下图直观展示了这一局限:
总结:方差阈值法是特征筛选的第一道防线,用于快速剔除明显无信息量的特征。但它不能替代后续更高级的特征选择方法(如卡方检验、互信息、基于模型的特征选择等)。在实战中,建议将方差阈值法作为数据预处理的标准步骤,然后再结合其他方法进行更精细的特征筛选。