ggplot2双轴图表问题:geom_point刻度不匹配与geom_label视觉污染
问题描述
- geom_point的标签存在无法消除的视觉污染线条,切换Sexo变量类别可临时消除,但发现是隐藏分类占用了图表的“幽灵空间”(代码中已标注)。
- 更严重的问题:geom_point的数值与右侧y轴刻度完全不匹配。了解双轴需关联主轴,但这不符合可视化逻辑。
提供的代码
graf1 %>% mutate(Sexo = fct_relevel(Sexo,"Mujeres", "Total", "Hombres")) %>% ggplot(aes(fill=Sexo, y=ing_medio, x=Year)) + geom_bar(position="dodge", stat="identity")+ coord_cartesian(ylim = c(0,800))+ geom_text(aes(label=paste0("$",round(ing_medio,1))), vjust=-0.5,size=3,check_overlap = F, position = position_dodge(0.9))+ scale_fill_brewer(palette="Paired")+ geom_line(aes( y=brecha, x=Year, group=1,color ="Brecha de género (%)"))+ scale_color_manual(NULL, values = "red") + geom_point(aes( y=brecha, x=Year, group=1,color ="Brecha de género (%)"),inherit.aes = F)+ geom_label(aes( y=brecha, x=Year,label=(ifelse(Sexo=="Total",paste0("-",brechap),"") )), size=3,inherit.aes = F,vjust=-1)+ #### 如果把"Total"换成"Hombres"或"Mujeres",污染会消失,但只是视觉上的 scale_x_discrete("Año")+ scale_y_continuous(name="Ingreso medio en miles de pesos ($)", sec.axis = sec_axis( trans=~./2000, name="Brecha (%)", labels = scales::label_percent(), breaks = seq(0, 1, by = 0.05)))+ theme(panel.grid.minor.y = element_line(color="lightgrey"), panel.grid.major.y = element_line(color="lightgrey"), element_blank(),panel.background = element_blank(),legend.position="bottom", legend.text=element_text(size=8), plot.title = element_text(face = "bold",size=9, hjust=0.5), axis.title=element_text(size=8,face="bold"), axis.title.y.right = element_text(angle = 90))+ labs(title="Ingreso medio mensual de las personas ocupadas y brecha de género en el ingreso, según sexo y año, 2017-2021")
当前图表效果

数据样本
dput(head(graf1)) list(data = structure(list(Sexo = structure(c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L), levels = c("Mujeres", "Total", "Hombres"), class = "factor"), ing_medio = c(467.060109828569, 581.086381783187, 664.363627171407, 499.37645299215, 606.399207890318, 685.007895112625, 506.650544840556, 620.528397683238, 704.273922502823, 551.32710782737, 635.133967009455, 692.288992114432, 586.178446433092, 681.039367826197, 749.045509202275), Year = c("2017", "2017", "2017", "2018", "2018", "2018", "2019", "2019", "2019", "2020", "2020", "2020", "2021", "2021", "2021"), brecha = c(197.303517342838, 197.303517342838, 197.303517342838, 185.631442120475, 185.631442120475, 185.631442120475, 197.623377662267, 197.623377662267, 197.623377662267, 140.961884287062, 140.961884287062, 140.961884287062, 162.867062769183, 162.867062769183, 162.867062769183), brechap = c("29.7%", "29.7%", "29.7%", "27.1%", "27.1%", "27.1%", "28.1%", "28.1%", "28.1%", "20.4%", "20.4%", "20.4%", "21.7%", "21.7%", "21.7%")), row.names = c(3L, 1L, 2L, 6L, 4L, 5L, 9L, 7L, 8L, 12L, 10L, 11L, 15L, 13L, 14L), class = "data.frame"), layers = list(<environment>, <environment>, <environment>, <environment>, <environment>), scales = <environment>, mapping = structure(list(x = ~Year, y = ~ing_medio, fill = ~Sexo), class = "uneval"), theme = structure(list(axis.title = structure(list(family = NULL, face = "bold", colour = NULL, size = 8, hjust = NULL, vjust = NULL, angle = NULL, lineheight = NULL, margin = NULL, debug = NULL, inherit.blank = FALSE), class = c("element_text", "element")), axis.title.y.right = structure(list(family = NULL, face = NULL, colour = NULL, size = NULL, hjust = NULL, vjust = NULL, angle = 90, lineheight = NULL, margin = NULL, debug = NULL, inherit.blank = FALSE), class = c("element_text", "element")), legend.text = structure(list(family = NULL, face = NULL, colour = NULL, size = 8, hjust = NULL, vjust = NULL, angle = NULL, lineheight = NULL, margin = NULL, debug = NULL, inherit.blank = FALSE), class = c("element_text", "element")), legend.position = "bottom", panel.background = structure(list(), class = c("element_blank", "element")), panel.grid.major.y = structure(list(colour = "lightgrey", size = NULL, linetype = NULL, lineend = NULL, arrow = FALSE, inherit.blank = FALSE), class = c("element_line", "element")), panel.grid.minor.y = structure(list(colour = "lightgrey", size = NULL, linetype = NULL, lineend = NULL, arrow = FALSE, inherit.blank = FALSE), class = c("element_line", "element")), plot.title = structure(list(family = NULL, face = "bold", colour = NULL, size = 9, hjust = 0.5, vjust = NULL, angle = NULL, lineheight = NULL, margin = NULL, debug = NULL, inherit.blank = FALSE), class = c("element_text", "element"))), complete = FALSE, validate = TRUE), coordinates = <environment>)
问题根源与解决方法
问题1:标签的视觉污染线条
根源
你的geom_label没有过滤冗余数据:每个Year对应3个Sexo类别,即使ifelse让非Total的标签为空,ggplot仍会为这3个类别绘制标签框——空标签的透明框叠加后就会产生“线条”污染;同时标签未设置fill=NA,进一步加剧了视觉干扰。
解决方法
直接过滤数据,只保留Sexo=="Total"的行绘制标签,同时去掉不必要的填充:
geom_label(data = . %>% filter(Sexo == "Total"), aes(y=brecha, x=Year, label=paste0("-",brechap)), size=3, inherit.aes=F, vjust=-1, fill=NA)
问题2:geom_point数值与右侧y轴不匹配
根源
副轴的转换逻辑完全错误:你设置的trans=~./2000是把主轴收入值转成副轴数值,但实际你要展示的是性别收入差的百分比,需要把百分比数值映射到主轴范围内,再通过副轴转换回百分比显示。
解决方法
- 先把百分比字符串转为数值:
graf1 <- graf1 %>% mutate(brechap_num = as.numeric(sub("%", "", brechap))/100)
- 计算主轴最大收入值,将百分比数值缩放至主轴范围:
max_ing <- max(graf1$ing_medio)
- 修改折线、点的y值为缩放后的百分比,同时调整副轴转换公式:
# 替换原有的geom_line和geom_point geom_line(aes(y=brechap_num * max_ing, x=Year, group=1,color ="Brecha de género (%)"))+ geom_point(aes(y=brechap_num * max_ing, x=Year, group=1,color ="Brecha de género (%)"), inherit.aes=F)+ # 替换原有的scale_y_continuous scale_y_continuous( name="Ingreso medio en miles de pesos ($)", sec.axis = sec_axis( trans=~./max_ing, name="Brecha (%)", labels = scales::label_percent(), breaks = seq(0, 0.3, by = 0.05) # 匹配数据的百分比范围 ) )
这样折线和点的位置就会和右侧副轴的百分比刻度完全对应。
内容的提问来源于stack exchange,提问作者Sebastián Valladares
相关产品推荐
相关产品推荐

