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

如何在ggplot直方图中设置可变坐标轴范围、易读刻度及动态标注?

解决方案

核心问题排查与修复

1. 数据处理语法错误

原代码中dplyr::mutate(fire_zone == 5)使用了比较运算符==,会生成逻辑值列,应改为赋值运算符=:

dat3 <- data.frame(new_year, new_burn_size) %>%
  mutate(fire_zone = 5)

2. 硬编码y轴范围导致适配失败

你手动设置limits=c(0,150)完全脱离了测试数据的实际频率(测试数据最大频率为7),直接导致柱子被压缩到几乎不可见。需要去掉硬编码,改用动态计算的方式:

  • 用scales::pretty_breaks()自动生成易读刻度(如0、5、10这类规整数值)
  • 用expand参数给y轴顶部预留空间,避免标注超出图表范围

3. 垂直线标注位置动态化

直接写死y=150这类数值无法适配不同数据集,应基于当前数据的最大频率按比例设置标注位置,比如取最大频率的95%、85%等。

4. 语法错误修正

中位数的geom_text行多了一个闭合括号,导致代码报错,需修正。


完整修正代码

library(ggplot2)
library(dplyr)
library(scales)

# 生成测试数据
new_year <- c(2009,2009,1988,1989,1991,2001,2008,2015,2020,1997,2012,2002,2012)
new_burn_size <- c(1.0,1.0,1.0,1.0,1.2,1.2,7.0,100.0,16711.0,18482.0,48400.0,48400.0,50727.1)
dat3 <- data.frame(new_year, new_burn_size) %>%
  mutate(fire_zone = 5)

# 预计算关键统计量与最大频率
max_freq <- max(table(cut(dat3$new_burn_size, breaks = seq(0, max(dat3$new_burn_size)+100, by=100))))
stat_vals <- list(
  twenty = 200,
  max_val = max(dat3$new_burn_size),
  mean_val = mean(dat3$new_burn_size),
  med_val = median(dat3$new_burn_size)
)

# 绘制直方图
plot1 <- ggplot(dat3, aes(x=new_burn_size)) +
  geom_histogram(binwidth = 100, right = TRUE, origin = 0, fill="gray50", color="white") +
  labs(title = "1980-2023 QC Fire Distribution FZ5")+
  # 动态y轴:自动生成易读刻度,顶部留空间
  scale_y_continuous(
    name = "Number of fires",
    breaks = pretty_breaks(),
    expand = expansion(mult = c(0, 0.1))
  ) +
  # x轴同样使用易读刻度
  scale_x_continuous(
    "Fire Size (1 ha minimum)",
    breaks = pretty_breaks()
  ) +
  theme(
    panel.grid.minor = element_blank(), 
    panel.background = element_blank(), 
    axis.line = element_line(colour = "black"), 
    legend.position = "none", 
    axis.text.x = element_text(angle = 90, size = 10), 
    plot.title = element_text(hjust = 0.5, face = "bold"), 
    axis.text.y = element_text(colour = "black", size = 10)
  ) +
  # 200 ha 垂直线与标注(y值取最大频率的95%)
  geom_vline(aes(xintercept = stat_vals$twenty), colour = "orange", linewidth =0.75)+
  geom_text(aes(label="200 ha", y=max_freq * 0.95, x=stat_vals$twenty), colour = "orange", size = 4, hjust=0) +
  # 最大值垂直线与标注
  geom_vline(aes(xintercept = stat_vals$max_val), colour = "#882255", linewidth=0.5, linetype = "dashed")+
  geom_text(aes(label=paste("max:",round(stat_vals$max_val)), y=max_freq * 0.85, x=stat_vals$max_val), col= "#882255", size=3, hjust=0) +
  # 均值垂直线与标注
  geom_vline(aes(xintercept = stat_vals$mean_val), colour = "#009E73", linewidth=0.5, linetype = "dashed")+
  geom_text(aes(label=paste("mean:",round(stat_vals$mean_val)), y=max_freq * 0.75, x=stat_vals$mean_val), col= "#009E73", size=3, hjust=0) +
  # 中位数垂直线与标注
  geom_vline(aes(xintercept = stat_vals$med_val), colour = "#56B4E9", linewidth=0.5, linetype = "dashed")+
  geom_text(aes(label=paste("med:",round(stat_vals$med_val)), y=max_freq * 0.65, x=stat_vals$med_val), col= "#56B4E9", size=3, hjust=0) +
  # bin宽度标注
  annotate('text', x = max(dat3$new_burn_size)*0.8, y=max_freq * 0.95, label = "bins = 100 ha")

