前言
最近在做某类生态现象的环境驱动因子,恰好被这篇论文的图片吸引。图片虽然看着复杂,仔细分析拆解,其实也就是平常的那些主要的图杂糅在一起,现特将复刻的代码放出来,供有需要的人参考。
复刻图片如下:

图片来源
Fredston, A.L., Cheung, W.W.L., Frölicher, T.L. et al. Marine heatwaves are not a dominant driver of change in demersal fishes. Nature 621, 324--329 (2023). https://doi.org/10.1038/s41586-023-06449-y
原图信息:
- x:MHW cumulative intensity(海洋热浪累积强度,℃・days)
- a 图 y:Biomass log ratio;b 图 y:Absolute biomass log ratio
- 线性回归 R²≈0.00/0.01,P 不显著,拟合线几乎水平
- 密度图:红色(MHW),蓝色 (无 MHW)
首先来讲讲这个图叫什么名字,有什么用。
边际密度散点回归图将(散点 + 线性拟合 + 置信带 + 边缘 KDE 密度)本质上把「关系」和「分布」压缩进一张画布。
核心优势------信息密度高且不牺牲细节:一张图里同时承载个体观测、整体趋势、估计不确定性和分布形态,特别适合用来论证「某个环境因子并非主要驱动因素」这类需要同时展示「趋势」和「分布」的结论。
复刻思路
图中主要要素
- 主坐标轴:散点 + 线性拟合直线 + 灰色 95% 置信区间填充 + 重点样本箭头标注(text + 箭头)
- 右侧窄坐标轴:y 方向的 KDE 核密度曲线,分两组(红色 = 发生 MHW,蓝色 = 无 MHW)
复刻所需数据
1.事件总量
2.有MHW次数
3.无MHW次数
4.每次事件的MHW累积强度
5.每次事件的生物量(取对数)
6.事件标记,1=有MHW,0=无MHW
|--------|---------|--------|
| 事件标记 | MHW累积强度 | 生物量 |
| 1 | 25 | 0.2 |
| 0 | 0 | |
| ...... | ...... | ...... |
| 1 | 30 | 0.3 |
复刻方法
首先生成相关数据。
Matlab
n_total = 369;
n_mhw = 139; %有MHW
n_no = 230; %无MHW
x = rand(n_total,1)*80; %MHW累积强度
y = randn(n_total,1)*0.4; %biomass log ratio
mhw_flag = [ones(n_mhw,1);zeros(n_no,1)]; %1=有MHW,0=无MHW
设置画布。
Matlab
fig = figure('Position',[100,100,1200,600],'Color','w');
ax_main = axes('Position',[0.06,0.12,0.75,0.82]);
ax_kde = axes('Position',[0.82,0.12,0.12,0.82]);
hold(ax_main,'on');
hold(ax_kde,'on');
左侧主图 先画散点图。
Matlab
scatter(ax_main, x, y, 22, 'k','filled','MarkerFaceAlpha',0.8);
然后是拟合。
Matlab
tbl = table(x,y,'VariableNames',{'x','y'});
mdl = fitlm(tbl,'y~x');
x_fit = linspace(min(x),max(x),200)';
[y_pred,ci] = predict(mdl,table(x_fit,'VariableNames',{'x'}));
接着是置信区间。
Matlab
patch(ax_main,[x_fit;flipud(x_fit)],[ci(:,1);flipud(ci(:,2))],...
[0.7 0.7 0.7],'FaceAlpha',0.4,'EdgeColor','none');
再加上一条拟合中线,即黑色直线,表示变化趋势。
Matlab
plot(ax_main, x_fit, y_pred, 'k-','LineWidth',1.4);
最后再放一个零点虚线,左侧主图完成!
Matlab
yline(ax_main,0,'k--','LineWidth',0.8);
右侧KDE密度图,需要调用函数 ksdensity计算。
Matlab
y_mhw = y(mhw_flag==1);
y_no = y(mhw_flag==0);
[f_mhw,yi_mhw] = ksdensity(y_mhw);
[f_no,yi_no] = ksdensity(y_no);
然后就是填充曲线,填充之前做了一点缩放。
Matlab
% 缩放密度宽度
scale_kde = 12;
% 红色:MHW
fill(ax_kde, f_mhw*scale_kde, yi_mhw, [0.75,0.2,0.3],'linestyle','none','FaceAlpha',0.6,'Marker','none');
plot(ax_kde, f_mhw*scale_kde, yi_mhw, 'r-','LineWidth',1.2);
% 蓝色:无MHW
fill(ax_kde, f_no*scale_kde, yi_no, [0.2,0.4,0.75],'linestyle','none','FaceAlpha',0.4,'Marker','none');
plot(ax_kde, f_no*scale_kde, yi_no, 'b-','LineWidth',1.2);
最后美化坐标轴。
Matlab
xlabel(ax_main,'MHW cumulative intensity (°C-days)');
ylabel(ax_main,'Biomass log ratio');
box(ax_main,'on');
xlim(ax_main,[0,80]);
set(ax_main,'linewidth',1.5,'fontsize',14,'fontname','Times New Roman');
xlim(ax_kde,[0,max([f_mhw,f_no])*scale_kde]);
ylim(ax_kde, ylim(ax_main));
set(ax_kde,'XColor','none','YColor','none','YTickLabel','');
复刻结果
Tada,搞定!

