ggplot2中sec_axis结合pretty_breaks无法显示次坐标轴负值百分比问题
解决ggplot2次坐标轴无法显示负值百分比的问题
尝试绘制带双坐标轴的图表时,使用scale_y_continuous的sec_axis参数搭配pretty_breaks()生成次坐标轴刻度,发现次坐标轴无法显示负值百分比,核心代码片段如下:
scale_y_continuous(sec.axis=sec_axis(breaks = pretty_breaks(), labels = label_percent()), breaks = pretty_breaks(), labels = label_dollar())
需要实现包含负值百分比的次坐标轴效果,以下是最小可复现代码:
library(dplyr) library(tidyr) library(ggplot2) library(scales) test = structure(list(Año = c(2017L, 2017L, 2017L, 2017L, 2017L, 2017L, 2018L, 2018L, 2018L, 2018L, 2018L, 2018L, 2019L, 2019L, 2019L, 2019L, 2019L, 2019L, 2020L, 2020L, 2020L, 2020L, 2020L, 2020L ), Vars = c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements", "Oil.GAV.%", "Non-Oil.GAV.%", "Other.GDP.elements.%", "Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements", "Oil.GAV.%", "Non-Oil.GAV.%", "Other.GDP.elements.%", "Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements", "Oil.GAV.%", "Non-Oil.GAV.%", "Other.GDP.elements.%", "Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements", "Oil.GAV.%", "Non-Oil.GAV.%", "Other.GDP.elements.%" ), value = c(5639977, 91442756, 7213129, 0.0987732330025509, 0.869107003693333, 0.0321197633041161, 6918891, 93037062, 7606055, 0.0913116709595953, 0.875434164471086, 0.0332541645693185, 6519993, 94351584, 7236432, 0.0923079337383433, 0.876825035531481, 0.0308670307301762, 4951333, 88365192, 5491485, -0.06, -0.35, -0.078)), row.names = c(NA, -24L), class = c("tbl_df", "tbl", "data.frame")) scaleFactor_t <- max(test %>% filter(Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")) %>% select(value)) / max(test %>% filter(!Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")) %>% select(value)) ggplot() + geom_col(data = test %>% filter(Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value , fill = Vars))+ geom_line(data = test %>% filter(!Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value*scaleFactor_t, colour = Vars), size = 1)+ geom_point(data = test %>% filter(!Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value*scaleFactor_t, colour = Vars), size = 2)+ scale_y_continuous(name="Miles de dólares", sec.axis=sec_axis(~./scaleFactor_t, name="Kcal per day", breaks =scales::pretty_breaks(), labels = scales::label_percent()), breaks =scales::pretty_breaks(), labels = scales::label_dollar())
问题原因
pretty_breaks()默认基于主坐标轴的数值范围生成刻度,而主坐标轴是正值的美元金额,转换到次坐标轴后,负值对应的主坐标轴数值不在默认生成的刻度范围内,因此次坐标轴不会显示负值刻度。
解决方案
自定义次坐标轴的刻度生成逻辑,确保覆盖负值范围,以下是两种可行方法:
方法1:手动指定次坐标轴刻度
直接设定包含负值的刻度数组,覆盖数据中的负数值区间:
scale_y_continuous( name="Miles de dólares", sec.axis=sec_axis( ~./scaleFactor_t, name="Porcentaje", breaks = c(-0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 1), # 手动指定包含负值的刻度 labels = scales::label_percent() ), breaks = scales::pretty_breaks(), labels = scales::label_dollar() )
方法2:自定义breaks函数,自动适配次坐标轴范围
基于pretty_breaks()扩展,让它根据次坐标轴的实际数值范围生成刻度:
# 自定义breaks函数,计算次坐标轴的实际数值范围 custom_breaks <- function() { function(x) { sec_range <- x / scaleFactor_t scales::pretty_breaks()(sec_range) } } # 应用到绘图代码 ggplot() + geom_col(data = test %>% filter(Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value , fill = Vars))+ geom_line(data = test %>% filter(!Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value*scaleFactor_t, colour = Vars), size = 1)+ geom_point(data = test %>% filter(!Vars %in% c("Oil.GAV", "Non-Oil.GAV", "Other.GDP.elements")), aes(x = Año, y = value*scaleFactor_t, colour = Vars), size = 2)+ scale_y_continuous( name="Miles de dólares", sec.axis=sec_axis( ~./scaleFactor_t, name="Porcentaje", breaks = custom_breaks(), labels = scales::label_percent() ), breaks = scales::pretty_breaks(), labels = scales::label_dollar() )
效果说明
修改后,次坐标轴会显示包含负值的百分比刻度,匹配2020年的负增长数据。建议将次坐标轴名称从"Kcal per day"修正为对应含义的"Porcentaje"(西班牙语“百分比”),契合数据语境。
内容的提问来源于stack exchange,提问作者cdcarrion
相关产品推荐
相关产品推荐

