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

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


当前回答

根据上面的答案和一些微调(无论它有什么价值),这里有一种通过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

其他回答

我承认并同意哈德利(和其他人)的观点,即单独的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")

可以对变量使用facet_wrap(~ variable, ncol=)来创建一个新的比较。它们不在同一个轴上,但很相似。

这是我对如何做二次轴变换的两种看法。首先,您希望将主数据和辅助数据的范围耦合起来。这通常是混乱的,因为您不想要的变量污染了全局环境。

为了简化这一点,我们将创建一个生成两个函数的函数工厂,其中scales::rescale()完成所有繁重的工作。因为这些是闭包,所以它们知道创建它们的环境,所以它们“有”创建之前生成的to和from参数的“内存”。

一个函数进行正向转换:将辅助数据转换为主要尺度。 第二个函数进行反向转换:将主要单位中的数据转换为次要单位。

library(ggplot2)
library(scales)

# Function factory for secondary axis transforms
train_sec <- function(primary, secondary, na.rm = TRUE) {
  # Thanks Henry Holm for including the na.rm argument!
  from <- range(secondary, na.rm = na.rm)
  to   <- range(primary, na.rm = na.rm)
  # Forward transform for the data
  forward <- function(x) {
    rescale(x, from = from, to = to)
  }
  # Reverse transform for the secondary axis
  reverse <- function(x) {
    rescale(x, from = to, to = from)
  }
  list(fwd = forward, rev = reverse)
}

这看起来相当复杂,但是创建函数工厂会使其余的一切变得更简单。现在,在绘制图形之前,我们将通过向工厂显示主要和次要数据来生成相关函数。我们将使用经济学数据集,它的失业列和pasavert列的范围非常不同。

sec <- with(economics, train_sec(unemploy, psavert))

然后我们使用y = sec$fwd(psavert)将辅助数据重新缩放到主轴,并指定~ sec$rev(.)作为辅助轴的转换参数。这给了我们一个主要范围和次要范围在图上占据相同空间的图。

ggplot(economics, aes(date)) +
  geom_line(aes(y = unemploy), colour = "blue") +
  geom_line(aes(y = sec$fwd(psavert)), colour = "red") +
  scale_y_continuous(sec.axis = sec_axis(~sec$rev(.), name = "psavert"))

工厂比这稍微灵活一些,因为如果您只是想重新调整最大值,您可以传入下限为0的数据。

# Rescaling the maximum
sec <- with(economics, train_sec(c(0, max(unemploy)),
                                 c(0, max(psavert))))

ggplot(economics, aes(date)) +
  geom_line(aes(y = unemploy), colour = "blue") +
  geom_line(aes(y = sec$fwd(psavert)), colour = "red") +
  scale_y_continuous(sec.axis = sec_axis(~sec$rev(.), name = "psavert"))

由reprex包于2021-02-05创建(v0.3.0)

我承认这个例子中的区别不是很明显,但如果你仔细观察,你会发现最大值是相同的,红线比蓝色的线低。

编辑:

这种方法现在已经在ggh4x包中的help_secondary()函数中被捕获和扩展。声明:我是ggh4x的作者。

这在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关于双缩放轴在图形中的主题的冗长讨论,它们是最好的解决方案吗?

我们当然可以用R函数图来建立一个双y轴的图。

# pseudo dataset
df <- data.frame(x = seq(1, 1000, 1), y1 = sample.int(100, 1000, replace=T), y2 = sample(50, 1000, replace = T))

# plot first plot 
with(df, plot(y1 ~ x, col = "red"))

# set new plot
par(new = T) 

# plot second plot, but without axis
with(df, plot(y2 ~ x, type = "l", xaxt = "n", yaxt = "n", xlab = "", ylab = ""))

# define y-axis and put y-labs
axis(4)
with(df, mtext("y2", side = 4))