ggplot 次要 y 轴刻度基于 facet_wrap 或 grid_arrange 的数据

ggplot secondry y axis scale based on data with facet_wrap or grid_arrange

我的数据由时间序列上的 25 个部门组成,我想在折线图中绘制每个部门的工人数量(系列 1)和平均工资(系列 2),第二个 y 轴为工人数量的平均工资和主要 y 轴,然后将图表排列在网格上。

示例数据:

period avg_wage number_of_workers sector
1990 2000 5000 construction
1991 2020 4970 construction
1992 2050 5050 construction
1990 1000 120 IT
1991 1100 400 IT
1992 1080 500 IT
1990 10000 900 hospital staff
1991 10200 980 hospital staff
1992 10400 1200 hospital staff

我尝试对网格使用 facet_wrap() 和 scale_y_continuous(sec.axis...) 如下:

#fake sample data for reference
dfa=data.frame(order=seq(1,100),workers=rnorm(1000,7),pay=rnorm(1000,3000,500),type="a") #1st sector
dfb=data.frame(order=seq(1,100),workers=rnorm(1000,25),pay=rnorm(1000,1000,500),type="b") #2nd sector
dfc=data.frame(order=seq(1,100),workers=rnorm(1000,400),pay=rnorm(1000,5000,500),type="c") #3rd sector
df=rbind(dfa,dfb,dfc)
colnames(df)=c(
  "order", #shared x axis/time value
  "workers", #time series 1 (y values for left side y axis)
  "pay", #time series 2 (y values for left side y axis)
  "type" #diffrent graphs to put on the grid
)

ggplotting 数据:

df=df %>% group_by(l=type) %>% mutate(coeff=max(pay)/max(workers)) %>% ungroup() #creating a coefficient to scale the secondry axis
plot=ggplot(data=df,aes(x=order))+
  geom_line(aes(y=workers),linetype="dashed",color="red")+
  geom_line(aes(y=pay/coeff)) +
  scale_y_continuous(sec.axis=sec_axis(~.*coeff2,name="wage"))+
  facet_wrap(~type,scale="free")

但不幸的是这不起作用因为你不能在函数sec_axis()中使用数据(这个例子甚至没有运行) .

我尝试的另一种方法是使用 for 循环和 grid.arrange():

plots=list()
for (i in (unique(df$type)))
{
  singlesector=df[df$type==i,]
  axiscoeff=df$coeff[1]
  plot=ggplot(data=singlesector,aes(x=order))+
    geom_line(aes(y=workers),linetype="dashed",color="red")+
    geom_line(aes(y=pay/coeff)) + labs(title=i)+
    scale_y_continuous(sec.axis=sec_axis(~.*axiscoeff,name="wage"))
  plots[[i]]=plot
    
}
grid.arrange(grobs=plots)

但是这也不起作用因为ggplot不保存变量axiscoeff的各种值所以它将第一个值应用于所有图表。

看结果(右边坐标轴乱了,不符合红线的数据):

有什么办法可以做我想做的事吗? 我想也许可以直接将所有图分别保存为 png,而不是以其他方式加入它们,但这似乎是一个极端的解决方案,需要花费太多时间来弄清楚。

据我所知,问题在于您(重新)缩放数据的方式,即使用 max(pay) / max(workers) 重新缩放数据以使 pay 的最大值映射到workers 的最大值,但未考虑变量的不同范围或分布。

相反,您可以使用 scales::rescale 重新缩放数据,使 pay 的范围映射到 workers.

的范围

除此之外,我还采用了一种不同的方法将这些图粘合在一起,该方法利用了 patchwork。为此,我将绘图代码放在一个函数中,split 数据 type,使用 lapply 循环拆分数据,最后使用 [=22= 将图粘合在一起].

注意:由于您的示例数据包含每个 order/type 的多个值,我略微更改了它以摆脱之字形线。

library(dplyr)
library(ggplot2)
library(patchwork)
library(scales)

df %>% 
  split(.$type) %>% 
  lapply(function(df) {
    range_pay <- range(df$pay)
    range_workers <- range(df$workers)
    ggplot(data = df, aes(x = order)) +
      geom_line(aes(y = workers), linetype = "dashed", color = "red") +
      geom_line(aes(y = rescale(pay, range_workers, range_pay))) +
      scale_y_continuous(sec.axis = sec_axis(~ rescale(.x, range_pay, range_workers), name = "wage")) +
      facet_wrap(~type)
  }) %>% 
  wrap_plots(ncol = 1)

数据

set.seed(123)
dfa <- data.frame(order = 1:100, workers = rnorm(100, 7), pay = rnorm(100, 3000, 500), type = "a") # 1st sector
dfb <- data.frame(order = 1:100, workers = rnorm(100, 25), pay = rnorm(100, 1000, 500), type = "b") # 2nd sector
dfc <- data.frame(order = 1:100, workers = rnorm(100, 400), pay = rnorm(100, 5000, 500), type = "c") # 3rd sector
df <- rbind(dfa, dfb, dfc)
names(df) <- c("order", "workers", "pay", "type")