简介:围绕MATLAB开展相关性分析的数学建模资料包,将统计方法与编程实现相结合,面向数学建模竞赛选手与数据分析初学者。内容聚焦皮尔逊相关系数与斯皮尔曼秩相关系数,前者刻画线性关系强度,后者基于秩次衡量单调关联,可适应非正态或存在离群值的数据场景,并提供了相关系数临界值查阅说明。压缩包内含11个文件,以m脚本、xls/xlsx体测数据、mat数据文件为主,另附docx临界值说明与jpg热力图结果,整体大小仅194KB,下载与部署都很轻量。包内以八年级男女生体测数据为案例,覆盖数据导入与预处理、相关系数计算、显著性判断、热力图可视化等完整流程,配套代码和说明文档模块化清晰,便于边看边练并迁移到自己的变量关系分析任务中。已有2408人浏览学习,适合课程设计、数学建模备赛或科研探索初期参考。
1. 相关性分析不是算个相关系数那么简单
两个变量的皮尔逊相关系数算出来0.85,建模群里一片欢呼,结果把时间趋势去掉之后只剩0.2——这场景在数学建模里太常见了。相关性分析看起来就是一行 corrcoef 的事,但指标选错、数据没洗干净、显著性没检验,结果就是给评委递刀子。这篇内容从三种相关系数的适用边界讲起,一直落到 MATLAB 代码实现、可视化出图、偏相关分析验证,覆盖一次完整的相关性分析流程。适合正在做数学建模作业、准备建模比赛,或者论文里需要量化变量关联度的从业者,按章节往下走就能复现。
2. 相关系数三件套:皮尔逊、斯皮尔曼与肯德尔怎么选
2.1 三种系数的数学定义与适用边界
相关性分析的第一步不是写代码,是选指标。MATLAB 里最常用的三个系数是皮尔逊(Pearson)、斯皮尔曼(Spearman)和肯德尔(Kendall),三者的数学假设完全不同,选错了结果就没意义。
皮尔逊相关系数衡量的是两个变量的线性相关程度,公式是协方差除以两个标准差的乘积。它要求数据近似正态分布、变量之间是线性关系,而且对异常值极其敏感——一个离群点就能把系数从 0.8 拉到 0.3。这是建模中最常用的系数,但也是被误用最多的系数。
斯皮尔曼系数是秩相关系数,计算时先把数据排序转换成秩次,再对秩次算皮尔逊相关。它不要求正态分布,对异常值的容忍度高,适合单调但非线性的关系。比如变量 A 增长时变量 B 以指数方式增长,皮尔逊可能只算出 0.6,斯皮尔曼能到 0.9。
肯德尔系数也是基于秩的,但它的思路是检查所有变量对的一致性——把每对观测值拿出来看排序方向是否一致,一致的对多说明相关性高。它在小样本、存在大量并列秩(tie)的场景下表现更稳定,但计算量偏大。
选型的基本原则是:数据近似正态且关系线性,用皮尔逊;数据偏态或关系单调非线性,用斯皮尔曼;样本量小或者数据里有大量重复值,用肯德尔。实际做数学建模时,我一般会三个系数都算一遍,如果差异不大,就以皮尔逊为主;如果差异明显,说明数据里存在非线性或异常值干扰,这时候斯皮尔曼的结果更可信。
2.2 MATLAB 实现:corrcoef 与 corr 的完整调用
MATLAB 里计算相关系数有两条路:corrcoef 和 corr。corrcoef 在基础 MATLAB 里就有,corr 属于 Statistics and Machine Learning Toolbox,功能更全。建模场景下建议直接用 corr,因为能同时算三种系数、拿到 p 值、指定备择假设。
% 加载示例数据:假设 data 是 n×m 矩阵,每列是一个变量 % 生成模拟数据:30个样本,5个变量,其中变量3和变量4存在较强相关 rng(42); n = 30; data = randn(n, 5); data(:, 3) = data(:, 3) + 0.8 * data(:, 4) + 0.2 * randn(n, 1); % 计算三种相关系数矩阵 R_pearson = corr(data, 'Type', 'Pearson'); R_spearman = corr(data, 'Type', 'Spearman'); R_kendall = corr(data, 'Type', 'Kendall'); % 同时输出相关系数和p值 [R, P] = corr(data, 'Type', 'Pearson'); % 只关注某两列之间的相关系数和p值 [r12, p12] = corr(data(:, 1), data(:, 2), 'Type', 'Pearson'); disp('Pearson相关系数矩阵:'); disp(round(R, 3)); disp('p值矩阵:'); disp(round(P, 3));corr 函数的第一个参数可以是整个矩阵,也可以是两个单独的列向量。矩阵输入时返回的是 m×m 的相关系数矩阵和对应的 p 值矩阵;向量输入时返回的是标量相关系数和标量 p 值。Type 参数用来指定系数类型,支持 'Pearson'、'Spearman'、'Kendall' 三种取值。
需要注意 corr 默认的 p 值检验是「相关系数不等于 0」的双侧检验,用的是 t 统计量近似。如果你只关心正相关或负相关,要手动把 p 值除以 2 再做单侧判断——不过大多数建模场景里双侧检验就够用了,不用纠结这个细节。
2.3 从相关系数矩阵到显著性检验:p 值的正确解读
相关系数矩阵出来了,下一步是看显著性。很多人只看系数绝对值大小,0.8 就觉得稳了,实际上样本量只有 15 的时候,0.8 的系数完全可能不显著。反过来,样本量上了 500,0.15 的系数也可能显著——所以显著性检验是判断关系是否真实存在的关键环节。
MATLAB 里 p 值的判断很简单:p 小于 0.05 说明在 95% 置信水平下相关系数显著不为零,p 小于 0.01 说明在 99% 水平下显著。但这里有个容易踩的坑:多重比较问题。如果你的数据有 10 个变量,相关系数矩阵里有 45 个不重复的系数,就算所有变量之间完全无关,纯靠随机也可能有 2~3 个 p 值小于 0.05。
% 多重比较校正:用Bonferroni方法调整p值 % 假设R和P来自前面的corr计算,m是变量个数 m = size(data, 2); % 提取上三角矩阵的非重复p值 p_values = P(triu(true(m), 1)); % Bonferroni校正:p值乘以比较次数 p_adjusted = p_values * (m * (m - 1) / 2); p_adjusted = min(p_adjusted, 1); % 校正后p值不能超过1 % 找出校正后仍然显著的变量对 [var1, var2] = find(triu(P, 1) < 0.05 & triu(R, 1) > 0.5); for k = 1:length(var1) fprintf('变量%d与变量%d:r=%.3f,p=%.4f\n', var1(k), var2(k), R(var1(k), var2(k)), P(var1(k), var2(k))); endBonferroni 是最保守的多重比较校正方法,做法是把 p 值乘以比较次数。45 次比较意味着原始 p 值要小于 0.0011 才算显著,条件确实苛刻,但如果你的论文或报告里要宣称发现了某些强相关关系,这个保守操作反而能帮你挡住质疑。
实际操作里我一般分两步走:先不管校正,把显著且系数绝对值大于 0.5 的变量对列出来;再做一次 Bonferroni 校正看哪些还稳。如果校正后依然显著,那这组关系可以放心写进报告;如果校正后消失了,就在正文里标注「校正前显著,校正后边缘显著」,至少态度是诚实的。
3. 数据预处理:相关性分析结果可信的前提
3.1 缺失值与异常值的处理顺序
直接拿原始数据算相关系数是新手最容易犯的错误。缺失值、异常值、趋势项、量纲差异,每一项都会扭曲相关系数的计算结果,而且扭曲方向不可控——可能放大也可能缩小,完全看数据里的噪声分布。
处理顺序上,先处理缺失值,再处理异常值,最后考虑去趋势和标准化。顺序不能反,因为异常值识别算法(比如三倍标准差法)本身对缺失值敏感,缺失太多会把正常值误判成异常值。
% 步骤一:检查缺失值分布 missing_count = sum(ismissing(data)); fprintf('每列缺失值数量:\n'); disp(missing_count); % 步骤二:缺失值处理——少量缺失用行删除,大量缺失用插值 if max(missing_count) < 5 data_clean = rmmissing(data); % 删除包含缺失值的行 else % 线性插值补全:对每列单独处理 data_clean = data; for col = 1:size(data, 2) col_data = data(:, col); nan_idx = isnan(col_data); if sum(nan_idx) > 0 x = 1:length(col_data); data_clean(nan_idx, col) = interp1(x(~nan_idx), col_data(~nan_idx), x(nan_idx), 'linear'); end end end % 步骤三:异常值检测——用三倍标准差法 data_final = data_clean; for col = 1:size(data_clean, 2) col_mean = mean(data_clean(:, col), 'omitnan'); col_std = std(data_clean(:, col), 'omitnan'); outlier_idx = abs(data_clean(:, col) - col_mean) > 3 * col_std; fprintf('变量%d异常值数量:%d\n', col, sum(outlier_idx)); % 异常值替换为NaN,然后再插值 data_final(outlier_idx, col) = NaN; end data_final = fillmissing(data_final, 'linear');rmmissing 删除包含缺失值的整行,数据量充足时最简单可靠。如果每列缺失少于 5%,行删除不会损失太多样本;但如果缺失比例高,还是用 interp1 做线性插值。三倍标准差法适合近似正态分布的数据,偏态严重时用四分位距法更稳——但建模比赛里三倍标准差已经够用,不必过度复杂。
异常值检测完不要直接删,替换成 NaN 再插值的好处是保留样本量。如果直接删除异常值所在的行,样本量会进一步缩小,影响后续的显著性检验效力。
3.2 数据标准化与去趋势:什么时候必须做
标准化和去趋势是两个容易被忽视的预处理步骤。标准化解决量纲问题——一个变量范围是 0~1,另一个是 0~10000,算相关系数本身不受影响,因为相关系数已经是无量纲的,但如果是算协方差矩阵或者做主成分分析,标准化就变得重要。相关性分析场景下,标准化是可选操作,去趋势则是特定条件下必须做。
去趋势针对的是时间序列数据。如果两个变量都随时间同步增长,比如年份和某指标,算出来的相关系数虚高——它们不是真正相关,只是共享同一个时间趋势。这时候必须把趋势去掉再算相关性。
% 对时间序列数据做去趋势处理 % 假设 data_ts 是 n×m 的时间序列数据,每行是一个时间点 n = size(data_ts, 1); t = (1:n)'; % 方法一:线性去趋势——对每列拟合一次线性回归,取残差 data_detrended = zeros(size(data_ts)); for col = 1:size(data_ts, 2) p = polyfit(t, data_ts(:, col), 1); trend = polyval(p, t); data_detrended(:, col) = data_ts(:, col) - trend; end % 方法二:直接用detrend函数 data_detrended2 = detrend(data_ts, 1); % 第二个参数1表示线性趋势 % 对比去趋势前后的相关系数 R_before = corr(data_ts, 'Type', 'Pearson'); R_after = corr(data_detrended, 'Type', 'Pearson'); disp('去趋势前相关系数矩阵:'); disp(round(R_before, 3)); disp('去趋势后相关系数矩阵:'); disp(round(R_after, 3));detrend 的第二个参数传 1 做线性去趋势,传 0 则只去均值。如果数据有季节性波动,可以用 detrend(data, 'linear') 配合季节差分,不过数学建模场景下线性去趋势已经能处理大部分虚高相关的问题。做完去趋势后,对比前后相关系数矩阵的变化——如果某个系数从 0.8 掉到 0.3,说明原来的相关主要来自共同趋势,不是变量间的真实联系。
3.3 多重共线性初筛:相关性矩阵的边界用途
相关性矩阵还有一个用途是初筛多重共线性。在回归建模中,如果两个自变量高度相关(|r| > 0.8),同时放进模型会导致系数估计不稳定、标准误膨胀。相关性矩阵能帮你快速发现这些潜在问题。
但相关性矩阵只能发现两两之间的共线性,三个变量之间的多重共线性(比如 X3 = X1 + X2)在相关性矩阵里可能完全不明显,因为两两之间的相关系数都不高。这种情况下要用方差膨胀因子(VIF)来判断,MATLAB 里可以自己算。
% 计算方差膨胀因子VIF % 假设 X 是 n×k 的自变量矩阵,每列一个自变量 X = data(:, 1:4); k = size(X, 2); VIF = zeros(k, 1); for i = 1:k y_i = X(:, i); % 当前变量作为因变量 X_rest = X; X_rest(:, i) = []; % 其余变量作为自变量 % 线性回归 b = X_rest \ y_i; y_hat = X_rest * b; resid = y_i - y_hat; R2_i = 1 - sum(resid.^2) / sum((y_i - mean(y_i)).^2); VIF(i) = 1 / (1 - R2_i); end disp('各变量的VIF值:'); disp(round(VIF, 2)); % VIF大于10说明存在严重多重共线性VIF 的计算逻辑是:把第 i 个自变量当作因变量,用其余自变量做回归,看 R² 多大。R² 越接近 1,说明这个变量能被其他变量完全解释,VIF 越大。经验法则是 VIF 大于 10 需要处理,大于 5 就要警惕。如果发现 VIF 超标的变量对,优先考虑删掉其中一个,或者用主成分分析做降维。
4. 避坑:相关性分析的五个翻车现场
4.1 皮尔逊系数被异常值带偏
现象:算出来的皮尔逊相关系数高达 0.85,画散点图发现只有一个点死死拉住了斜线,去掉这个点后相关系数掉到 0.2。
原因:皮尔逊系数对异常值极度敏感。一个离群点会同时放大协方差和标准差,而且通常协方差被影响的程度更大,导致系数虚高或虚低。散布图上拉高斜率的单点就是典型情况。
解决:先做异常值检测(三倍标准差法或四分位距法),处理完异常值后重算相关系数。同时对比皮尔逊和斯皮尔曼的结果——如果差异大于 0.3,说明异常值在起作用。后续报告里可以把两个系数都写出来,用斯皮尔曼作为稳健性验证。
4.2 样本量太小导致 p 值不可信
现象:样本量只有 12,算出来 r = 0.75,p = 0.02,看着挺美好,但换一组样本结果完全不一样。
原因:小样本下相关系数的抽样波动极大。n = 12 时置信区间宽度接近 0.5,而且 p 值对单个样本点非常敏感,删掉一个点就可能从显著变成不显著。
解决:样本量小于 30 时,用肯德尔系数替代皮尔逊或斯皮尔曼,肯德尔在小样本下的稳定性更好。更有说服力的做法是画 Bootstrap 置信区间——对样本做有放回抽样 1000 次,每次重算相关系数,观察 2.5% 到 97.5% 分位数区间。如果区间跨过 0,关系不可靠。
4.3 非线性关系被线性系数完美漏掉
现象:两个变量呈现明显的 U 型关系,画出散点图很漂亮,但皮尔逊系数算出来接近 0,报告里写「变量间无显著相关」。
原因:皮尔逊系数只衡量线性相关,对非线性关系完全失效。U 型关系是典型例子——左半段负相关、右半段正相关,叠加之后相互抵消。
解决:算系数之前先画散点图。如果散点图呈现明显的曲线形态,考虑对变量做变换(对数、平方、倒数)后再算线性相关,或者直接改用斯皮尔曼系数。斯皮尔曼对单调关系有效,但 U 型这种非单调关系依然测不出来——这时需要用距离相关性或互信息这类更通用的指标。
4.4 把相关性当成因果性
现象:分析发现某地区的冰淇淋销量和溺水人数高度相关(r = 0.9),于是得出结论「吃冰淇淋导致溺水」。
原因:这是经典的第三个变量问题——天气炎热同时推高了冰淇淋销量和下水游泳的人数。相关性只说明两个变量之间有联动,不说明谁导致谁,甚至可能没有直接关系。
解决:报告里严格区分「相关」和「因果」。如果需要在建模中验证因果关系,用偏相关分析控制潜在的混淆变量,或者使用 Granger 因果检验(时间序列场景)。但大多数相关性分析场景下,写到「存在显著正相关」就足够了,不要往因果方向过度发挥。
4.5 多重比较不校正,假阳性满天飞
现象:10 个变量的相关性矩阵里有 45 个系数,其中 4 个显著,感觉发现了「多个变量之间的关联网络」,但校正之后全部不显著。
原因:显著水平 0.05 意味着纯随机情况下每 20 个检验就有 1 个假阳性。45 个检验的期望假阳性数是 2.25 个——你看到的 4 个「显著」可能有一半是噪声。
解决:做完整相关矩阵分析时,先做 Bonferroni 校正,再报告结果。或者换一种思路:不用相关性矩阵做全局扫描,而是先根据理论或建模需求指定 3~5 对关键变量,只检验这几对,把多重比较次数降下来。少即是多,预先指定的假设检验比事后扫描更站得住脚。
5. 相关性可视化:从散点图到矩阵热力图
5.1 散点图矩阵:plotmatrix 与自定义组合
相关性分析不能只给数字,数学建模比赛和论文评审都要求可视化。散点图矩阵是展示多变量两两关系最直接的方式——对角线上是每个变量的分布,非对角线是两两散点图。
% 使用plotmatrix绘制散点图矩阵 % data_clean是预处理后的数据,变量数量不要超过6个 variable_names = {'温度', '湿度', '风速', 'PM2.5', '气压'}; figure('Position', [100, 100, 800, 800]); [R_scatter, ax_list] = plotmatrix(data_clean); % 添加变量名标注 for i = 1:size(data_clean, 2) xlabel(ax_list(end, i), variable_names{i}); ylabel(ax_list(i, 1), variable_names{i}); end % 在对角线上方标注相关系数 for i = 1:size(data_clean, 2) for j = (i+1):size(data_clean, 2) r_val = corr(data_clean(:, i), data_clean(:, j)); text(ax_list(i, j).XLim(2)*0.7, ax_list(i, j).YLim(2)*0.9, ... sprintf('r=%.2f', r_val), 'FontSize', 8, 'Color', 'red'); end endplotmatrix 返回的 ax_list 是子图的句柄数组,可以逐个访问调整坐标轴标注。变量名要提前放在 cell 数组里,以免图上只显示「V1、V2」这种没有信息量的标签。在散点图右上角标注相关系数是建模报告的常见做法,比单独给个系数表更直观。
5.2 热力图与聚类:heatmap 与矩阵重排
散点图矩阵适合变量少的情况,变量超过 8 个时图会变得拥挤不清。这时候改用热力图更合适——颜色深浅直接传递相关性强弱,配合聚类重排能发现变量之间的分群结构。
% 绘制相关系数热力图 R_final = corr(data_final, 'Type', 'Pearson'); figure('Position', [100, 100, 700, 600]); % 使用heatmap函数(MATLAB R2017a及以上版本支持) h = heatmap(variable_names, variable_names, R_final, ... 'Colormap', parula, ... 'ColorLimits', [-1, 1], ... 'CellLabelFormat', '%.2f'); h.Title = '变量相关系数矩阵热力图'; h.FontSize = 12; % 对相关系数矩阵做层次聚类并重排 % 将相关系数转成距离度量:距离 = 1 - |r| dist_matrix = 1 - abs(R_final); % 层次聚类 tree = linkage(squareform(dist_matrix), 'average'); leaf_order = optimalleaforder(tree, squareform(dist_matrix)); % 按聚类结果重新排列变量顺序 R_reordered = R_final(leaf_order, leaf_order); variable_names_reordered = variable_names(leaf_order);热力图里把 colorLimits 固定到 [-1, 1] 很重要,否则 MATLAB 会根据数据范围自动缩放色标,导致 0.3 和 0.9 显示为差不多的颜色,误导观察。聚类重排的意义在于把相关性高的变量排在相邻位置,让热力图呈现出明显的块状结构——一眼就能看出哪些变量聚成一群。
5.3 把可视化结果导出为报告素材
建模报告和论文对图片分辨率有要求,直接在 MATLAB 里截图保存的 PNG 往往不够清晰。推荐用 exportgraphics 函数导出 300dpi 以上的图片,而且支持白底背景,适合直接插入文档。
% 导出高清图片 % 确保当前figure是激活的 exportgraphics(gcf, 'correlation_heatmap.png', 'Resolution', 300); % 如果需要在论文中自适应大小,可以导出为矢量图 exportgraphics(gcf, 'correlation_heatmap.pdf', 'ContentType', 'vector');exportgraphics 是处理导出最好的选择,300dpi 足够印刷要求。矢量图 PDF 格式适合插入论文——放大缩小不模糊。注意 exportgraphics 导出的是当前 figure 的完整内容,导出前先用 axis 调整好坐标轴范围,不要出现空白边距过大的问题。另外给图片命名时直接用英文和下划线,中文文件名在部分 LaTeX 编译环境下会出问题。
6. 进阶:偏相关分析——剥离混淆变量后的真实相关
6.1 偏相关分析的基本思想与适用场景
两变量之间显示出的相关性,往往受到第三个变量的间接影响。举个例子:变量 A 与变量 B 的相关系数是 0.7,看起来很强,但如果变量 C 同时驱动 A 和 B,那么这个 0.7 可能是「假的」。偏相关分析做的事情,就是在控制变量 C 的影响后,重新计算 A 与 B 的真实相关性。它通常算的是剔除第三个变量影响后的残差之间的皮尔逊相关。这才是写进报告里最站得住的数字。
6.2 MATLAB 手写偏相关系数:残差化方法
MATLAB 的统计工具箱里其实有 partialcorr 函数,但很多建模场景下拿不到工具箱授权。我自己更习惯用残差化思路手写偏相关——把 A 对 C、B 对 C 分别做线性回归,取残差,再求残差之间的皮尔逊相关。两者结果一致,而且代码完全透明,评审追问细节时不心虚。
% 手写偏相关系数:控制Z后计算X与Y的相关 % X, Y, Z均为列向量,长度相同 function [r_partial, p_partial] = partial_corr_manual(X, Y, Z) % 步骤1:对X和Y分别关于Z做线性回归,取残差 n = length(X); Z_design = [ones(n, 1), Z]; % 加一列截距项 % 回归X~Z beta_XZ = Z_design \ X; resid_X = X - Z_design * beta_XZ; % 回归Y~Z beta_YZ = Z_design \ Y; resid_Y = Y - Z_design * beta_YZ; % 步骤2:计算残差的皮尔逊相关系数 [r_partial, p_partial] = corr(resid_X, resid_Y); end % 示例:控制温度和湿度后,分析风速与PM2.5的相关 % 假设data_final列为:[温度, 湿度, 风速, PM2.5] X = data_final(:, 3); % 风速 Y = data_final(:, 4); % PM2.5 Z = [data_final(:, 1), data_final(:, 2)]; % 控制温度和湿度 [r_raw, p_raw] = corr(X, Y); % 原始相关 [r_part, p_part] = partial_corr_manual(X, Y, Z); % 偏相关 fprintf('原始相关系数:%.3f(p=%.3f)\n', r_raw, p_raw); fprintf('偏相关系数:%.3f(p=%.3f)\n', r_part, p_part); if abs(r_part) < abs(r_raw) fprintf('控制Z后相关性下降,说明Z存在混淆效应\n'); end偏相关分析的结论判断是:如果偏相关系数明显小于原始相关系数,说明原始相关部分是由控制变量驱动的。比如原始 r = 0.7、偏相关 r = 0.2,那 X 和 Y 的「真正关系」其实很弱。如果偏相关依然显著且数值变化不大,X 和 Y 的关系是独立存在的。这个思路在建模报告的「稳健性检验」部分非常好用。
现在距离完成内容只剩一步——如果正文组织唯恐落后于实际,把偏相关代码段拿去做实际数据验证。我一般习惯的做法,是把偏相关分析写成函数存成独立的 .m 文件,每次拿到新数据直接调用,省去反复复制粘贴。用偏相关分析校验过每一条核心相关性关系之后,报告里写结论能踏实很多——至少不用怕评委问一句「那第三个变量呢?」,一句话就能答上来,方案有据、系数能复现、有解释力,希望帮到你。
本文还有配套的精品资源,点击获取