我需要在一个图表中绘制一个显示计数的柱状图和一个显示率的折线图,我可以分别做这两个,但当我把它们放在一起时,我的第一层(即geom_bar)的比例被第二层(即geom_line)重叠。

我可以将geom_line的轴向右移动吗?


当前回答

我承认并同意哈德利(和其他人)的观点,即单独的y量表“存在根本缺陷”。说到这里,我经常希望ggplot2有这个特性——特别是当数据是宽格式的,并且我想快速地可视化或检查数据时(即仅供个人使用)。

虽然tidyverse库可以很容易地将数据转换为长格式(这样facet_grid()就可以工作),但这个过程仍然不是简单的,如下所示:

library(tidyverse)
df.wide %>%
    # Select only the columns you need for the plot.
    select(date, column1, column2, column3) %>%
    # Create an id column – needed in the `gather()` function.
    mutate(id = n()) %>%
    # The `gather()` function converts to long-format. 
    # In which the `type` column will contain three factors (column1, column2, column3),
    # and the `value` column will contain the respective values.
    # All the while we retain the `id` and `date` columns.
    gather(type, value, -id, -date) %>%
    # Create the plot according to your specifications
    ggplot(aes(x = date, y = value)) +
        geom_line() +
        # Create a panel for each `type` (ie. column1, column2, column3).
        # If the types have different scales, you can use the `scales="free"` option.
        facet_grid(type~., scales = "free")

其他回答

Hadley的回答参考了Stephen Few的报告《双缩放轴在图中是最好的解决方案吗?》

我不知道OP中的“counts”和“rate”是什么意思,但快速搜索会给我counts和Rates,所以我得到了一些关于北美登山事故的数据:

Years<-c("1998","1999","2000","2001","2002","2003","2004")
Persons.Involved<-c(281,248,301,276,295,231,311)
Fatalities<-c(20,17,24,16,34,18,35)
rate=100*Fatalities/Persons.Involved
df<-data.frame(Years=Years,Persons.Involved=Persons.Involved,Fatalities=Fatalities,rate=rate)
print(df,row.names = FALSE)

 Years Persons.Involved Fatalities      rate
  1998              281         20  7.117438
  1999              248         17  6.854839
  2000              301         24  7.973422
  2001              276         16  5.797101
  2002              295         34 11.525424
  2003              231         18  7.792208
  2004              311         35 11.254019

然后,我尝试按照Few在上述报告第7页建议的那样绘制图表(并按照OP的要求将计数绘制为柱状图,将率绘制为折线图):

The other less obvious solution, which works only for time series, is to convert all sets of values to a common quantitative scale by displaying percentage differences between each value and a reference (or index) value. For instance, select a particular point in time, such as the first interval that appears in the graph, and express each subsequent value as the percentage difference between it and the initial value. This is done by dividing the value at each point in time by the value for the initial point in time and then multiplying it by 100 to convert the rate to a percentage, as illustrated below.

df2<-df
df2$Persons.Involved <- 100*df$Persons.Involved/df$Persons.Involved[1]
df2$rate <- 100*df$rate/df$rate[1]
plot(ggplot(df2)+
  geom_bar(aes(x=Years,weight=Persons.Involved))+
  geom_line(aes(x=Years,y=rate,group=1))+
  theme(text = element_text(size=30))
  )

这就是结果:

但我不是很喜欢它,我不能轻易地给它加上一个传奇……

1 威廉森,杰德,等人。2005年北美登山事故。The Mountaineers Books, 2005。

It seemingly appears to be a simple question but it boggles around 2 fundamental questions. A) How to deal with a multi-scalar data while presenting in a comparative chart, and secondly, B) whether this can be done without some thumb rule practices of R programming such as i) melting data, ii) faceting, iii) adding another layer to existing one. The solution given below satisfies both the above conditions as it deals data without having to rescale it and secondly, the techniques mentioned are not used.

这是结果,