完整版代码
Matlab
clear; clc; close all;
rng(42); %固定随机种子
% ========== 模拟数据 ==========
n_total = 369;
n_mhw = 139; %有MHW
n_no = 230; %无MHW
x = rand(n_total,1)*80; %MHW累积强度
y = randn(n_total,1)*0.4; %biomass log ratio
mhw_flag = [ones(n_mhw,1);zeros(n_no,1)]; %1=有MHW,0=无MHW
%% ========== 作图 ==========
fig = figure('Position',[100,100,1200,600],'Color','w');
ax_main = axes('Position',[0.06,0.12,0.75,0.82]);
ax_kde = axes('Position',[0.82,0.12,0.12,0.82]);
hold(ax_main,'on');
hold(ax_kde,'on');
% ---- 左侧主图 ----
scatter(ax_main, x, y, 22, 'k','filled','MarkerFaceAlpha',0.8);
% ---- 线性拟合 + 95%置信区间 ----
tbl = table(x,y,'VariableNames',{'x','y'});
mdl = fitlm(tbl,'y~x');
x_fit = linspace(min(x),max(x),200)';
[y_pred,ci] = predict(mdl,table(x_fit,'VariableNames',{'x'}));
% 置信区间
patch(ax_main,[x_fit;flipud(x_fit)],[ci(:,1);flipud(ci(:,2))],...
[0.7 0.7 0.7],'FaceAlpha',0.4,'EdgeColor','none');
% 拟合中线
plot(ax_main, x_fit, y_pred, 'k-','LineWidth',1.4);
% y=0虚线
yline(ax_main,0,'k--','LineWidth',0.8);
% ---- 右侧KDE密度图 ----
y_mhw = y(mhw_flag==1);
y_no = y(mhw_flag==0);
[f_mhw,yi_mhw] = ksdensity(y_mhw);
[f_no,yi_no] = ksdensity(y_no);
% 缩放密度宽度
scale_kde = 12;
% 红色:MHW
fill(ax_kde, f_mhw*scale_kde, yi_mhw, [0.75,0.2,0.3],'linestyle','none','FaceAlpha',0.6,'Marker','none');
plot(ax_kde, f_mhw*scale_kde, yi_mhw, 'r-','LineWidth',1.2);
% 蓝色:无MHW
fill(ax_kde, f_no*scale_kde, yi_no, [0.2,0.4,0.75],'linestyle','none','FaceAlpha',0.4,'Marker','none');
plot(ax_kde, f_no*scale_kde, yi_no, 'b-','LineWidth',1.2);
hold on
% ---- 坐标轴设置 ----
xlabel(ax_main,'MHW cumulative intensity (°C-days)');
ylabel(ax_main,'Biomass log ratio');
box(ax_main,'on');
xlim(ax_main,[0,80]);
set(ax_main,'linewidth',1.5,'fontsize',14,'fontname','Times New Roman');
xlim(ax_kde,[0,max([f_mhw,f_no])*scale_kde]);
ylim(ax_kde, ylim(ax_main));
set(ax_kde,'XColor','none','YColor','none','YTickLabel','');