☰
MATLAB PCA主成分分析实战:从原理到降维避坑指南
2026/10/14 17:16:51 网站建设 项目流程

简介:基于MATLAB的PCA主成分分析实例,以不同浓度混合物的拉曼光谱为实验数据,面向需要掌握主成分分析方法、开展光谱数据分析或进行数据降维实践的学生与科研人员。PCA是经典的多元统计方法,通过正交变换将一组相关变量转换为线性无关的主成分,在保留主要信息的同时降低数据维度,广泛用于化学计量学、模式识别与光谱分析等场景。压缩包共含2个文件,其中MATLAB脚本负责完整的PCA计算与结果展示,Excel表格提供可直接调用的训练集数据,整体大小仅2.26MB,轻量易用。目前已有1957人学习下载,尤其适合课程设计、科研入门或教学演示。通过该实例,读者可获得可直接复现的PCA分析脚本与6×40训练集数据,动手验证不同浓度混合样本的聚类与分辨效果,理解从原始光谱到主成分得分的完整流程,并能根据自身数据修改脚本,快速迁移到其他实验场景。

1. 从一堆高维数据里抓重点:PCA 在 MATLAB 里到底能干什么

做数据分析的人大概率都撞过这样的墙:手里有一张几十列的表格,每一列看着都有用,但放在一起就乱成一团;想聚类、想回归,特征一多模型就开始耍脾气,训练慢、过拟合、结果还解释不清。这时候 PCA 主成分分析往往是最先被想起的招——它在 MATLAB 里也就是两三行代码的事。但很多人跑完 pca() 拿到 coeff 和 score 就不知道下一步干什么了,更说不清为什么有时候 PC1 能解释 80% 的方差,有时候却连 30% 都不到。这篇笔记想讲的,是「基于 MATLAB 的 PCA 主成分分析实例」里真正值得动手的部分:数据从哪来、预处理怎么做、pca 函数和手写 eig 的差别在哪、主成分个数怎么定、以及那些跑完才发现数据方向反了的血泪体验。适合手里已经有数据、想用 PCA 做降维或者特征筛选的从业者,默认你会一点 MATLAB 基本操作,但完全不懂 PCA 也没有关系,照着跑一遍就通。

2. PCA 的原理与 MATLAB 的两种实现路径:先搞懂它在算什么,再谈调参

2.1 方差最大化与协方差矩阵:PCA 的数学直觉

PCA 做的事情说白了就是找一组新的坐标轴,让数据在这些轴上的投影方差依次递减。第一个新轴方向就是 PC1,它捕捉原始数据里方差最大的那个方向;PC2 垂直于 PC1,捕捉剩余方差里最大的方向,以此类推。为什么盯着方差看?因为在数据里,方差大的方向通常意味着信息量大、区分度高,而方差接近零的方向基本是噪声或者冗余。

数学上,这个找方向的过程等价于对数据的协方差矩阵做特征值分解。假设原始数据矩阵 X 已经做了中心化(每一列减去均值),那么协方差矩阵 C = X'X / (n-1),对 C 求特征值和特征向量,特征向量就是新坐标轴的方向(也就是主成分载荷),特征值就是数据在该方向上的方差。特征值越大,对应主成分越重要。累计特征值占总特征值之和的比例,就是大家常说的方差解释率。

这里有一个很多初学者容易混淆的点:PCA 是看协方差矩阵还是看相关矩阵?如果你的数据列之间量纲差异很大(比如一列是温度,一列是压力,一列是浓度),直接用协方差矩阵会让量纲大的变量垄断 PC1。这时候应该先标准化(z-score),让每一列方差变成 1,再用相关矩阵做特征分解。在 MATLAB 里,pca() 函数默认会做中心化,但不会自动标准化,这个开关需要你自己控制,后面会细说。

2.2 pca 函数与手写 eig:两种实现,结果怎么对齐

MATLAB 里做 PCA 有两条路:直接用统计工具箱的 pca 函数,或者自己用 eig 对协方差矩阵做特征分解。两条路都能得到主成分载荷,但输出格式和默认行为有差异。

