我第一次在Nature子刊上看到双轴组合图时,就被它的信息密度震撼到了。这种将柱状图和折线散点图结合的可视化方式,完美解决了我在展示两组量纲不同数据时的困境。比如在研究病毒传播规律时,左侧纵轴可以显示病例数量(单位:例),右侧纵轴能同时呈现阳性率(单位:%),两组数据的时间维度还能完美对齐。
这种图表最厉害的地方在于,它让读者一眼就能发现数据间的关联性。我去年投稿时,审稿人特别指出这种呈现方式比分开两个图表更直观。实际操作中,R语言的ggplot2包提供了原生支持,通过sec_axis()函数就能轻松创建第二个坐标轴。不过要注意,两组数据的量级差异不宜超过100倍,否则会导致某一侧的图形被压缩变形。
拿到原始数据的第一件事,是用tidyverse进行数据清洗。我通常会创建一个包含以下字段的数据框:
r复制library(tidyverse)
cData <- read_csv("research_data.csv") %>%
mutate(
Week = as.factor(Week),
Group = fct_relevel(Group, "Control", "Treatment")
)
在绘制组合图前,必须预先计算好各组统计量。我吃过亏,曾经直接在图上计算均值,结果误差棒显示异常。正确的做法是先用dplyr完成聚合计算:
r复制data_summary <- cData %>%
group_by(Week, Group) %>%
summarise(
mean_value1 = mean(Value1),
sd_value1 = sd(Value1),
mean_value2 = mean(Value2),
sd_value2 = sd(Value2),
.groups = "drop"
)
这是最容易被忽视的步骤。当两组数据量级差异较大时(如病例数在千级,阳性率在百分级),需要确定缩放系数。我的经验法则是:让两组数据的最大值在图表中的视觉高度相近。比如若病例数最大4000,阳性率最大2%,那么缩放系数可以设为2000(4000/2)。
先绘制左侧纵轴对应的柱状图主体。这里有个实用技巧:使用position_dodge()让不同组别的柱子并排显示,同时用alpha参数调整透明度以便后续叠加其他元素。
r复制base_plot <- ggplot(data_summary) +
geom_col(
aes(x = Week, y = mean_value1, fill = Group),
position = position_dodge(0.8),
width = 0.7,
alpha = 0.6
) +
geom_errorbar(
aes(
x = Week,
ymin = mean_value1 - sd_value1,
ymax = mean_value1 + sd_value1,
group = Group
),
position = position_dodge(0.8),
width = 0.2
)
接下来叠加右侧纵轴对应的元素。关键点在于:要将原始数据乘以之前确定的缩放系数。我习惯用不同颜色区分组别,并用linewidth参数加粗折线。
r复制combo_plot <- base_plot +
geom_line(
aes(
x = Week,
y = mean_value2 * 2000,
color = Group,
group = Group
),
position = position_dodge(0.8),
linewidth = 1.2
) +
geom_point(
aes(
x = Week,
y = mean_value2 * 2000,
color = Group
),
position = position_dodge(0.8),
size = 3
)
这是最关键的步骤。通过scale_y_continuous()的sec.axis参数创建第二个坐标轴,需要指定反向缩放公式。注意要确保两个轴的比例尺协调。
r复制final_plot <- combo_plot +
scale_y_continuous(
name = "Case Number (n)",
sec.axis = sec_axis(~ . / 2000, name = "Positive Rate (%)"),
expand = expansion(mult = c(0, 0.1))
)
顶级期刊偏爱柔和而不失对比的配色。我常用的方案是:
r复制final_plot +
scale_fill_manual(values = c("#FE8F3C80", "#1E899A80")) +
scale_color_manual(values = c("#FE8F3C", "#1E899A"))
Nature图表的典型特征包括:
r复制final_plot +
theme_classic() +
theme(
axis.line = element_line(linewidth = 0.5),
axis.text = element_text(size = 10, family = "Arial"),
axis.title.y.left = element_text(color = "black", margin = margin(r = 10)),
axis.title.y.right = element_text(color = "#1E899A", margin = margin(l = 10))
)
将图例放置在图表右上角是常见做法。我还会:
r复制final_plot +
theme(
legend.position = c(0.85, 0.9),
legend.spacing.y = unit(0.2, "cm")
) +
guides(
fill = guide_legend(title = NULL),
color = guide_legend(title = NULL)
)
当发现两个y轴的刻度线对不齐时,需要检查:
我常用的调试方法是先隐藏一个轴,检查单轴时的显示效果,再逐步添加第二个轴。
ggplot2默认会为fill和color美学分别生成图例。要合并它们可以:
r复制combo_plot +
guides(
fill = guide_legend(override.aes = list(color = c("#FE8F3C", "#1E899A"))),
color = "none"
)
期刊投稿通常要求600dpi以上的TIFF格式。我推荐使用ggsave配合特定参数:
r复制ggsave(
"figure.tiff",
plot = final_plot,
device = "tiff",
dpi = 600,
width = 8,
height = 6,
units = "cm"
)
对于需要在线展示的情况,可以用plotly包转换为交互图表。特别提醒:要先在ggplot2中完成所有静态调整,因为转换后某些美学效果会丢失。
r复制library(plotly)
ggplotly(final_plot) %>%
layout(
hovermode = "x unified",
yaxis2 = list(showgrid = FALSE)
)
在最后的图表优化阶段,我会打印出1:1大小的样张,放在桌面上退后两步观察整体效果。这个土方法帮我发现了不少在屏幕上难以察觉的细节问题,比如刻度标签过密或图例遮挡数据。