如何在ggplot2中用geom_ribbon正确绘制两条线间的着色区域?
问题描述
使用R语言ggplot2绘制不同教育水平下男性(Masculino)和女性(Femenino)的百分比折线图,希望根据哪组百分比更高着色两条线之间的区域,但当前着色区域无法正确显示,存在大量NA值或边界计算错误。
实际输出:折线图显示正常,但两条线之间的填充区域未正确渲染,仅出现零散色块或无填充。
期望输出:两条折线之间的区域根据男女百分比高低,分别填充对应颜色(女性占比高则用红色,男性占比高则用蓝色)。
使用的代码如下:
library(tidyverse) desired_levels <- c("Ninguno", "Educación preescolar", "Educación básica primaria (1 a 5 grado)", "Educación básica primaria (6 a 9 grado)", "Educación Media (10 a 11 grado)", "Tecnológico sin graduar", "Técnico graduado", "Universitario sin graduar", "Universitario graduado", "Tecnológico graduado", "Técnico sin graduar", "Postgrado") graph1 <- function(data, barrio) { # Filtering data data <- data %>% filter(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?` != "No sabe, no informa") table <- data %>% group_by(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`, Género) %>% summarise(n = sum(EXPANSOR), .groups = 'drop') %>% group_by(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`) %>% mutate(percentage = n / sum(n) * 100) %>% ungroup() %>% rename(NivelEducativo = `¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`) table$NivelEducativo <- factor( table$NivelEducativo, levels = desired_levels ) table <- table %>% complete(NivelEducativo, Género, fill = list(n = 0, percentage = 0)) # Creating bounds table for shaded area bounds <- table %>% pivot_wider(names_from = Género, values_from = percentage) %>% mutate( ymax = pmax(Masculino, Femenino, na.rm = TRUE), ymin = pmin(Masculino, Femenino, na.rm = TRUE), fill = Femenino > Masculino ) %>% replace_na(list(Masculino = 0, Femenino = 0, ymax = 0, ymin = 0)) plot <- ggplot() + geom_ribbon(data = bounds, aes(x = NivelEducativo, ymin = ymin, ymax = ymax, fill = fill), alpha = 0.4) + geom_line(data = table, aes(x = NivelEducativo, y = percentage, group = Género, color = Género), size = 1) + geom_point(data = table, aes(x = NivelEducativo, y = percentage, shape = Género, color = Género), size = 4) + geom_text(data = table, aes(x = NivelEducativo, y = percentage, label = paste0(round(n), " (", round(percentage), "%)")), position = position_dodge(width = 0.9), size = 4, color = "black", vjust = -0.5) + scale_fill_manual(values = c("TRUE" = "#e31a1c", "FALSE" = "#1f78b4"), guide = "none") + scale_color_manual(values = c("Masculino" = "#1f78b4", "Femenino" = "#e31a1c")) + scale_shape_manual(values = c("Masculino" = 21, "Femenino" = 23)) + theme_minimal() + labs(title = "Nivel educativo más alto cursado por género en CB", subtitle = "Distribución porcentual por género", x = "Nivel educativo", y = "Porcentaje (%)", color = "Género", shape = "Género") + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 12), axis.text.y = element_text(size = 12), legend.text = element_text(size = 12), legend.title = element_text(size = 12), plot.title = element_text(face = "bold", size = 16), plot.subtitle = element_text(size = 14), axis.title.x = element_text(size = 14), axis.title.y = element_text(size = 14), legend.position = "right", legend.background = element_rect(color = "gray", size = 0.1), legend.key = element_blank(), panel.grid.major = element_line(color = "gray", size = 0.2), panel.grid.minor = element_blank(), axis.line = element_line(color = "black", size = 0.5)) return(plot) } graph1(data_cb, "CB")
错误原因
- x轴类型不兼容:
geom_ribbon需要连续型x轴数据来计算填充区域,但当前NivelEducativo是因子(分类变量),ggplot无法在分类轴上正确生成连续的填充区域,导致边界计算错误或NA值。 - 分类轴无法识别过渡:分类变量作为x轴时,
geom_ribbon无法识别相邻类别之间的过渡关系,无法填充折线之间的完整区域。
修正方案
将因子型x轴转换为数值型,同时保留原因子的文本标签用于显示,具体步骤如下:
- 在数据处理阶段添加数值型x列(使用因子的整数编码)。
- 修改所有几何图层的x映射为数值型x。
- 通过
scale_x_continuous将数值x轴重新映射为原教育水平的文本标签。
修正后的代码
library(tidyverse) desired_levels <- c("Ninguno", "Educación preescolar", "Educación básica primaria (1 a 5 grado)", "Educación básica primaria (6 a 9 grado)", "Educación Media (10 a 11 grado)", "Tecnológico sin graduar", "Técnico graduado", "Universitario sin graduar", "Universitario graduado", "Tecnológico graduado", "Técnico sin graduar", "Postgrado") graph1 <- function(data, barrio) { # Filtering data data <- data %>% filter(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?` != "No sabe, no informa") table <- data %>% group_by(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`, Género) %>% summarise(n = sum(EXPANSOR), .groups = 'drop') %>% group_by(`¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`) %>% mutate(percentage = n / sum(n) * 100) %>% ungroup() %>% rename(NivelEducativo = `¿Cuál es el nivel educativo más alto que esta cursando o ha cursado?`) table$NivelEducativo <- factor( table$NivelEducativo, levels = desired_levels ) table <- table %>% complete(NivelEducativo, Género, fill = list(n = 0, percentage = 0)) %>% # 添加数值型x列,对应因子的整数位置 mutate(x = as.integer(NivelEducativo)) # Creating bounds table for shaded area bounds <- table %>% pivot_wider(names_from = Género, values_from = percentage) %>% mutate( x = as.integer(NivelEducativo), ymax = pmax(Masculino, Femenino, na.rm = TRUE), ymin = pmin(Masculino, Femenino, na.rm = TRUE), fill = Femenino > Masculino ) %>% replace_na(list(Masculino = 0, Femenino = 0, ymax = 0, ymin = 0)) plot <- ggplot() + # 使用数值型x绘制填充区域 geom_ribbon(data = bounds, aes(x = x, ymin = ymin, ymax = ymax, fill = fill), alpha = 0.4) + # 折线、点、文本均使用数值型x geom_line(data = table, aes(x = x, y = percentage, group = Género, color = Género), size = 1) + geom_point(data = table, aes(x = x, y = percentage, shape = Género, color = Género), size = 4) + geom_text(data = table, aes(x = x, y = percentage, label = paste0(round(n), " (", round(percentage), "%)")), position = position_dodge(width = 0.9), size = 4, color = "black", vjust = -0.5) + scale_fill_manual(values = c("TRUE" = "#e31a1c", "FALSE" = "#1f78b4"), guide = "none") + scale_color_manual(values = c("Masculino" = "#1f78b4", "Femenino" = "#e31a1c")) + scale_shape_manual(values = c("Masculino" = 21, "Femenino" = 23)) + # 将数值x映射回原教育水平标签 scale_x_continuous(breaks = 1:length(desired_levels), labels = desired_levels) + theme_minimal() + labs(title = "Nivel educativo más alto cursado por género en CB", subtitle = "Distribución porcentual por género", x = "Nivel educativo", y = "Porcentaje (%)", color = "Género", shape = "Género") + theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 12), axis.text.y = element_text(size = 12), legend.text = element_text(size = 12), legend.title = element_text(size = 12), plot.title = element_text(face = "bold", size = 16), plot.subtitle = element_text(size = 14), axis.title.x = element_text(size = 14), axis.title.y = element_text(size = 14), legend.position = "right", legend.background = element_rect(color = "gray", size = 0.1), legend.key = element_blank(), panel.grid.major = element_line(color = "gray", size = 0.2), panel.grid.minor = element_blank(), axis.line = element_line(color = "black", size = 0.5)) return(plot) } graph1(data_cb, "CB")
关键改动说明
- 在
table和bounds数据中添加x = as.integer(NivelEducativo),将分类变量转换为连续数值型x轴。 - 所有几何图层的x映射改为数值型
x,确保geom_ribbon能正确计算填充区域。 - 添加
scale_x_continuous将数值x轴重新标注为原教育水平的文本标签,保持视觉上的分类效果。
修改后,两条折线之间的区域会根据男女百分比高低正确填充对应颜色,符合期望输出效果。
内容的提问来源于stack exchange,提问作者lasagna
相关产品推荐
相关产品推荐