% 方式一:使用 pca 函数(推荐) [coeff, score, latent, tsquared, explained] = pca(X); % coeff: 主成分载荷矩阵,每一列是一个主成分方向 % score: 数据在主成分空间的投影(新坐标) % latent: 各主成分的特征值(方差) % explained: 各主成分解释方差的百分比,加起来是 100 % 方式二:手动实现,便于理解原理 X_centered = X - mean(X); % 中心化 C = (X_centered' * X_centered) / (size(X, 1) - 1); % 协方差矩阵 [eigVec, eigVal] = eig(C); % 特征分解 % eigVal 是对角矩阵,对角线上的元素是特征值,可能需要翻转

pca 函数返回的 explained 直接给出了每个主成分的方差解释百分比,省去了手动计算。而 eig 返回的特征值矩阵是对角矩阵,且不一定按大小排序,需要自己提取对角线并排序。两者算出来的主成分方向本质一致,但符号可能相反——同一个主成分,特征向量乘以 -1 仍然是一个合法的方向。这就导致手动实现和 pca 函数画出来的载荷图可能镜像翻转,不是算错,是符号不定。

另外,pca 函数默认做了中心化(Centered 参数为 true),而手动实现里这一步要自己写。如果数据还没中心化就直接求协方差矩阵,第一主成分会被均值带偏。还有一点:pca 函数对输入数据的行和列有约定——行是观测样本,列是变量,这一点千万不能搞反,否则算出来的「主成分」会是样本方向而不是变量方向。

% 数据方向检查,避免行/列搞反 % X 的 size 应该是 [n_samples, n_features] % 常见错误是把特征放在行上,导致 PCA 结果解释完全反转 disp(size(X)); % 确认第一维是样本数

2.3 标准化选型:什么时候用 zscore,什么时候直接用原始数据

标准化是 PCA 流程里最容易起分歧的一步。我的判断标准很简单:如果各列变量的单位或量级不同,标准化;如果各列已经是同一物理量、近似同量级,可以直接用。举个例子,某个模拟项目里采集了三个传感器信号,单位都是伏特,取值范围都在 -5 到 5 之间,那就不需要标准化,直接用协方差矩阵。但如果数据里同时有温度(几十度)和浓度(零点几克每升),不标准化的话 PC1 基本就是温度一个人的独角戏,浓度信息被完全淹没。

% 标准化处理(z-score) X_std = zscore(X); % zscore 默认对每一列做 (x - mean) / std % 标准化之后再做 PCA,等价于用相关矩阵做特征分解 % 标准化前后对比:查看各列标准差 std_X = std(X); std_X_std = std(X_std); disp([std_X; std_X_std]); % 如果标准化前 std 差异超过一个数量级,强烈建议用 zscore

标准化之后有一个连带影响:所有变量的方差都是 1,因此每个变量对主成分的初始贡献是等权的。这时候如果某个变量对业务特别重要,你想让它在 PCA 里占更大权重,就不应该标准化,或者标准化之后手动加权。这个选择没有绝对的对错,取决于你想让 PCA 回答什么问题——是找数据内在的最大方差方向,还是剔除量纲影响后的相关性结构。实践里,做聚类和异常检测一般建议标准化,做信号处理和图像压缩一般直接用原始数据。两种都跑一遍,对比碎石图(后面会讲),哪个解释更符合业务直觉就用哪个。

3. 用自带数据集跑通第一个完整 PCA:从加载数据到画出碎石图

3.1 数据选取与预处理:为什么用内置数据集做范例

新手学 PCA 最容易卡在数据准备上——要么数据没整理成矩阵,要么有缺失值,要么列名和类型混在一起。为了避免这些干扰,最干净的做法是用 MATLAB 自带的数据集。自带数据集里最有名的是鸢尾花(fisheriris),150 行、4 列,四个特征分别是花萼长宽和花瓣长宽,三个类别各 50 条。这个数据集量纲差异不大、样本量适中、结构干净,特别适合做 PCA 入门。当然,如果你手头有自己的业务数据,结构也一样——每一行是一条记录,每一列是一个特征。

% 加载 MATLAB 内置鸢尾花数据集 load fisheriris; % meas: 150x4 的数值矩阵,四列分别是花萼长、花萼宽、花瓣长、花瓣宽 % species: 150x1 的类别标签,用于后续可视化着色 X = meas; % 先看一眼数据的基本统计量 mean_X = mean(X); std_X = std(X); [min_X, max_X] = bounds(X); % 如果 std 列之间差异太大,考虑 zscore % 数据是否有缺失值 missing_count = sum(isnan(X), 1); disp(missing_count);

fisheriris 这个数据集因为太小、太规整,有时候会被当成「玩具」,但它非常适合验证 PCA 流程是否走通。如果你的数据有缺失值,pca 函数默认会整行删除含有 NaN 的样本,这在样本量少的时候可能丢太多数据,建议先单独处理缺失值。预处理这一步还有一个经常被忽略的点:pca 函数会忽略数据的列名和类型,它只认数值矩阵。如果你从表格里读入数据,记得先用 table2array 把数值列取出来,类别和文本列单独存。

% 如果数据是 table 格式,先转成数值矩阵 T = readtable('mydata.csv'); X = table2array(T(:, 2:end)); % 假设第一列是 ID 之类的非数值列 % 注意:table2array 要求所有列类型一致或可转换 % 有文本列混在里面会报错,先单独把文本列拆出来

3.2 执行 PCA 并解读输出:coeff、score、latent、explained 各自是什么

数据准备好了,跑 PCA 就是一行代码的事。但跑完之后那四个输出变量,每个到底是什么、能用来干什么,很多人并没有真正分清。我见过有人把 score 当载荷用,有人把 latent 当方差解释率,画出来的图全对不上号。

% 对标准化后的数据跑 PCA X_std = zscore(X); [coeff, score, latent, tsquared, explained] = pca(X_std); % coeff 是 4x4 矩阵,每一列是一个主成分方向 % 第一列是 PC1 方向,第二列是 PC2 方向,以此类推 % score 是 150x4 矩阵,每一行是原始样本在四个主成分上的投影坐标 % latent 是 4x1 向量,四个主成分的特征值(方差) % explained 是 4x1 向量,四个主成分解释的方差百分比 % 查看前两个主成分的载荷 disp(coeff(:, 1:2)); % 查看各主成分方差解释率 disp(explained);

解释一下四个输出:coeff 每列是一个单位向量,指向主成分方向,它的每个元素反映了原始变量对这个主成分的贡献权重。score 是原始数据在新坐标系下的坐标,后续聚类、分类、回归都改用 score 的前几列。latent 是特征值,它的数值大小直接反映了该主成分的方差,latent 的平方根是标准差,在画置信椭圆时会用到。explained 最有直观意义——它告诉你 PC1 占了总方差的百分之多少,比如 72.5%,意味着只用这一个维度就能保留近四分之三的信息。

还有一个容易忽略的 tsquared 输出,它是 Hotelling T2 统计量,衡量每个样本到主成分空间中心的距离。这个量在异常检测里很有用,偏离中心太远的样本通常是异常点。如果你想用 PCA 做数据清洗,可以在跑完 pca 后直接把 tsquared 值偏大的样本标记出来,人工检查一下是不是录入错误。

% 用 tsquared 找异常样本 threshold = prctile(tsquared, 95); % 取 95 分位数作为阈值 outlier_idx = find(tsquared > threshold); disp(outlier_idx); % 这些样本可能是在某几个特征上明显偏离正常范围 % 注意:这是统计异常,不代表业务异常,需要结合实际判断

3.3 可视化三件套:碎石图、双标图、载荷图怎么画

跑完 PCA 之后,图形化输出几乎是必须的。碎石图(Scree Plot)用来定主成分个数,双标图(Biplot)用来同时看样本分布和变量贡献,载荷图用来理解每个变量和主成分的关系。这三张图画完,PCA 结果的基本盘就清楚了。

% 碎石图:画特征值(或方差解释率)随主成分序号的变化 figure; plot(1:length(latent), latent, 'o-', 'LineWidth', 1.5); xlabel('主成分序号'); ylabel('特征值(方差)'); title('碎石图'); grid on; % 找“拐点”:特征值曲线从陡降变平缓的位置 % 一般取拐点之前的主成分个数 % 双标图:样本得分 + 变量载荷叠加在一张图上 figure; biplot(coeff(:, 1:2), 'scores', score(:, 1:2), 'varlabels', {'SL', 'SW', 'PL', 'PW'}); % 左边和下边是样本得分的坐标轴,右边和上边是载荷的坐标轴 % 变量箭头越长,说明该变量对这两个主成分的贡献越大 % 两个箭头夹角越小,说明这两个变量越正相关 % 载荷图:只看变量在主成分空间的位置 figure; plot(coeff(:, 1), coeff(:, 2), 'o'); text(coeff(:, 1) + 0.03, coeff(:, 2), {'SL', 'SW', 'PL', 'PW'}, 'FontSize', 10); xlabel('PC1 载荷'); ylabel('PC2 载荷'); axis equal; % 保证横纵轴比例一致,否则角度会被拉伸扭曲 grid on;

双标图是 PCA 可视化里信息密度最高的图,但也是坑最多的图——如果数据的量级差异大,样本得分(score)和载荷(coeff)画在同一张图里会出现「样本挤在原点、箭头飞出图外」的情况。这时候 pca 函数里有个可选参数解决这个问题:pca(X, 'Scores') 不会改变,但 biplot 内部会把载荷缩放到和得分匹配。如果数据标准化了,这种尺度问题会减轻很多。另外,biplot 的坐标轴含义要搞清楚:下方横轴是 PC1 得分,上方横轴是 PC1 载荷,两者线性对应,但数值范围不同,看的时候别混。载荷图建议加 axis equal,不然箭头的角度会被拉伸,导致你误判变量之间的夹角关系。

4. 主成分个数怎么定:从方差解释率到业务可解释性的三层判断

4.1 累积方差解释率阈值:85% 还是 95%,取决于你拿 PCA 干什么

主成分要选几个,最粗的规则是看累积方差解释率。一般推荐选到累积解释率达到 85% 到 95% 之间。但这个阈值不是死的,关键看你的下游任务:如果 PCA 之后接的是回归模型,解释率到 85% 通常够了,再多几个主成分边际收益很低,还容易把噪声也学进去;如果 PCA 是为了做数据可视化,那只需要前两个或前三个主成分,因为它们能画在二维或三维图上,这时候纠结解释率没太大意义;如果 PCA 是为了压缩存储或者加速计算,那就需要权衡压缩比和信息损失,可能 80% 就够了。

% 计算累积方差解释率 cum_explained = cumsum(explained); disp(cum_explained); % 找到累积解释率首次超过 90% 的位置 k_90 = find(cum_explained >= 90, 1, 'first'); disp(['累积解释率 >= 90% 需要 ', num2str(k_90), ' 个主成分']);

用这个规则有一个注意点:累积解释率是针对训练数据算的,不能保证在新数据上同样成立。如果你的数据是从总体里抽样来的,样本量太小会导致特征值偏高,解释率虚高。稳妥的做法是在多个随机子集上分别计算解释率,看波动大不大。如果波动很大,说明你的主成分结构不稳定,这时候不要说取 k 个,可能整个 PCA 的适用性都需要重新审视。

% 多次随机子采样验证解释率稳定性 rng(42); % 固定随机种子 sub_explained = zeros(100, size(X, 2)); for i = 1:100 idx = randsample(size(X, 1), round(0.8 * size(X, 1))); [~, ~, ~, ~, sub_ex] = pca(X_std(idx, :)); sub_explained(i, :) = sub_ex; end % 查看各主成分解释率的波动范围 disp([min(sub_explained); max(sub_explained)]);

4.2 特征值大于 1 准则与碎石图拐点:怎么读更准确

除了累积解释率,还有两种经典判定法。一种是 Kaiser 准则——只保留特征值大于 1 的主成分。因为标准化数据的每个原始变量方差是 1,如果一个主成分的特征值小于 1,说明它解释的方差还不如一个原始变量多,留着意义不大。这个准则在变量数几十个的时候比较好使,但变量太少(比如就四五个)时它可能只留下 1 到 2 个主成分,容易丢信息。另一种是碎石图拐点法——看特征值曲线的斜率从陡降变为平缓的位置,取拐点之前的主成分数。

% Kaiser 准则:保留特征值大于 1 的主成分 k_kaiser = sum(latent > 1); disp(['特征值大于 1 的主成分数:', num2str(k_kaiser)]); % 碎石图拐点:观察曲线弯曲最剧烈的位置 figure; plot(1:length(latent), latent, 'o-', 'LineWidth', 2); hold on; % 用一阶差分找斜率突变点 diff_latent = diff(latent); [~, elbow_idx] = min(diff_latent); plot(elbow_idx + 1, latent(elbow_idx + 1), 'ro', 'MarkerSize', 10); hold off; xlabel('主成分序号'); ylabel('特征值'); title('碎石图与拐点');

拐点法实际操作时需要一点经验判断——不同人看同一张碎石图可能选不同的拐点。我的习惯是:先用程序自动定位斜率突变点,再人工看一眼曲线确认。如果多个候选拐点挨得很近,就结合累积解释率和下游任务一起定。还有一个容易被忽视的点:如果数据的变量之间的相关性本来就弱,碎石图可能没有明显拐点,特征值曲线是一条平缓下降的线。这时候说明数据本身没有特别强的低维结构,硬要压缩维度反而会损失大量信息,不如回归原始特征或者换用其他降维方法。

4.3 载荷矩阵的可解释性:业务上「这个主成分到底是什么」怎么回答

统计上选了几个主成分只是第一步,更难的是回答「PC1 在业务上代表什么」。这一步靠的是载荷矩阵——coeff 里每个元素告诉你原始变量对主成分的贡献大小和方向。比如上面鸢尾花那个例子,如果 PC1 上花瓣长和花瓣宽的载荷都是 0.6 以上、花萼宽的载荷接近零,那 PC1 可以理解为「花瓣尺寸因子」;PC2 上花萼宽载荷最大,可能对应「花萼形状因子」。这种命名不是说 PCA 自动给出了语义标签,而是你在用载荷的数值分布反推业务含义。

% 打印前三个主成分的载荷,手工做业务解释 for k = 1:3 fprintf('PC%d 载荷:\n', k); disp(coeff(:, k)'); % 找一个载荷绝对值最大的变量作为主成分的主要语义 [~, idx_max] = max(abs(coeff(:, k))); fprintf('PC%d 主要由第 %d 个变量驱动,载荷值 %.3f\n', k, idx_max, coeff(idx_max, k)); end

载荷解释有一些容易踩的坑。首先是符号问题:同一个主成分,特征向量符号翻转后业务含义完全反向,比如 PC1 上变量 A 的载荷从 0.7 变成 -0.7,这不代表结果变了,只是方向取反了,解释的时候需要按「载荷绝对值大小」来判断变量贡献,不要被符号带偏。其次是载荷接近零的变量不代表它不重要,只代表它在这个主成分上没有贡献,它可能在其他主成分上权重很大。最后是载荷矩阵的解释一定结合 score 一起看——载荷告诉你哪些变量在同一个方向上变化,得分告诉你哪些样本在这个方向上取值偏高或偏低,两者结合才能形成完整的故事。

5. PCA 落地避坑指南:数据、参数、解释三个层面的五个真实教训

5.1 没做标准化就开跑,PC1 被量纲大的变量垄断

现象:数据里同时有温度(范围 20-80)和浓度(范围 0.1-0.5)两类变量,跑完 pca 发现 PC1 载荷几乎全在温度变量上,浓度变量在 PC2 上的贡献也微乎其微。碎石图显示 PC1 解释了 95% 的方差,看起来「效果很好」,但投影之后的样本分布跟浓度完全没有关系。 原因:协方差矩阵对量纲敏感。温度变量的方差大,在协方差矩阵里数值占绝对主导,特征分解后第一个特征向量自然指向温度方向。浓度变量的信息被数值上掩盖了。 解决:跑 PCA 前先检查各列标准差,如果差异超过一个数量级,用 zscore 标准化再跑。标准化之后 PC1 的解释率会下降,从 95% 降到 40% 左右是正常的,不要慌——这才是数据真实结构的反映。

% 检查量纲差异 std_vals = std(X); if max(std_vals) / min(std_vals) > 10 disp('建议先做 zscore 标准化'); X_processed = zscore(X); else X_processed = X; end

5.2 特征向量的符号不固定,两个批次跑出的图镜像翻转

现象:同一个数据集,第一次跑 pca 出来的载荷图,PC1 向右上方指向;过了两天重新跑一次(或者换一台机器),PC1 变成了向左下方,图画出来镜像对称。数据一模一样,图却变了,怎么看怎么别扭。 原因:特征向量在数学上是一个方向,方向的正负是任意的。数值求解器每次迭代的初始值、浮点误差等因素可能导致特征向量符号翻转。这不算 bug,但确实会造成可视化不一致。 解决:在跑完 pca 后加一个符号固定的步骤——规定每个主成分的第一个非零元素为正,如果为负,把该主成分的载荷、得分全部乘以 -1。

% 固定符号方向 for k = 1:size(coeff, 2) [~, idx] = max(abs(coeff(:, k))); % 找绝对值最大的元素 if coeff(idx, k) < 0 coeff(:, k) = -coeff(:, k); score(:, k) = -score(:, k); end end

5.3 行和列方向搞反,把 500 个特征当成 500 个样本

现象:读入的数据是 500 行 × 30 列,本意是 500 个样本、30 个特征。但读进来的时候发现是 30 行 × 500 列(特征行、样本列)。没检查就直接跑 pca,score 变成了 30 行 × 30 列,画图全乱。更隐蔽的是如果行列数恰好相等(比如 50×50 的方阵),跑完了都发现不了。 原因:pca 函数约定第一维是样本,第二维是变量。方向反了之后,PCA 变成对「样本维度」做降维,结果完全不是预期含义。 解决:跑 pca 前用 size 检查矩阵方向,或者用变量名列名来判断——如果读进来的数据列名是特征名,说明每一列是变量,行才是样本。另一种验证方法:跑完后看 score 的行数是否等于原始数据的行数,如果不等于,就是方向反了。

% 方向自检 assert(size(score, 1) == size(X, 1), 'score 行数与原始数据不一致,请检查 X 的方向'); % 另外检查 coeff 的行数是否等于变量数 assert(size(coeff, 1) == size(X, 2), 'coeff 行数与变量数不一致,请检查 X 的方向');

5.4 用 pca 函数处理有缺失值的数据,样本量悄悄变少

现象:原始数据有 8000 行,缺失值集中在某几列。跑完 pca 后发现 score 只有 7200 行,少了 800 行。后续拿 score 去做聚类,结果发现聚类样本数对不上,排查了半天才发现是 PCA 阶段删了数据。 原因:pca 函数默认的 'Rows' 参数是 'complete',意思是只要某行有任何一列是 NaN,整行直接删除。如果缺失值不是随机分布的,删除后还会引入样本选择偏差。 解决:根据缺失比例决定策略。如果缺失值占比小(比如小于 5%),可以用行均值或列均值填充;如果缺失值占比大,考虑用 kNN 填充或者干脆删除对应变量。也可以在 pca 调用时设置 'Rows', 'pairwise',用成对完整观测计算协方差矩阵,但这样得到的协方差矩阵可能不正定,结果更不稳定,一般不太推荐。

% 填充缺失值后再跑 PCA X_filled = X; for j = 1:size(X, 2) col = X(:, j); col(isnan(col)) = nanmean(col); % 用列均值填充 X_filled(:, j) = col; end [coeff_filled, score_filled] = pca(zscore(X_filled));

5.5 用新数据投影时忘了保存 coeff,测试集和训练集对不齐

现象:用训练数据跑完 PCA,取前三个主成分的得分训练了一个分类器。模型上线后,新数据拿来做预测,需要先把新数据投影到同一个主成分空间。结果发现当初只保存了分类器,没保存 coeff 矩阵,新数据根本没有办法投影,整个上线流程卡住。 原因:PCA 的投影变换不是数据自带的属性,它是由训练集的 coeff 矩阵决定的。新数据必须用同一个 coeff 矩阵做线性变换,而不是重新跑一遍 pca——重新跑的话主成分方向会和训练集不一致,分类器就废了。 解决:训练阶段把 coeff、均值、标准差(如果标准化了)全部保存成 .mat 文件,预测阶段加载后直接做矩阵乘法。

% 训练阶段:保存 PCA 参数 mean_X = mean(X_train); std_X = std(X_train); X_train_std = (X_train - mean_X) ./ std_X; [coeff, score, latent, ~, explained] = pca(X_train_std); save('pca_model.mat', 'coeff', 'mean_X', 'std_X', 'explained'); % 预测阶段:加载并用同一套参数投影 load('pca_model.mat'); X_new_std = (X_new - mean_X) ./ std_X; % 用训练集的均值和标准差 score_new = X_new_std * coeff; % 注意:如果后续只用了前 k 个主成分,这里也取前 k 列

6. 用交叉验证和平行分析来定主成分个数:更稳的经验法则

讲完避坑,最后一章给一个更系统的主成分个数判定方法——平行分析(Parallel Analysis)。这个方法的核心思路是:把原始数据的每一列随机打乱,破坏变量间的相关性,然后对打乱后的数据跑 PCA,记录特征值。重复几十次,得到一组随机数据的特征值参考分布。然后拿原始数据的特征值和这个分布比,只有原始特征值明显大于随机特征值的那些主成分才值得保留。这个方法的优势在于它不依赖经验阈值,而是用数据自己和自己比,能有效避免过高估计主成分数量(这是累积解释率法容易犯的毛病)。

% 平行分析实现 rng(42); n_perm = 50; n_vars = size(X, 2); perm_latent = zeros(n_perm, n_vars); for i = 1:n_perm X_perm = X_std; for j = 1:n_vars X_perm(:, j) = X_perm(randperm(size(X, 1)), j); end [~, ~, perm_latent_i] = pca(X_perm); perm_latent(i, :) = perm_latent_i; end % 取随机特征值的 95 分位数作为参考线 ref_line = prctile(perm_latent, 95); figure; plot(1:n_vars, latent, 'o-', 'LineWidth', 2); hold on; plot(1:n_vars, ref_line, '--', 'LineWidth', 1.5); hold off; legend({'原始特征值', '随机特征值 95% 分位'}); xlabel('主成分序号'); ylabel('特征值'); title('平行分析'); % 保留原始特征值高于参考线的主成分 k_parallel = sum(latent > ref_line');

平行分析跑出来的结果往往比 Kaiser 准则保守,比碎石图客观,在变量数较多的数据集上尤其好用。需要注意的一点是:随机打乱是按列独立打乱,这会完全破坏列间相关性,但保留每一列自身的分布特征(均值方差不变)。这正好模拟了「变量间没有相关性但各自有方差」的零模型。如果数据本身有很强的时间结构或者空间结构(比如时间序列),逐列随机打乱可能不太合适,可以考虑分块打乱。

除了定主成分个数,最后再说一个值得养成的习惯:PCA 跑完之后,把前几个主成分的得分和原始数据里最重要的业务标签画在一起看。比如你已经知道了样本的类别、等级或者好坏标签,把 score 的前两列按标签着色画散点图。如果不同标签在得分空间里分得开,说明 PCA 保留的信息对你的下游任务是有用的;如果完全混在一起,那你该回头检查是不是预处理出了问题,或者这些标签本身就不由数据里的方差结构决定。这个验证只需要几行代码,但对判断「PCA 在这个项目里到底值不值得用」非常有帮助。

% 用类别标签给得分图着色 figure; gscatter(score(:, 1), score(:, 2), species); xlabel('PC1'); ylabel('PC2'); legend('Location', 'best'); grid on; % 如果三个类别在 PC1-PC2 平面上明显分开,说明前两个主成分已经抓住了分类关键信息

某个项目里我帮一个实验团队处理过一组近百维的光谱数据,第一次跑 PCA 直接用了原始数据,PC1 解释了 98% 的方差,看似完美,但 PC1 的载荷几乎全集中在波长最大的那几个通道上,后来才发现是那几个通道的量级比其他通道大三个数量级。标准化之后重新跑,PC1 解释率降到 45%,但 PC2 和 PC3 补上了更多化学信息,最终模型效果反而翻了一倍。那之后我养成了一个习惯:每跑一次 PCA,先打印各列标准差,再打印协方差矩阵的前三行三列,确认数据形态符合预期,才继续往下走。现在我把这套流程固定下来,每次做 PCA 都走同一个检查清单,少踩很多冤枉坑,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询