如何在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
相关产品推荐
相关产品推荐

