R - ggplot2'闪避' geom_step()重叠geom_bar()

时间:2017-04-16 07:12:36

标签: r ggplot2 tidyverse

使用ggplot2' s geom_bar(stat="identity")绘制计数是一种可视化计数的有效方法。我想使用这种方法来显示我观察到的计数,并将它们与我希望通过使用geom_step在条形图上覆盖阶梯步骤图层的预期计数进行比较。

然而,当我这样做时,我遇到的问题是,默认情况下,条形图的位置被躲避但geom_step没有。例如,使用连续和离散因变量:

library(tidyverse)

test <- data_frame(a = 1:10, b = runif(10, 1, 10))

test_plot <- ggplot(test, aes(a, b)) + 
  geom_bar(stat="identity") + 
  geom_step(color = 'red')

test2 <- data_frame(a = letters[1:10], b = runif(10, 1, 10))

test2_plot <- ggplot(test2, aes(a, b, group = 1)) + 
  geom_bar(stat="identity") + 
  geom_step(color = 'red'))

gridExtra::grid.arrange(test_plot, test2_plot, ncol = 2)

enter image description here

正如您所看到的那样,这两层是偏移的,这是不可取的。

阅读文档后,我看到geom_path有一个position =选项,但尝试类似geom_step(color = 'red', position = position_dodge(width = 0.5))的内容并不能达到我想要的效果,而是将条形和阶梯线压缩到中心。另一种选择是像这样geom_step(aes(a-0.5, b), color = 'red')直接调整数据,这对于具有连续因变量的数据产生接近可接受的结果。您还可以将stairstep行计算为函数,并使用stat_function()绘制它。

enter image description here

然而,这些方法不适用于具有离散因变量的数据,而我的实际数据具有离散的因变量,因此我需要另一个答案。

此外,当移动时,阶梯线将不会覆盖最后一根柱,如上图所示。是否有一种简单优雅的方式来扩展它以覆盖最后一个栏?

如果geom_step()是错误的方法,而我想要获得的东西可以用另一种方式实现,我也对此感兴趣。

4 个答案:

答案 0 :(得分:2)

我认为解决此问题的最有效方法是以下列方式定义自定义geom:

library(tidyverse)

geom_step_extend <- function(data, extend = 1, nudge = -0.5,
                             ...) {
  # Function for computing the last segment data
  get_step_extend_data <- function(data, extend = 1, nudge = -0.5) {
    data_out <- as.data.frame(data[order(data[[1]]), ])
    n <- nrow(data)
    max_x_y <- data_out[n, 2]
    if (is.numeric(data_out[[1]])) {
      max_x <- data_out[n, 1] + nudge
    } else {
      max_x <- n + nudge
    }

    data.frame(x = max_x,
               y = max_x_y,
               xend = max_x + extend,
               yend = max_x_y)
  }

  # The resulting geom
  list(
    geom_step(position = position_nudge(x = nudge), ...),
    geom_segment(
      data = get_step_extend_data(data, extend = extend, nudge = nudge),
      mapping = aes(x = x, y = y,
                    xend = xend, yend = yend),
      ...
    )
  )
}

set.seed(111)
test <- data_frame(a = 1:10, b = runif(10, 1, 10))
test2 <- data_frame(a = letters[1:10], b = runif(10, 1, 10))

test_plot <- ggplot(test, aes(a, b, group = 1)) + 
  geom_bar(stat = "identity") + 
  geom_step_extend(data = test, colour = "red")

test2_plot <- ggplot(test2, aes(a, b, group = 1)) + 
  geom_bar(stat = "identity") + 
  geom_step_extend(data = test2, colour = "red")

gridExtra::grid.arrange(test_plot, test2_plot, ncol = 2)

Example_output

基本上这个解决方案由三部分组成:

  1. 向左移动,position_nudge步长曲线按所需值(在本例中为-0.5);
  2. 使用函数get_step_extend_data计算缺席(右侧的)段数据。它的行为受ggplot2:::stairstep的启发,这是geom_step;
  3. 的基本功能
  4. 使用geom_stepgeom_segment的单独geom中撰写list

答案 1 :(得分:1)

这是一个相当粗略的解决方案,但在这种情况下应该有用。

创建一个替代数据框,扩展每一行以将x轴扩展-0.5和0.5:

test2 <- data.frame(a = lapply(1:nrow(test), function(x) c(test[x,"a"]-.5, test[x,"a"], test[x, "a"]+0.5)) %>% unlist, 
                b = lapply(1:nrow(test), function(x) rep(test[x,"b"], 3)) %>% unlist)

使用geom_line参数绘制轮廓:

ggplot(test, aes(a,b)) + geom_bar(stat="identity", alpha=.7) + geom_line(data=test2, colour="red")

enter image description here

如果将geom_bar宽度设置为1,则显示更整洁:

ggplot(test, aes(a,b)) + geom_bar(width=1, stat="identity", alpha=.7) + geom_line(data=test2, colour="red")

enter image description here

答案 2 :(得分:1)

由于ggplot2 version 3.3.0现已由geom_step使用direction = "mid"支持此选项:

library(tidyverse)

test <- data_frame(a = 1:10, b = runif(10, 1, 10))

test_plot <- ggplot(test, aes(a, b)) + 
  geom_bar(stat="identity") + 
  geom_step(color = 'red', direction = "mid", size = 2)

test_plot

enter image description here

答案 3 :(得分:0)

我喜欢 molx 在 ggplot2 3.3.0 版中对 direction = 'mid' 使用 geom_step() 的回答。但是,对于时间序列,我建议移动用于 geom_bar()geom_col() 图的 x 轴的数据:

data.frame(time = seq(as.POSIXct('2020-10-01 05:00'), 
                      as.POSIXct('2020-10-01 14:00'), by = 'hour'), 
                 value = runif(10, 0, 100)) %>%
  mutate(time_shift_bars = times + 30*60) %>% 
  ggplot(df, mapping = aes(y = value)) + 
  geom_step(color  = 'red', mapping = aes(x = time)) +
  geom_col(width = 60*60, mapping = aes(x = time_shift_bars))

![resulting plot](https://i.stack.imgur.com/fJBac.png)

The reason I prefer this is because for example 09:00 occurs at a specific instance, and the data represents the average for the following hour. If your time-series data is not averaged like this, use the `direction` method.