print(plot1)

批量绘图优化方案

将绘图逻辑封装为函数,循环处理不同省份/火区的数据:

plot_fire_hist <- function(data, group_col, group_val) {
  # 筛选目标分组数据
  dat_sub <- data %>% filter({{group_col}} == group_val)
  
  # 计算当前分组的统计量
  max_freq <- max(table(cut(dat_sub$new_burn_size, breaks = seq(0, max(dat_sub$new_burn_size)+100, by=100))))
  stat_vals <- list(
    twenty = 200,
    max_val = max(dat_sub$new_burn_size),
    mean_val = mean(dat_sub$new_burn_size),
    med_val = median(dat_sub$new_burn_size)
  )
  
  # 生成图表
  ggplot(dat_sub, aes(x=new_burn_size)) +
    geom_histogram(binwidth = 100, right = TRUE, origin = 0, fill="gray50", color="white") +
    labs(title = paste0("1980-2023 Fire Distribution - ", deparse(substitute(group_col)), " ", group_val))+
    scale_y_continuous(name = "Number of fires", breaks = pretty_breaks(), expand = expansion(mult = c(0, 0.1)))+
    scale_x_continuous("Fire Size (1 ha minimum)", breaks = pretty_breaks())+
    theme(panel.grid.minor = element_blank(), panel.background = element_blank(), axis.line = element_line(colour = "black"), legend.position = "none", axis.text.x = element_text(angle = 90, size = 10), plot.title = element_text(hjust = 0.5, face = "bold"), axis.text.y = element_text(colour = "black", size = 10))+
    geom_vline(aes(xintercept = stat_vals$twenty), colour = "orange", linewidth =0.75)+
    geom_text(aes(label="200 ha", y=max_freq * 0.95, x=stat_vals$twenty), colour = "orange", size = 4, hjust=0)+
    geom_vline(aes(xintercept = stat_vals$max_val), colour = "#882255", linewidth=0.5, linetype = "dashed")+
    geom_text(aes(label=paste("max:",round(stat_vals$max_val)), y=max_freq * 0.85, x=stat_vals$max_val), col= "#882255", size=3, hjust=0)+
    geom_vline(aes(xintercept = stat_vals$mean_val), colour = "#009E73", linewidth=0.5, linetype = "dashed")+
    geom_text(aes(label=paste("mean:",round(stat_vals$mean_val)), y=max_freq * 0.75, x=stat_vals$mean_val), col= "#009E73", size=3, hjust=0)+
    geom_vline(aes(xintercept = stat_vals$med_val), colour = "#56B4E9", linewidth=0.5, linetype = "dashed")+
    geom_text(aes(label=paste("med:",round(stat_vals$med_val)), y=max_freq * 0.65, x=stat_vals$med_val), col= "#56B4E9", size=3, hjust=0)+
    annotate('text', x = max(dat_sub$new_burn_size)*0.8, y=max_freq * 0.95, label = "bins = 100 ha")
}

# 调用示例(假设全量数据有province列)
# plot_fire_hist(your_full_data, province, "XX省")

额外优化建议

  • 针对数据差异极大的情况,可尝试给x轴添加对数刻度(scale_x_log10()),但需提前处理0值;或保留线性刻度,用coord_cartesian(xlim = c(0, 500))截断x轴,同时添加标注说明存在超出范围的大火。
  • 批量生成图表后,可使用purrr::map()循环处理所有分组,再用patchwork包拼接或批量保存为文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:45:05