相关性筛选 ,指的是通过计算特征之间(或特征与标签之间)的相关性 ,筛选出最有用的特征 ,剔除冗余或无关的特征。
它属于你之前看的组件列表里(特征工程组件 -> Python特征工程组件 -> 相关性筛选)的核心功能。下面我从概念、常见方法、代码实现、典型场景几个方面来讲。
一、为什么需要相关性筛选?
现实数据中,特征往往存在以下问题:
-
特征之间高度相关(冗余):比如"身高(cm)"和"身高(m)",信息几乎重复。
-
特征与标签无关:比如"用户ID"对"是否流失"没有预测力。
-
维度太高:几百上千个特征,训练慢、容易过拟合。
相关性筛选的目的:
-
剔除冗余特征,减少维度。
-
保留与标签最相关的特征。
-
提升模型效果和训练速度。
二、相关性的两种类型
| 类型 | 衡量什么 | 用途 |
|---|---|---|
| 特征-特征相关性 | 特征之间的冗余程度 | 剔除冗余特征 |
| 特征-标签相关性 | 特征对标签的预测能力 | 筛选有用特征 |
三、常见的相关性度量方法
| 方法 | 适用数据类型 | 取值范围 | 特点 |
|---|---|---|---|
| 皮尔逊相关系数 | 连续 vs 连续 | -1, 1 | 衡量线性关系 |
| 斯皮尔曼相关系数 | 连续/有序 | -1, 1 | 衡量单调关系,抗异常值 |
| 肯德尔相关系数 | 有序 | -1, 1 | 衡量一致性 |
| 卡方检验 | 类别 vs 类别 | p值 | 衡量独立性 |
| 互信息 | 任意 vs 任意 | [0, ∞) | 衡量任意关系 |
| 方差膨胀因子(VIF) | 连续 vs 连续 | [1, ∞) | 衡量多重共线性 |
| 距离相关系数 | 任意 vs 任意 | 0, 1 | 衡量任意关系 |
四、代码实现
1. 皮尔逊相关系数(pandas .corr())
import pandas as pd
import numpy as np
# 构造数据
np.random.seed(42)
df = pd.DataFrame({
'height_cm': [170, 175, 180, 165, 185],
'height_m': [1.70, 1.75, 1.80, 1.65, 1.85], # 与 height_cm 高度相关
'weight': [65, 70, 75, 60, 80],
'age': [25, 30, 35, 40, 45],
'label': [0, 1, 1, 0, 1]
})
# 计算相关系数矩阵
corr_matrix = df.corr()
print(corr_matrix)
输出:
height_cm height_m weight age label
height_cm 1.000000 1.000000 0.998... 1.000... 0.316...
height_m 1.000000 1.000000 0.998... 1.000... 0.316...
weight 0.998... 0.998... 1.000000 0.998... 0.316...
age 1.000... 1.000... 0.998... 1.000000 0.316...
label 0.316... 0.316... 0.316... 0.316... 1.000000
2. 剔除高度相关的特征(特征-特征)
# 找出相关性大于 0.95 的特征对
threshold = 0.95
corr_matrix_abs = corr_matrix.abs()
# 只看上三角(避免重复)
upper = corr_matrix_abs.where(np.triu(np.ones(corr_matrix_abs.shape), k=1).astype(bool))
# 找出需要删除的列
to_drop = [column for column in upper.columns if any(upper[column] > threshold)]
print("需要删除的列:", to_drop)
# 删除
df_reduced = df.drop(columns=to_drop)
print(df_reduced)
输出:
需要删除的列: ['height_m', 'weight', 'age']
height_cm label
0 170 0
1 175 1
2 180 1
3 165 0
4 185 1
3. 特征-标签相关性筛选
# 计算每个特征与 label 的相关性
corr_with_label = df.corr()['label'].drop('label')
print(corr_with_label)
# 筛选相关性绝对值大于 0.1 的特征
selected = corr_with_label[corr_with_label.abs() > 0.1].index.tolist()
print("筛选后的特征:", selected)
4. 斯皮尔曼相关系数
# method='spearman' 或 'kendall'
corr_spearman = df.corr(method='spearman')
print(corr_spearman)
5. 卡方检验(类别 vs 类别)
from sklearn.feature_selection import chi2
from sklearn.preprocessing import LabelEncoder
# 构造类别数据
X = np.array([
['男', '北京'],
['女', '上海'],
['男', '北京'],
['女', '广州'],
['男', '上海']
])
y = np.array([0, 1, 0, 1, 1])
# 先做 OneHot 或 Label 编码
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False)
X_encoded = encoder.fit_transform(X)
# 卡方检验
chi2_scores, p_values = chi2(X_encoded, y)
print("卡方值:", chi2_scores)
print("p值:", p_values)
6. 互信息(mutual_info_classif / mutual_info_regression)
from sklearn.feature_selection import mutual_info_classif, mutual_info_regression
# 分类问题
mi_scores = mutual_info_classif(X_encoded, y, random_state=42)
print("互信息(分类):", mi_scores)
# 回归问题
# mi_scores = mutual_info_regression(X_encoded, y)
互信息能捕捉非线性关系,比皮尔逊更通用。
7. 方差膨胀因子(VIF)------ 检测多重共线性
from statsmodels.stats.outliers_influence import variance_inflation_factor
# VIF 需要数值型数据
X_vif = df[['height_cm', 'weight', 'age']].values
vif_data = pd.DataFrame()
vif_data['feature'] = ['height_cm', 'weight', 'age']
vif_data['VIF'] = [variance_inflation_factor(X_vif, i) for i in range(X_vif.shape[1])]
print(vif_data)
输出示例:
feature VIF
0 height_cm 1234.56
1 weight 987.65
2 age 2345.67
VIF 判断标准:
-
VIF < 5:无多重共线性
-
5 ≤ VIF < 10:中度共线性
-
VIF ≥ 10:严重共线性,建议删除
8. sklearn 的 SelectKBest + 相关性
from sklearn.feature_selection import SelectKBest, f_classif
# 用 F 检验选前 k 个特征
selector = SelectKBest(score_func=f_classif, k=2)
X_selected = selector.fit_transform(X_encoded, y)
print("选中的特征索引:", selector.get_support(indices=True))
print("特征得分:", selector.scores_)
9. VarianceThreshold ------ 剔除低方差特征
from sklearn.feature_selection import VarianceThreshold
# 剔除方差小于 0.1 的特征
selector = VarianceThreshold(threshold=0.1)
X_selected = selector.fit_transform(X_encoded)
print("选中的特征索引:", selector.get_support(indices=True))
10. 在 Pipeline 中使用
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_encoded, y, test_size=0.3, random_state=42)
pipe = Pipeline([
('scaler', StandardScaler()),
('select', SelectKBest(score_func=f_classif, k=2)), # 选前2个特征
('clf', LogisticRegression())
])
pipe.fit(X_train, y_train)
print("测试集准确率:", pipe.score(X_test, y_test))
五、相关性筛选的常用阈值
| 场景 | 阈值建议 |
|---|---|
| 特征-特征相关性(剔除冗余) | 0.9 或 0.95 |
| 特征-标签相关性(保留有用) | 0.1 或 0.05(p值) |
| VIF(多重共线性) | 5 或 10 |
| 互信息 | 看排序,选前 k 个 |
| 方差阈值 | 0.01 或 0.1 |
注意:阈值不是绝对的,要结合业务和数据特点调整。
六、典型应用场景
| 场景 | 方法 |
|---|---|
| 剔除冗余特征 | 特征-特征相关性 > 0.95 |
| 筛选有用特征 | 特征-标签相关性 |
| 检测多重共线性 | VIF |
| 类别特征筛选 | 卡方检验、互信息 |
| 非线性关系筛选 | 互信息、距离相关 |
| 高维数据降维 | SelectKBest + 相关性 |
| 回归问题 | mutual_info_regression、f_regression |
七、相关性筛选的优缺点
优点
-
简单快速:计算成本低,容易理解。
-
减少维度:剔除冗余特征,加快训练。
-
提升可解释性:保留的特征更有业务意义。
-
减少过拟合:维度降低,模型更简单。
缺点
-
只看线性关系:皮尔逊只能捕捉线性关系,非线性要用互信息。
-
忽略特征组合:两个特征单独与标签不相关,组合起来可能很相关。
-
可能误删:相关性高不代表冗余,有时两个特征都重要。
-
阈值难定:0.95 还是 0.9?需要实验。
-
不适用于所有模型:树模型对冗余特征不敏感,筛选收益有限。
八、关键提醒
-
相关性 ≠ 因果性:两个特征相关,不代表一个导致另一个。
-
筛选参数从训练集学:相关性计算、阈值选择,只用训练集,避免数据泄露。
-
结合业务判断:统计上相关,业务上可能无意义,要综合判断。
-
互信息能捕捉非线性:线性关系用皮尔逊,非线性用互信息。
-
特征组合可能有用:两个单独不相关的特征,组合起来可能很强,筛选时要注意。
-
树模型不需要:XGBoost、LightGBM 对冗余特征不敏感,筛选收益有限。
-
筛选后要重新评估:筛选可能丢信息,要检查模型效果是否真的提升。
九、相关性筛选 vs 其他特征选择方法
| 方法 | 类型 | 特点 |
|---|---|---|
| 相关性筛选 | Filter(过滤式) | 快,独立于模型 |
| 卡方检验 | Filter | 类别特征 |
| 互信息 | Filter | 任意关系 |
| RFE(递归特征消除) | Wrapper(包裹式) | 用模型选,慢但效果好 |
| Lasso / L1 正则 | Embedded(嵌入式) | 模型自带特征选择 |
| 树模型特征重要性 | Embedded | 随机森林、XGBoost 自带 |
十、总结
相关性筛选 = 通过计算特征之间或特征与标签之间的相关性,筛选出最有用的特征。
-
两种类型:特征-特征相关性(剔冗余)、特征-标签相关性(选有用)。
-
常用方法:皮尔逊、斯皮尔曼、卡方、互信息、VIF。
-
sklearn 实现 :
SelectKBest、VarianceThreshold、mutual_info_classif。 -
核心价值:降维、去冗余、提升模型效果和可解释性。
-
主要代价:只看线性、忽略组合、可能误删。
-
关键原则:参数从训练集学;结合业务判断;树模型收益有限。