在ggplot中添加多个功能区

时间:2018-10-05 18:34:43

标签: r ggplot2

我有一个带3个色带的ggplot。我可以使用以下代码生成该图:

library(ggplot2)
library(RColorBrewer)

data <- data.frame(
  date = seq.Date(as.Date("2018-01-01"), as.Date("2018-01-31"), by= "days"), 
  value = runif(min = 0, max = 1, n = 31) 
)


breaks <- c(0.1, 0.2, 0.3)
reds <- brewer.pal(3, "Reds")


pl <- ggplot2::ggplot(data = data,
                      aes(x = date, y = value)) +

  geom_ribbon(
    aes(
      x = date,
      ymin = value * (1 - breaks[1]),
      ymax = value * (1 + breaks[1])
    ),
    fill = reds[3],
    alpha = 0.4
  )  +

  geom_ribbon(
    aes(
      x = date,
      ymin = value * (1 - breaks[2]),
      ymax = value * (1 + breaks[2])
    ),
    fill = reds[2],
    alpha = 0.4
  )  +

  geom_ribbon(
    aes(
      x = date,
      ymin = value * (1 - breaks[2]),
      ymax = value * (1 + breaks[2])
    ),
    fill = reds[1],
    alpha = 0.4
  )  +

  geom_line(size = 1); pl

这很完美,可以满足我的要求。

enter image description here

我的问题是如何在代码中概括功能区的数量。如果我想添加一个新的功能区,我可以复制/粘贴我的代码,但这不是我想要的...我只想扩展breaks-vector(c(0.1,0.2,0.3,0.4))然后绘制应该自动包含4个色带(甚至更多)。就我而言,后面的图将由一个函数产生。此函数应仅将中断(和数据)作为参数。

我想我可以使用围绕geom_ribbon的for循环来做到这一点,并将结果存储在列表中。但是我没有成功:-(

有人有想法吗?提前非常感谢!

3 个答案:

答案 0 :(得分:2)

如果将断裂点作为数据集的一部分,则可以调整数据的形状,使断裂点成为变量,然后可以将其分配给美学(在这种情况下为填充)。我记得前一阵子回答a similar question,尽管实际上它的计算更为复杂。

要将breaks向量设为数据框的一列,我只是将其添加为列表。每个观察结果都有相同的休息时间。

data %>%
  mutate(brk = list(breaks))
#> # A tibble: 31 x 3
#>    date        value brk      
#>    <date>      <dbl> <list>   
#>  1 2018-01-01 0.0502 <dbl [3]>
#>  2 2018-01-02 0.190  <dbl [3]>
#>  3 2018-01-03 0.409  <dbl [3]>
#>  4 2018-01-04 0.453  <dbl [3]>
#>  5 2018-01-05 0.295  <dbl [3]>
#>  6 2018-01-06 0.170  <dbl [3]>
#>  7 2018-01-07 0.592  <dbl [3]>
#>  8 2018-01-08 0.315  <dbl [3]>
#>  9 2018-01-09 0.118  <dbl [3]>
#> 10 2018-01-10 0.374  <dbl [3]>
#> # ... with 21 more rows

然后取消嵌套列表列即可分隔这些中断值,以便对每个中断重复一次日期-值组合。由于有3个中断,因此现在的行数是3倍。

data %>%
  mutate(brk = list(breaks)) %>%
  unnest()
#> # A tibble: 93 x 3
#>    date        value   brk
#>    <date>      <dbl> <dbl>
#>  1 2018-01-01 0.0502   0.1
#>  2 2018-01-01 0.0502   0.2
#>  3 2018-01-01 0.0502   0.3
#>  4 2018-01-02 0.190    0.1
#>  5 2018-01-02 0.190    0.2
#>  6 2018-01-02 0.190    0.3
#>  7 2018-01-03 0.409    0.1
#>  8 2018-01-03 0.409    0.2
#>  9 2018-01-03 0.409    0.3
#> 10 2018-01-04 0.453    0.1
#> # ... with 83 more rows

为了方便将这些中断用作离散变量,我创建了一个仅将中断值作为因子的列,并颠倒了其顺序。这里(以及我链接到的上一个问题)棘手的是排序。 ggplot层是在先前的层之上构建的,因此,如果最宽的功能区最后被绘制,它将阻塞所有较小的层。中断的默认顺序为数字顺序,但是由于我将其作为因素,因此我可以反转级别,以便首先绘制最宽的级别0.3,然后将其置于下一层之下。

最后,要划清界线:为此,您只需要日期和值,并且不需要它们按照取消嵌套的方式重复我做过的事情,因此我将日期和值的不同组合用于geom_line。您可以通过其他方式来执行此操作,包括创建两个数据帧,一个具有重复数据帧,一个不具有重复数据帧,但是我通常更喜欢在一个管道中完成所有操作。

data %>%
  mutate(brk = list(breaks)) %>%
  unnest() %>%
  mutate(brk_fct = as.factor(brk) %>% fct_rev()) %>%
  ggplot(aes(x = date, y = value)) +
    geom_ribbon(aes(ymin = value * (1 - brk), ymax = value * (1 + brk), fill = brk_fct)) +
    geom_line(data = . %>% distinct(date, value)) +
    scale_fill_brewer(palette = "Reds")

reprex package(v0.2.1)于2018-10-05创建

答案 1 :(得分:0)

我的第一个直觉是用另一个其他答案将breaks作为一个新列创建一个长数据集。但是,您可以在循环中添加图层。

添加带有循环的图层可能很麻烦,因为ggplot2会延迟评估,直到绘制绘图为止(请参见说明here)。我们可以通过 tidyeval 代码使用“取消引用”来强制评估。

您会看到我遍历了多个中断,并为每个中断添加了一层,但是通过用!!取消引号来强制评估。

您会看到我也使用rev()反转调色板。

reds = brewer.pal(length(breaks), "Reds")

p1 = ggplot(data = data,
               aes(x = date, y = value))
for(i in 1:length(breaks)) {
    p1 = p1 + geom_ribbon( aes(ymin = value*(1 - !!breaks[i]),
                          ymax = value*(1 + !!breaks[i])),
                      fill = rev(reds)[i],
                      alpha = .4)
}
p1 + geom_line(size = 1)

enter image description here

答案 2 :(得分:0)

这基本上是@camille答案的base版本(我在撰写时没有看到)。无论如何,也可以发布它...

ggplot(data = data, aes(x = date, y = value)) +
  geom_line(size = 1) +
  geom_ribbon(data = merge(expand.grid(date = data$date, breaks = breaks), data), 
              aes(ymin = value * (1 - breaks), ymax = value * (1 + breaks),
                  fill = factor(breaks, levels = rev(unique(breaks)))), alpha = 0.4) +
 scale_fill_brewer(palette = "Reds")