ggplot2图表异常:图例显示但绘图无颜色/有颜色但无图例
我正在开发带双Y轴和图例的多变量ggplot2图表,参考相关解决方案后,目前得到两个版本的图表:
- 一个版本图例正常显示且颜色正确,但绘图元素没有颜色
- 另一个版本绘图元素有颜色,但图例不显示
我知道这和geom_point()中colour参数的位置有关,参考之前的回答:
colour= XYZ 应该放在 aes() 内部,而不是外部
geom_point(aes(data, colour=XYZ)) #------>显示图例
geom_point(aes(data),colour=XYZ) #------>不显示图例
数据集片段如下:
h1enraw <-structure(list(run = c(1738, 1739, 1740, 1741, 1742, 1743), temp = c(19, 19, 19, 19, 21, 22), avgbase = c(1386, 1386, 1389, 1389, 1352, 1336), no2c = c(6.98, 6.96, 6.94, 6.99, 7.01, 7.01), no3c = c(18.52, 17.6, 18.77, 19.81, 18.22, 18.60)), row.names = c(NA, 6L), class = "data.frame")
我理解为什么一个版本有图例另一个没有,但不清楚这个设置为何会影响绘图元素的颜色显示,恳请指点方向。
图例显示但绘图无颜色的代码
# 选择第二Y轴的缩放系数 scaleRight <- 40 ymax <-43 h1no2<-ggplot(h1enraw, aes(x=run)) + geom_path(aes(y=temp, group=1,colour="cornflowerblue"), size=0.9) + geom_point(aes(y=no2c, group=1,colour="red")) + geom_point(aes(y=no3c, group=1,colour="darkgreen")) + geom_line(aes(y=avgbase/scaleRight, group=1,colour="chocolate1"), size=0.9) + scale_y_continuous(breaks=seq(0,40,2), expand = expansion(mult = c(0,.05)), sec.axis = sec_axis(~.*scaleRight, name = "Average Baseline (Transmitance Units)")) + coord_cartesian(ylim = c(0, ymax)) + theme_classic() + labs( y="Temperature (°C) / Concentration (ppm)", x="Run", title = "H1 - High Temperature Cycle") + theme(text = element_text(size=9), axis.text.x = element_text(angle=90, vjust=0.6), axis.text=element_text(size=12), axis.title=element_text(size=14,face="bold"), panel.border = element_rect(colour = "chocolate1", fill=NA, size=0.5), legend.position = "bottom", legend.title=element_text(size=10), legend.text = element_text(size=8), axis.title.y = element_text(size=10), plot.title = element_text(size=14, face="bold")) + theme(legend.title=element_blank()) + scale_fill_manual(breaks=c("temp","no2c","no3c","avgbase"), labels=c("Temperature", "NO2", "NO3", "Avg Baseline"), values = c("temp"="cornflowerblue", "no2c"="red", "no3c"="darkgreen", "avgbase"="chocolate1")) + scale_color_manual(labels = c("Temperature", expression(NO[2]), expression(NO[3]), "Avg Baseline"), values = c("temp"="cornflowerblue", "no2c"="red", "no3c"="darkgreen", "avgbase"="chocolate1"), breaks=c("temp","no2c","no3c","avgbase"))
(对应图表:图例显示但绘图元素无颜色)
绘图有颜色但无图例的代码
# 选择第二Y轴的缩放系数 scaleRight <- 40 ymax <-43 h2no2<-ggplot(h2enraw, aes(x=run)) + geom_path(aes(y=temp, group=1), colour="cornflowerblue", size=0.9) + geom_point(aes(y=no2c, group=1 ),colour="red") + geom_point(aes(y=no3c, group=1 ),colour="darkgreen") + geom_line(aes(y=avgbase/scaleRight, group=1 ),colour="chocolate1", size=0.9) + scale_y_continuous(breaks=seq(0,40,2), expand = expansion(mult = c(0,.05)), sec.axis = sec_axis(~.*scaleRight, name = "Average Baseline (Transmitance Units)")) + coord_cartesian(ylim = c(0, ymax)) + theme_classic() + labs( y="Temperature (°C) / Concentration (ppm)", x="Run", title = "H2 - High Temperature Cycle") + theme(text = element_text(size=9), axis.text.x = element_text(angle=90, vjust=0.6), axis.text=element_text(size=12), axis.title=element_text(size=14,face="bold"), panel.border = element_rect(colour = "chocolate1", fill=NA, size=0.5), legend.position = "bottom", legend.title=element_text(size=10), legend.text = element_text(size=8), axis.title.y = element_text(size=10), plot.title = element_text(size=14, face="bold")) + scale_fill_manual(name="Legend", breaks=c("temp","no2c","no3c","avgbase"), labels=c("Temperature", "NO2", "NO3", "Avg Baseline"))
(对应图表:绘图元素有颜色但无图例)
为什么第一个版本绘图无颜色但图例正常?
当把colour="cornflowerblue"这类颜色值放在aes()内部时,ggplot2会将这些字符串视为分类变量的水平值,而非直接作为颜色代码。此时绘图元素的颜色由后续scale_color_manual决定,但scale_color_manual的breaks是"temp"、"no2c"这类变量名,和aes(colour=...)中传入的颜色字符串完全不匹配,导致ggplot找不到对应颜色映射,只能用默认灰色显示绘图元素。
图例能正常显示,是因为scale_color_manual定义了完整的标签和颜色映射,只是该映射与aes()内的颜色字符串无关联。
为什么第二个版本绘图有颜色但无图例?
当把colour="cornflowerblue"放在aes()外部时,这是直接给绘图元素设置固定颜色,不属于ggplot的“美学映射”范畴。ggplot仅当美学属性(如colour、fill)在aes()内部时,才会生成对应图例——外部设置的固定样式不会触发图例生成,因此看不到图例。
正确的解决方案:规范美学映射
要同时实现绘图元素有颜色+图例正常显示,需将colour映射到变量标识(而非直接写颜色值),再通过scale_color_manual绑定颜色:
方案1:整理为长数据(推荐,符合ggplot规范)
library(tidyr) scaleRight <- 40 ymax <-43 # 转换为长格式数据 h1enraw_long <- h1enraw %>% mutate(avgbase_scaled = avgbase / scaleRight) %>% pivot_longer(cols = c(temp, no2c, no3c, avgbase_scaled), names_to = "variable", values_to = "value") # 绘图 ggplot(h1enraw_long, aes(x=run, y=value, colour=variable)) + # 根据变量类型选择几何对象 geom_line(data = subset(h1enraw_long, variable %in% c("temp", "avgbase_scaled")), size=0.9) + geom_point(data = subset(h1enraw_long, variable %in% c("no2c", "no3c"))) + scale_y_continuous(breaks=seq(0,40,2), expand = expansion(mult = c(0,.05)), sec.axis = sec_axis(~.*scaleRight, name = "Average Baseline (Transmitance Units)")) + coord_cartesian(ylim = c(0, ymax)) + theme_classic() + labs(y="Temperature (°C) / Concentration (ppm)", x="Run", title = "H1 - High Temperature Cycle", colour="") + theme(text = element_text(size=9), axis.text.x = element_text(angle=90, vjust=0.6), axis.text=element_text(size=12), axis.title=element_text(size=14,face="bold"), panel.border = element_rect(colour = "chocolate1", fill=NA, size=0.5), legend.position = "bottom", legend.text = element_text(size=8), axis.title.y = element_text(size=10), plot.title = element_text(size=14, face="bold")) + scale_color_manual(labels = c("Temperature", expression(NO[2]), expression(NO[3]), "Avg Baseline"), values = c("temp"="cornflowerblue", "no2c"="red", "no3c"="darkgreen", "avgbase_scaled"="chocolate1"))
方案2:不整理数据,直接映射变量名
scaleRight <- 40 ymax <-43 ggplot(h1enraw, aes(x=run)) + geom_path(aes(y=temp, colour="temp"), size=0.9) + geom_point(aes(y=no2c, colour="no2c")) + geom_point(aes(y=no3c, colour="no3c")) + geom_line(aes(y=avgbase/scaleRight, colour="avgbase"), size=0.9) + scale_color_manual(labels = c("Temperature", expression(NO[2]), expression(NO[3]), "Avg Baseline"), values = c("temp"="cornflowerblue", "no2c"="red", "no3c"="darkgreen", "avgbase"="chocolate1"), breaks=c("temp","no2c","no3c","avgbase")) + scale_y_continuous(breaks=seq(0,40,2), expand = expansion(mult = c(0,.05)), sec.axis = sec_axis(~.*scaleRight, name = "Average Baseline (Transmitance Units)")) + coord_cartesian(ylim = c(0, ymax)) + theme_classic() + labs( y="Temperature (°C) / Concentration (ppm)", x="Run", title = "H1 - High Temperature Cycle", colour="") + theme(text = element_text(size=9), axis.text.x = element_text(angle=90, vjust=0.6), axis.text=element_text(size=12), axis.title=element_text(size=14,face="bold"), panel.border = element_rect(colour = "chocolate1", fill=NA, size=0.5), legend.position = "bottom", legend.text = element_text(size=8), axis.title.y = element_text(size=10), plot.title = element_text(size=14, face="bold"))
修改后,aes(colour="temp")中的"temp"作为分类键,与scale_color_manual的breaks对应,ggplot就能正确将颜色映射到绘图元素,同时生成对应图例。
内容的提问来源于stack exchange,提问作者MGB

