5.特征工程与维度降维实践指南——Python数据挖掘代码实践
文章目录
- 一、引言
- 二、特征构造与变量选择
- 2.1 数据聚合与透视
- 2.2 相关性分析
- 2.3 正则化方法
- 2.4 树模型与特征重要性
- 2.5 非数值数据处理
- 2.5.1 文本数据转化
- 2.5.2 类别数据编码
- 2.5.3 对比表格:文本数据转化与类别数据编码方法
- 三、 维度降维技术
- 3.1 降维方法对比表
- 3.2 主成分分析(PCA)
- 3.3 因子分析
- 3.4 基于树模型的降维
- 3.5 其他降维方法(扩展)
- 3.5.1 t-SNE 与 UMAP
- 3.5.2 线性判别分析(LDA)
一、引言
在数据挖掘与建模过程中,特征工程与维度降维起着至关重要的作用。
- 特征工程 涉及从原始数据中提取、构造、转化和选择最具信息量的特征,以便模型能够更准确地学习数据中的模式。
- 维度降维 则是在高维数据中剔除冗余和噪声,通过将数据投影到低维空间,不仅降低了计算复杂度,还提高了模型的可解释性。
实践目标:
本博客旨在分享如何利用 Python 工具,从数据聚合、透视、正则化、树模型到非数值数据处理,实现特征构造与变量选择,进而为后续建模奠定坚实基础。
二、特征构造与变量选择
本部分主要介绍如何通过数据聚合、透视、相关性分析以及正则化方法和树模型对特征进行构造和筛选。同时,我们还讨论如何将文本和类别数据转换为数值形式,为下游建模做好准备。
2.1 数据聚合与透视
-
数据聚合:
使用聚合统计方法(如均值、标准差、最大值、最小值、总和等)可以从原始数据中提取出描述数据特性的统计指标。这有助于捕捉数据分布、趋势以及潜在异常。
工具:- Pandas:利用
groupby
方法对数据按某些关键字段分组,再计算聚合统计量; - 透视表(Pivot Table):使用
pd.pivot_table
重新组织数据结构,实现多维度数据交叉汇总,便于观察各维度间的关系。
- Pandas:利用
-
实践示例:
import pandas as pd import numpy as np # 模拟数据:销售数据示例 np.random.seed(42) data = pd.DataFrame({ '日期': pd.date_range(start='2023-01-01', periods=100, freq='D'), '产品类别': np.random.choice(['电子产品', '家居用品', '服饰', '食品'], size=100), '销售额': np.random.randint(100, 1000, size=100) }) # 创建透视表:按产品类别和月份聚合销售额 data['月份'] = data['日期'].dt.strftime('%Y-%m') pivot_table = pd.pivot_table(data, values='销售额', index='产品类别', columns='月份', aggfunc='sum', fill_value=0) print(pivot_table)
这段代码展示了如何将销售数据聚合到产品类别与月份的交叉表中,为进一步分析提供基础。
2.2 相关性分析
- 目的:
计算各变量间的相关性,可以帮助识别哪些特征具有高信息量,以及哪些特征之间高度相关而可能冗余,从而指导变量选择和模型简化。 - 工具:
- Pandas 的
corr()
方法,用于计算皮尔逊相关系数矩阵; - Seaborn 可用于绘制相关矩阵热图,使得相关性一目了然。
- Pandas 的
博客教程:
- 4.玩转热图(相关矩阵、缺失值、多维相关、聚类热图、时间序列)——Python数据挖掘代码实践
- 4.玩转热图(续:地图热图)——Python数据挖掘代码实践
- 4.玩转热图(续:矩阵式网络关系热图、Pivot Table 热图、三维/交互式热图)——Python数据挖掘代码实践
2.3 正则化方法
-
LASSO(L1 正则化):
通过在模型训练过程中加入 L1 正则化项,促使部分特征系数变为零,从而实现自动变量选择。
工具:- 使用 scikit-learn 中的
LassoCV
可以自动通过交叉验证选择最佳正则化参数。
- 使用 scikit-learn 中的
-
岭回归(L2 正则化):
在模型中加入 L2 正则化项,虽然不会使特征系数完全为零,但能抑制系数过大,降低模型过拟合风险。
工具:- 使用 scikit-learn 中的
RidgeCV
实现岭回归,并通过交叉验证选择参数。
- 使用 scikit-learn 中的
-
实践示例(LASSO):
from sklearn.linear_model import LassoCV from sklearn.model_selection import train_test_split # 假设 data 中有多个特征和一个目标变量 '销售额' # 此处我们模拟扩展 data 的特征 np.random.seed(42) data['特征1'] = np.random.rand(100) data['特征2'] = np.random.rand(100) data['特征3'] = np.random.rand(100) # 准备特征与目标 X = data[['特征1', '特征2', '特征3']] y = data['销售额'] # 分割数据 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 使用 LassoCV 自动选择最佳参数 lasso = LassoCV(cv=5, random_state=42) lasso.fit(X_train, y_train) print("各特征系数:", lasso.coef_)
如果某些特征系数趋向于 0,表明这些特征可能冗余,可以考虑剔除。
2.4 树模型与特征重要性
-
基于树模型的特征选择:
树模型(如随机森林、梯度提升树)通过评估各特征在分割决策中的贡献,能够自动计算特征的重要性得分。 -
工具:
- scikit-learn 的
RandomForestRegressor
或RandomForestClassifier
可用于提取特征重要性。
- scikit-learn 的
-
实践示例:
from sklearn.ensemble import RandomForestRegressor # 使用随机森林进行特征重要性分析 rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(X_train, y_train) # 提取特征重要性 importance = rf.feature_importances_ for feature, score in zip(X.columns, importance): print(f"{feature}: {score:.4f}")
根据输出的特征重要性,可以保留对预测贡献较大的特征,剔除冗余部分,从而降低模型复杂性。
2.5 非数值数据处理
在许多实际数据挖掘项目中,数据并非全为数值型数据。文本数据和类别数据需要先转化为数值形式,才能用于建模。下面介绍两种常见类型数据的处理方法,并通过对比表格详细说明何时使用何种方法及其优缺点。
2.5.1 文本数据转化
理论说明
-
词袋模型(Bag-of-Words, BoW)
- 原理:将文本拆分为单词(或 n-gram),忽略单词顺序,仅统计每个单词的出现频率。
- 优点:简单易实现,能够反映文本中关键词的频率;
- 缺点:无法捕捉单词顺序和上下文信息,词汇表可能非常大且稀疏。
-
TF-IDF(Term Frequency–Inverse Document Frequency)
- 原理:不仅计算词频(TF),还考虑单词在整个语料库中的逆文档频率(IDF),以降低高频但信息量低的词语权重;
- 优点:更好地反映每个词对文本的重要性,能够抑制通用词汇的影响;
- 缺点:仍然忽略单词顺序和上下文信息,且计算复杂度较词袋模型稍高。
实践工具与示例代码
使用 scikit-learn 的 CountVectorizer 与 TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
# 模拟文本数据:例如产品评论
documents = [
"这款手机的性能非常好,外观漂亮,价格合理",
"手机质量一般,外观普通,但价格便宜",
"外观精美,性能卓越,用户体验极佳",
"价格昂贵,性能不稳定,不推荐购买",
"总体满意,性价比高,值得入手"
]
# 1. 使用 CountVectorizer(词袋模型)
count_vectorizer = CountVectorizer()
bow_features = count_vectorizer.fit_transform(documents)
print("词袋模型特征矩阵 (稀疏矩阵形式):")
print(bow_features.toarray())
print("词汇表:")
print(count_vectorizer.get_feature_names_out())
# 2. 使用 TfidfVectorizer(TF-IDF)
tfidf_vectorizer = TfidfVectorizer()
tfidf_features = tfidf_vectorizer.fit_transform(documents)
print("\nTF-IDF 特征矩阵 (稀疏矩阵形式):")
print(tfidf_features.toarray())
print("词汇表:")
print(tfidf_vectorizer.get_feature_names_out())
2.5.2 类别数据编码
理论说明
-
独热编码(One-Hot Encoding)
- 原理:将每个类别转化为一个新的二元变量。如果有 k 个类别,则会生成 k 个新特征,每个特征表示该类别是否出现。
- 优点:不会引入类别间的顺序关系,适用于无序分类变量;
- 缺点:对于类别数目较多的数据,会产生大量稀疏特征,增加计算和存储成本。
-
类别编码(Ordinal Encoding)
- 原理:将类别变量映射为整数,通常适用于有序分类变量。
- 优点:简单、直观,不会扩展特征数量;
- 缺点:可能隐含了类别之间的顺序关系,即使在无序分类中使用也可能误导模型。
实践工具与示例代码
使用 Pandas 的 get_dummies 进行独热编码
import pandas as pd
# 模拟包含类别数据的 DataFrame
data = pd.DataFrame({
'产品类别': ['电子产品', '家居用品', '服饰', '食品', '电子产品', '服饰']
})
# 使用 Pandas get_dummies 进行独热编码
one_hot_encoded = pd.get_dummies(data['产品类别'], prefix='类别')
print("独热编码结果:")
print(one_hot_encoded)
使用 scikit-learn 的 OneHotEncoder 与 OrdinalEncoder
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
# 假设我们有相同的类别数据
categories = data[['产品类别']]
# 1. OneHotEncoder(独热编码),新版使用 sparse_output 参数
ohe = OneHotEncoder(sparse_output=False) # sparse_output=False 返回数组而非稀疏矩阵
one_hot_array = ohe.fit_transform(categories)
print("\nOneHotEncoder 结果:")
print(one_hot_array)
print("类别顺序:", ohe.categories_)
# 2. OrdinalEncoder(类别编码)
oe = OrdinalEncoder()
ordinal_array = oe.fit_transform(categories)
print("\nOrdinalEncoder 结果:")
print(ordinal_array)
print("类别顺序:", oe.categories_)
2.5.3 对比表格:文本数据转化与类别数据编码方法
方法 | 应用场景 | 优点 | 缺点 | 常用工具 |
---|---|---|---|---|
词袋模型 (BoW) | 文本分类、情感分析(无上下文信息需求) | 简单、易理解、快速构建 | 忽略单词顺序、词汇表可能非常大 | CountVectorizer (scikit-learn) |
TF-IDF | 文本分析、文档聚类(需要区分高频与低频词) | 强调重要词汇、降低噪音 | 计算量较大、仍忽略上下文 | TfidfVectorizer (scikit-learn) |
独热编码 (One-Hot) | 无序分类变量(如产品类别、地区) | 不引入顺序关系、广泛适用 | 类别数多时产生大量稀疏特征 | pd.get_dummies(), OneHotEncoder (scikit-learn) |
类别编码 (Ordinal) | 有序分类变量(如等级、评分) | 简单、直接、特征数量不增加 | 可能误导模型,引入虚假顺序关系 | OrdinalEncoder (scikit-learn) |
三、 维度降维技术
本章节介绍如何通过降维技术在保持关键信息的同时降低高维数据的复杂性。
3.1 降维方法对比表
方法 | 核心原理 | 优点 | 局限性 | 适用场景 | 常用工具 |
---|---|---|---|---|---|
PCA | 正交变换,保留最大方差 | 计算简单、降噪效果好、可视化直观 | 仅捕捉线性关系,解释性较弱 | 数据预处理、可视化、降噪 | scikit-learn PCA, NumPy, Pandas |
因子分析 | 提取潜在因子解释数据协方差 | 强调潜在构念解释,适合高解释性需求 | 假设要求严格,对数据要求较高 | 社会科学、经济学、问卷调查 | factor_analyzer, R 的 psych 包 |
树模型降维 | 利用决策树/随机森林的特征重要性进行筛选 | 能捕捉非线性关系,直接输出特征重要性 | 依赖于模型,可能受噪声影响 | 分类、回归、特征筛选 | scikit-learn RandomForest, XGBoost |
t-SNE/UMAP | 非线性降维,保持局部邻域结构 | 揭示高维数据簇结构,适合可视化 | 计算量大,参数敏感,不适用于预处理 | 数据可视化、探索性分析 | scikit-learn t-SNE, umap-learn |
线性判别分析 (LDA) | 寻找最大类别分离方向 | 强化类别分离效果,适合有监督降维 | 仅适用于有标签数据,假设数据正态分布 | 分类问题降维、特征提取 | scikit-learn LinearDiscriminantAnalysis |
3.2 主成分分析(PCA)
理论基础
PCA 通过正交变换将原始高维数据转换为低维表示,排序依据是各主成分所保留的数据方差。主要目标是在尽可能减少信息损失的前提下,降低特征数量,从而减少计算复杂度和噪声干扰,同时便于可视化。
实践代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# 模拟生成 100 个样本、10 个特征的高维数据
np.random.seed(42)
data = pd.DataFrame(np.random.rand(100, 10), columns=[f'Feature_{i+1}' for i in range(10)])
# 初始化 PCA,降维到 2 维
pca = PCA(n_components=2)
principal_components = pca.fit_transform(data)
# 创建 DataFrame 保存降维结果
pca_df = pd.DataFrame(principal_components, columns=['PC1', 'PC2'])
# 绘制降维后数据的散点图
plt.figure(figsize=(8, 6))
plt.scatter(pca_df['PC1'], pca_df['PC2'], c='blue', alpha=0.7, edgecolor='k')
plt.title("PCA 降维结果", fontsize=14)
plt.xlabel("主成分 1 (PC1)", fontsize=12)
plt.ylabel("主成分 2 (PC2)", fontsize=12)
plt.show()
# 输出各主成分解释的方差比例
print("各主成分方差比例:", pca.explained_variance_ratio_)
3.3 因子分析
理论基础
因子分析假设观测数据是由少数潜在因子驱动的,通过提取这些因子来解释变量之间的共变异性。这种方法在需要深层次解释数据构念的领域,如心理学和经济学中较为常见。
实践代码
(注:需安装 factor_analyzer
包)
pip install factor_analyzer
from factor_analyzer import FactorAnalyzer
# 假设数据已存储在 DataFrame "data" 中
# 初始化因子分析模型,假设提取 3 个因子
fa = FactorAnalyzer(n_factors=3, rotation="varimax")
fa.fit(data)
# 查看每个因子的方差解释比例,返回三个值
eigenvalues, variance, cumulative_variance = fa.get_factor_variance()
print("每个因子解释的方差比例:", variance)
print("累计解释的方差比例:", cumulative_variance)
# 查看因子载荷矩阵
loadings = pd.DataFrame(fa.loadings_, index=data.columns, columns=[f'Factor_{i+1}' for i in range(3)])
print("因子载荷矩阵:")
print(loadings)
3.4 基于树模型的降维
理论基础
树模型(如随机森林)在训练过程中可以计算出各特征的重要性。根据重要性得分,选择对预测贡献较大的特征,从而实现降维。该方法不仅能捕捉非线性关系,还能直接提供变量排序,为特征筛选提供依据。
实践代码
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
# 扩展数据:使用之前生成的 DataFrame "data",假设目标变量为 "Target"
np.random.seed(42)
data['Target'] = np.random.rand(100)
# 分割数据
X = data.drop(columns=['Target'])
y = data['Target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 使用随机森林进行特征重要性评估
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
importance = rf.feature_importances_
# 将特征重要性保存为 DataFrame
importance_df = pd.DataFrame({'Feature': X.columns, 'Importance': importance}).sort_values(by='Importance', ascending=False)
print("特征重要性排序:")
print(importance_df)
3.5 其他降维方法(扩展)
3.5.1 t-SNE 与 UMAP
- t-SNE:一种非线性降维技术,适合可视化高维数据的簇结构,但计算复杂度高。
- UMAP:类似 t-SNE,但通常速度更快、结果更稳定,适合大规模数据可视化。
实践示例(t-SNE)
from sklearn.manifold import TSNE
# 使用 t-SNE 将数据降维到 2 维
tsne = TSNE(n_components=2, random_state=42)
tsne_results = tsne.fit_transform(data.drop(columns=['Target']))
tsne_df = pd.DataFrame(tsne_results, columns=['Dim1', 'Dim2'])
plt.figure(figsize=(8, 6))
plt.scatter(tsne_df['Dim1'], tsne_df['Dim2'], c='green', alpha=0.7, edgecolor='k')
plt.title("t-SNE 降维结果", fontsize=14)
plt.xlabel("Dimension 1", fontsize=12)
plt.ylabel("Dimension 2", fontsize=12)
plt.show()
3.5.2 线性判别分析(LDA)
- LDA(Linear Discriminant Analysis):适用于有监督的降维,通过最大化类别间差异来降低维度。
实践示例(LDA)
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
# 生成分类数据示例(假设 Target 为分类变量)
data['Category'] = np.where(data['Target'] > 0.5, 'High', 'Low')
# 分割数据
X = data.drop(columns=['Target', 'Category'])
y = data['Category']
lda = LDA(n_components=1)
lda_results = lda.fit_transform(X, y)
plt.figure(figsize=(8, 6))
plt.hist(lda_results, bins=20, color='coral', edgecolor='black', alpha=0.7)
plt.title("LDA 降维结果(单维直方图)", fontsize=14)
plt.xlabel("LDA 分量", fontsize=12)
plt.ylabel("频数", fontsize=12)
plt.show()
封面图: