MATLAB | MATLAB 也可以画 Mantel test 相关性热图了?

绘制效果

代码说明

Mantel test 相关性热图的构造复刻自 R 语言代码:

  • Houyun Huang(2021). linkET: Everything is Linkable. R package version 0.0.3.

我为我开发的 MATLAB 绘图工具 SHeatmap 添加了新的辅助函数 SMantelLink 使得 MATLAB 也能具备绘制这样图像的能力。因此想要绘制首先需要下载工具函数包,可在以下仓库获取:

Mantel test

在给出绘图教程前,先给出 Mantel test 的原理和代码实现,这样大家可以在其他地方使用 Mantel test。

Mantel test 被用来检验两个距离矩阵的相关性,基本实现是:将两个矩阵下三角部分(不含对角线)拉成向量计算相关系数 r, 随机同时交换距离矩阵2的行列,之后将矩阵2下三角部分再次拉成长条,对两个列向量进行相关性检验,记录 r p r e m r_{prem} rprem 值,看有多少次因为交换行列导致 r p r e m r_{prem} rprem 值对比 r 值上升,计算上升次数/总次数作为 p 值,同时为了和主流计算保持一致(如 R 语言的 vegan 包),采用加 1 校正:

r 值越高说明相关性越强,p 值越低说明越显著。以下是 Mantel test 实现代码 mantel,工具包还有一个拿存储空间换运行速度的 mantelFast 函数,运行速度是下面这个函数的10倍,可以工具包里瞅瞅。

matlab 复制代码
function [rho, pval, rperm] = mantel(dist1, dist2, varargin)
% MANTEL - Mantel test for matrix correlation
%   rho = mantel(dist1, dist2) performs Mantel test with 999 permutations
%   using Pearson correlation.
%
%   rho = mantel(dist1, dist2, nperm) specifies number of permutations.
%
%   rho = mantel(dist1, dist2, method) specifies correlation method
%         ('Pearson' or 'Spearman', default 'Pearson').
%
%   rho = mantel(dist1, dist2, nperm, method) sets both.
%
%   [rho, pval] = mantel(___) also returns the p-value (one-sided).
%
%   [rho, pval, rperm] = mantel(___) returns the permuted r values.

% References
% [1] Mantel N. The detection of disease clustering and a generalized regression approach. 
%     Cancer Res. 1967 Feb;27(2):209-20. PMID: 6018555.
% [2] Borcard, D. & Legendre, P. (2012) Is the Mantel correlogram powerful enough to be 
%     useful in ecological analysis? A simulation study. Ecology 93: 1473-1481.
% [3] Legendre, P. and Legendre, L. (2012) Numerical Ecology. 3rd English Edition. Elsevier.

% Parse input arguments
switch nargin
    case 2
        nperm = 999; method = 'Pearson';
    case 3
        if isnumeric(varargin{1})
            nperm = varargin{1}; method = 'Pearson';
        else
            nperm = 999; method = varargin{1};
        end
    case 4
        nperm = varargin{1}; method = varargin{2};
end
% Extract lower-triangular vectors
N = size(dist1, 1);
ind = tril(true(N), -1);
V1 = dist1(ind);
V2 = dist2(ind);
% Observed correlation
rho = corr(V1, V2, 'Type', method);
% Permutation test
rperm = zeros(nperm, 1);
for i = 1:nperm
    perm = randperm(N);
    dist2_perm = dist2(perm, perm);
    V2_perm = dist2_perm(ind);
    rperm(i) = corr(V1, V2_perm, 'Type', method);
end
% p-value (one-sided, positive correlation)
pval = (sum(rperm >= rho) + 1) / (nperm + 1);
end

以下说明该代码计算结果与 R 语言相同:

MATLAB 计算

matlab 复制代码
rng(1)
X1 = rand([100, 20]);
X2 = rand([100, 5]);
writematrix(X1, 'X1.csv');
writematrix(X2, 'X2.csv');

D1 = squareform(pdist(X1));
D2 = squareform(pdist(X2));
[rho, pval] = mantel(D1, D2, 9999, 'Pearson')

计算得到:rho = 0.0438, pval = 0.0827

R 语言计算

r 复制代码
dataX1 <- read.csv("X1.csv", header = FALSE, row.names = NULL)
dataX2 <- read.csv("X2.csv", header = FALSE, row.names = NULL)

D1 <- dist(dataX1)
D2 <- dist(dataX2)

result <- mantel(D1, D2, method = "pearson", permutations = 9999)
print(result)

计算得到:Mantel statistic r: 0.04375, Significance: 0.0828 , 计算结果相似。当然 Mantel test 具有随机性,建议加大随机置换次数并设置随机数种子。

mantelFast 函数就是把循环部分完全换成 MATLAB 矩阵运算:但是在数据量超过 2e8 时容易会内存不足:

matlab 复制代码
% Permutation test
[~, perm] = sort(rand(N, nperm), 1);
[indR, indC] = find(tril(true(N), -1));
V2 = dist2(sub2ind([N, N], perm(indR, :), perm(indC, :)));
rperm = corr(V1, V2, 'Type', method);

Mantel test 相关性热图

数据准备

数据来源:

  • Väre, H., Ohtonen, R. and Oksanen, J. (1995) Effects of reindeer grazing on understorey vegetation in dry Pinus sylvestris forests. Journal of Vegetation Science 6, 523--530.
