ggplot2中geom_bar双Y轴设置:解决PBIAS柱子显示异常
双Y轴柱状图显示修复方案
需求是绘制双Y轴柱状图:
- 左Y轴对应NSE、KGE、VE指标,取值范围0-1
- 右Y轴对应PBIAS指标,取值范围-15至5
初始代码可生成正常单Y轴柱状图,但尝试通过缩放平移设置双Y轴时,scale_y_continuous(limits = c(0,1))会直接删除超出范围的PBIAS数据,导致PBIAS柱子被移除。
方案1:数据转换+坐标裁剪(推荐)
核心思路:先将PBIAS的数值转换到左Y轴的0-1范围,再绘制图表,最后通过sec_axis映射回原始PBIAS刻度;同时用coord_cartesian裁剪显示范围(而非删除数据)。
完整代码:
library("ggplot2") ## 原始数据 data <- data.frame( value=c(0.82,0.87,0.65,-3.39,0.75,0.82,0.63,1.14,0.85,0.87,0.67,-7.03), sd=c(0.003,0.047,0.006,4.8,0.003,0.028,0.006,4.77,0.004,0.057,0.014,4.85), index=c("NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS"), period=c("all","all","all","all","calibration","calibration","calibration","calibration","validation","validation","validation","validation") ) ## 固定因子顺序 data$index <- factor(data$index, levels = c('NSE','KGE','VE',"PBIAS")) data$period <- factor(data$period, levels = c('all','calibration', 'validation')) ## 转换PBIAS数据到左Y轴范围(0-1):(PBIAS +15)/20 data$y_trans <- ifelse(data$index == "PBIAS", (data$value + 15)/20, data$value) data$sd_trans <- ifelse(data$index == "PBIAS", data$sd/20, data$sd) ## 绘制双Y轴柱状图 ggplot(data, aes(x=index, y=y_trans, fill=period))+ geom_bar(position="dodge", stat="identity")+ geom_errorbar(aes(ymin=y_trans - sd_trans, ymax=y_trans + sd_trans), position = position_dodge(0.9), width=0.2 ,alpha=0.5, size=1)+ theme_bw()+ scale_y_continuous( name = "NSE/KGE/VE", breaks = seq(0,1,0.2), sec.axis = sec_axis( ~ .*20 -15, name="PBIAS", breaks = seq(-15,5,5) ) )+ # 裁剪Y轴显示范围,不删除超出数据 coord_cartesian(ylim = c(0,1))
方案2:分图层绘制不同指标
如果不想修改原始数据,可以分别绘制两组指标的柱子,手动指定PBIAS的Y轴映射:
library("ggplot2") ## 原始数据 data <- data.frame( value=c(0.82,0.87,0.65,-3.39,0.75,0.82,0.63,1.14,0.85,0.87,0.67,-7.03), sd=c(0.003,0.047,0.006,4.8,0.003,0.028,0.006,4.77,0.004,0.057,0.014,4.85), index=c("NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS"), period=c("all","all","all","all","calibration","calibration","calibration","calibration","validation","validation","validation","validation") ) ## 固定因子顺序 data$index <- factor(data$index, levels = c('NSE','KGE','VE',"PBIAS")) data$period <- factor(data$period, levels = c('all','calibration', 'validation')) # 分离数据 data_norm <- subset(data, index != "PBIAS") data_pbias <- subset(data, index == "PBIAS") data_pbias$y_trans <- (data_pbias$value +15)/20 data_pbias$sd_trans <- data_pbias$sd/20 ggplot()+ # 绘制NSE/KGE/VE geom_bar(data=data_norm, aes(x=index, y=value, fill=period), position="dodge", stat="identity")+ geom_errorbar(data=data_norm, aes(x=index, ymin=value-sd, ymax=value+sd), position=position_dodge(0.9), width=0.2, alpha=0.5, size=1)+ # 绘制PBIAS geom_bar(data=data_pbias, aes(x=index, y=y_trans, fill=period), position="dodge", stat="identity")+ geom_errorbar(data=data_pbias, aes(x=index, ymin=y_trans-sd_trans, ymax=y_trans+sd_trans), position=position_dodge(0.9), width=0.2, alpha=0.5, size=1)+ theme_bw()+ scale_y_continuous( name = "NSE/KGE/VE", breaks = seq(0,1,0.2), sec.axis = sec_axis( ~ .*20 -15, name="PBIAS", breaks = seq(-15,5,5) ) )+ coord_cartesian(ylim=c(0,1))
关键说明
- 避免使用
scale_y_continuous(limits)过滤数据:该参数会直接删除超出范围的观测值,导致PBIAS柱子消失;改用coord_cartesian(ylim)仅裁剪显示范围,保留所有数据。 - 数据转换逻辑:PBIAS的取值范围(-15,5)与左Y轴(0,1)的线性转换公式为:
- 左Y轴值 = (PBIAS值 +15)/20
- PBIAS值 = 左Y轴值*20 -15
- 误差棒同步转换:PBIAS的标准差也要除以20,确保误差范围的显示与原始数据匹配。
内容的提问来源于stack exchange,提问作者sinyu
相关产品推荐
相关产品推荐