如果有兴趣了解更多关于此方法的信息,请点击下面的链接。 如何绘制一个2 y轴图表与条形并排而不重新缩放数据

根据上面的答案和一些微调(无论它有什么价值),这里有一种通过sec_axis实现两个尺度的方法:

假设有一个简单的(完全虚构的)数据集dt:在五天的时间里,它追踪了被打断的次数VS工作效率:

        when numinter prod
1 2018-03-20        1 0.95
2 2018-03-21        5 0.50
3 2018-03-23        4 0.70
4 2018-03-24        3 0.75
5 2018-03-25        4 0.60

(两列的范围相差大约5倍)。

下面的代码将画出它们占用整个y轴的两个级数:

ggplot() + 
  geom_bar(mapping = aes(x = dt$when, y = dt$numinter), stat = "identity", fill = "grey") +
  geom_line(mapping = aes(x = dt$when, y = dt$prod*5), size = 2, color = "blue") + 
  scale_x_date(name = "Day", labels = NULL) +
  scale_y_continuous(name = "Interruptions/day", 
    sec.axis = sec_axis(~./5, name = "Productivity % of best", 
      labels = function(b) { paste0(round(b * 100, 0), "%")})) + 
  theme(
      axis.title.y = element_text(color = "grey"),
      axis.title.y.right = element_text(color = "blue"))

下面是结果(上面的代码+一些颜色调整):

重点(除了在指定y_scale时使用sec_axis之外)是在指定系列时将第二个数据系列的每个值与5相乘。为了在sec_axis定义中获得正确的标签,它需要除以5(并格式化)。因此,上述代码中的关键部分实际上是geom_line和~中的*5。sec_axis中的/5(一个除当前值的公式。5)。

相比之下(我不想在这里判断方法),这是两个图表叠加在一起的样子:

你可以自己判断哪一个能更好地传递信息(“不要打扰别人工作!”)。我想这是一个公平的决定方式。

这两个图像的完整代码(实际上并没有比上面更多,只是完成并准备运行)在这里:https://gist.github.com/sebastianrothbucher/de847063f32fdff02c83b75f59c36a7d更详细的解释在这里:https://sebastianrothbucher.github.io/datascience/r/visualization/ggplot/2018/03/24/two-scales-ggplot-r.html

这在ggplot2中是不可能的,因为我认为具有单独y尺度的图(不是相互转换的y尺度)从根本上是有缺陷的。一些问题:

The are not invertible: given a point on the plot space, you can not uniquely map it back to a point in the data space. They are relatively hard to read correctly compared to other options. See A Study on Dual-Scale Data Charts by Petra Isenberg, Anastasia Bezerianos, Pierre Dragicevic, and Jean-Daniel Fekete for details. They are easily manipulated to mislead: there is no unique way to specify the relative scales of the axes, leaving them open to manipulation. Two examples from the Junkcharts blog: one, two They are arbitrary: why have only 2 scales, not 3, 4 or ten?

你也可能想要阅读Stephen Few关于双缩放轴在图形中的主题的冗长讨论,它们是最好的解决方案吗?

总有办法的。

这里有一个解决方案,允许完全任意轴而不重新缩放。其思想是生成两个除了轴以外完全相同的图,并使用cowplot包中的insert_yaxis_grob和get_y_axis函数将它们组合在一起。

library(ggplot2)
library(cowplot)

## first plot 
p1 <- ggplot(mtcars,aes(disp,hp,color=as.factor(am))) + 
    geom_point() + theme_bw() + theme(legend.position='top', text=element_text(size=16)) +
    ylab("Horse points" )+ xlab("Display size") + scale_color_discrete(name='Transmitter') +
    stat_smooth(se=F)

## same plot with different, arbitrary scale   
p2 <- p1 +
    scale_y_continuous(position='right',breaks=seq(120,173,length.out = 3),
                       labels=c('little','medium little','medium hefty'))

ggdraw(insert_yaxis_grob(p1,get_y_axis(p2,position='right')))