You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:00:58