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

如何在ggplot中交换双Y轴的左右位置?

ggplot双Y轴:变量分配的决策原则与实践

核心决策逻辑

选择变量对应左右Y轴,核心围绕阅读习惯和图表核心目标,以下是具体原则:

  • 「绝对数值类指标」(如种群规模TotalAlive)优先放左侧主Y轴:读者对绝对数量的感知更直观,左侧作为默认视觉焦点,承载核心信息。
  • 「比例/相对值类指标」(如年龄组占比Proportion*)放右侧次Y轴:相对值是对主指标的补充分析,放在次轴既避免干扰主信息,又能关联对比。
  • 若两类指标都是绝对值,优先把波动幅度更大、更核心的指标放在左侧:避免线条因轴范围被压缩,保证视觉清晰度。

结合你的代码调整

你的代码逻辑方向正确,只需修正几处变量名错误,并明确变量分配的合理性:

  • 把TotalAlive(种群规模,绝对数)放在左侧主轴,作为核心展示指标;
  • 把各年龄组占比通过* max(TotalAlive)缩放后映射到主轴范围,再通过sec_axis(~ . / max(Ind_Count_Year$TotalAlive))转换为比例,放在右侧次轴。

修正后的完整代码:

##### 定义并标记年龄组!!!####
data$AgeGroup <- case_when(
  data$Age_y >= 0 & data$Age_y < 2 ~ "Juvenile", 
  data$Age_y >= 2 & data$Age_y < 7 ~ "Sub-adult",
  data$Age_y >= 7 ~ "Adult",
  TRUE ~ NA_character_ # 处理缺失或无效年龄情况
)

# 创建数据框以追踪每年存活动物数量
years <- seq(from = as.numeric(format(min(data$BirthDate), "%Y")), to = as.numeric(format(DateDownload, "%Y")), by = 1)
Ind_Count_Year <- data.frame(Year = integer(), TotalAlive = integer(), Juveniles = integer(), SubAdults = integer(), Adults = integer(), 
                         ProportionJuveniles = numeric(), ProportionSubAdults = numeric(), ProportionAdults = numeric())
for (year in years) {
  # 筛选当年存活的动物(出生年份≤当年且消失年份为NA或≥当年则存活)
  alive_in_year <- data %>% 
    filter(format(BirthDate, "%Y") <= year & (is.na(GoneDate) | format(GoneDate, "%Y") >= year))
  # 统计各年龄组个体数量
  juveniles <- sum(alive_in_year$AgeGroup == "Juvenile")
  subadults <- sum(alive_in_year$AgeGroup == "Sub-adult")
  adults <- sum(alive_in_year$AgeGroup == "Adult")
  # 存活动物总数
  total_alive <- juveniles + subadults + adults
  # 各年龄组占比
  proportion_juveniles <- juveniles / total_alive
  proportion_subadults <- subadults / total_alive
  proportion_adults <- adults / total_alive
  # 将数据添加至数据框(修正变量名错误:alive_data → Ind_Count_Year)
  Ind_Count_Year <- rbind(Ind_Count_Year, data.frame(Year = year, TotalAlive = total_alive, Juveniles = juveniles, SubAdults = subadults, Adults = adults, 
                                             ProportionJuveniles = proportion_juveniles, ProportionSubAdults = proportion_subadults, ProportionAdults = proportion_adults))
}

# 查看生成的数据框(可选)
print(Ind_Count_Year)

# 绘制每年存活动物数量及各年龄组占比的双Y轴图
ggplot(Ind_Count_Year, aes(x = Year)) + 
  geom_line(aes(y = ProportionJuveniles * max(Ind_Count_Year$TotalAlive), color = "Juveniles (<2yrs)"), size = 1.75) + 
  geom_line(aes(y = ProportionSubAdults * max(Ind_Count_Year$TotalAlive), color = "Sub-adults (>2yrs to 7yrs)"), size = 1.75) + 
  geom_line(aes(y = ProportionAdults * max(Ind_Count_Year$TotalAlive), color = "Adults (>7yrs)"), size = 1.75) +
  geom_line(aes(y = TotalAlive, color = "Pop. Size"), size = 1.25, linetype = "twodash") + 
  labs(title = "Population History") + # 添加标题与标签
  scale_y_continuous(
    name = "Population Size", 
    expand = c(0, 0), 
    breaks = waiver(), 
    n.breaks = 12, 
    # 修正变量名错误:alive_data$TotalAlive → Ind_Count_Year$TotalAlive
    sec.axis = sec_axis(~ . / max(Ind_Count_Year$TotalAlive), name = "% of Total Population")
  ) +
  scale_color_manual(values = c("Pop. Size" = "black","Juveniles (<2yrs)" = "#FFD966", "Sub-adults (>2yrs to 7yrs)" = "#70AD47","Adults (>7yrs)" = "#ED7D31")) +
  theme_minimal() + 
  theme(
    plot.title = element_text(hjust = 0.5),
    legend.position = "bottom",
    legend.title = element_blank(),
    axis.line = element_line(linewidth = 1),
    axis.ticks = element_line(linewidth = 1),
    panel.grid.minor = element_blank()  # 移除次要网格线
  ) +
  scale_x_continuous(name = "Year", breaks = seq(1950, max(Ind_Count_Year$Year)+1, by = 5), limits = c(1970, NA))

数据示例

YearTotalAliveJuvenilesSubAdultsAdultsProportionJuvenilesProportionSubAdultsProportionAdults
194940310.000000000.750000000.2500000
195040310.000000000.750000000.2500000
195140310.000000000.750000000.2500000
195251310.200000000.600000000.2000000
195350320.000000000.600000000.4000000

内容的提问来源于stack exchange,提问作者JPM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:27:03