matlab 复制代码
%% Load data (加载数据)
load('lichenData.mat')                  % Load pre-saved data package (加载预存的数据包)
Data1 = varechem.Variables;             % Environmental matrix (环境因子矩阵)
Data2 = varespec.Variables;             % Species composition matrix (物种组成矩阵)
labels = varechem.Properties.VariableNames; % Environmental variable names (环境变量名称)

% Define species groups: 44 columns into 4 groups (将44个物种列分为4组)
groupName = {'Spec01', 'Spec02', 'Spec03', 'Spec04'};
group = zeros(1, size(Data2, 2));
group(1:7) = 1;    % Group 1: columns 1-7 (第1组:列1-7)
group(8:18) = 2;   % Group 2: columns 8-18 (第2组:列8-18)
group(19:37) = 3;  % Group 3: columns 19-37 (第3组:列19-37)
group(38:44) = 4;  % Group 4: columns 38-44 (第4组:列38-44)

热图绘制

matlab 复制代码
ax = gca; 

%% Draw heatmap
[rho, pval] = corr(Data1);
objHM = SHeatmap(ax, rho, 'Format','sq');
objHM.draw();

% Display significance stars: p < 0.05 *, p < 0.01 **, p < 0.001 *** 
objHM.setText()
objHM.showStars(pval, 'Levels', [0.05, 0.01, 0.001], 'CorrLabel','off')

objHM.setVarName(labels)             % Set row/column labels (设置行列标签)
objHM.setType('tril0');              % Show only lower triangle without diagonal (仅显示下三角矩阵,不含对角线)
% Adjust label positions (调整标签位置)
objHM.setRowLabelLocation('left')    
objHM.setColLabelLocation('bottom')
% Show all labels, including those that were previously hidden. (显示所有标签,包括被隐藏的标签)
objHM.setRowLabel('Visible','on')
objHM.setColLabel('Visible','on')
delete(objHM.Colorbar)

Mantel 链接绘制

matlab 复制代码
% Create Mantel link object with env data, species data and groups (创建Mantel链接对象,传入环境数据、物种数据及分组信息)
objML = SMantelLink(ax, Data1, Data2, 'Group',group);
objML.GroupName = groupName;          % Set group names (设置组名)
objML.LegendLocation = 'west';        % Place legend on the left (图例置于左侧)
objML.Layout = 'triu';                % Links placed in upper triangle (链接采用上三角布局)
objML.draw()                          % Render the links (渲染链接图)

draw() 之前很多参数都可以设置,比如为了每次结果一致,建议提前设置随机数种子,且可以设置更大的置换次数:

matlab 复制代码
rng(123456)
objML.NumPerm = 9999;

再比如计算距离矩阵的方式 (以下是默认计算方式),更多计算方式参见 MATLAB 官网 pdist 函数的文档,这里不赘述:

matlab 复制代码
objML.Distance1 = 'euclidean';
objML.Distance2 = @(ZI, ZJ) sum(abs(ZI - ZJ), 2) ./ sum(ZI + ZJ, 2);

同时 Curvature 可以设置链接线的弯曲程度和方向,比如为负数就是反向弯曲,为0就是直线,LinkBendMode,可以设置为 simple,就是所有线弯曲方向一致:

复制代码
objML.Curvature = 0;
matlab 复制代码
objML.Curvature = -1/3;
objML.LinkBendMode = 'simple';

左下角布局与字体、配色修改

matlab 复制代码
%% Figure and axes
fig = figure('Units','normalized', 'Position',[.05,.15,.72,.72]); 
ax = axes('Parent',fig, 'Position',[.06,.05,.88,.9]); 

%% Draw heatmap
[rho, pval] = corr(Data1);
objHM = SHeatmap(ax, rho, 'Format','sq');
objHM.draw();

objHM.setText()
objHM.showStars(pval, 'Levels', [0.05, 0.01, 0.001], 'CorrLabel','off')
objHM.setVarName(labels)
objHM.setType('triu0');
objHM.setRowLabelLocation('right')
objHM.setColLabelLocation('top')
objHM.setRowLabel('Visible','on')
objHM.setColLabel('Visible','on')
delete(objHM.Colorbar)

% Apply a custom colormap with 25 colors (应用自定义 25 色 colormap)
colormap(slanCM(102, 25))
% Adjust font properties for labels (调整标签字体)
set([objHM.rowLabelHdl, objHM.colLabelHdl], 'FontSize',14, 'FontName','Helvetica')


%% Draw mantel links
objML = SMantelLink(ax, Data1, Data2, 'Group',group);
objML.GroupName = groupName;
objML.Layout = 'tril';

% Customize colors (自定义颜色)
objML.PColor = [0,64,115; 79,148,204; 224,224,224]./255;
objML.NodeColor1 = [184,207,248]./255;
objML.NodeColor2 = [184,207,248]./255;

objML.draw()

% Adjust legend and group label fonts (调整图例和组标签字体)
set(objML.legendTitleHdl, 'FontName','Helvetica')
set(objML.legendTickLabelHdl, 'FontSize',13, 'FontName','Helvetica')
set(objML.groupLabelHdl, 'FontSize',14, 'FontName','Helvetica')

以上绘图代码需要下载工具函数才能运行: