如何去除堆叠geom_col之间的空白?

8
library(tidyverse)
library(lubridate)

date <- seq(ymd('2018-08-01'), ymd('2018-08-31'), by = '1 day')
c <- 21.30
x1 <- runif(length(date), 0, 20)
x2 <- rnorm(length(date), 10, 3)
x3 <- abs(rnorm(length(date), 40, 10))
data <- data.frame(c, x1, x2, x3) %>% 
  t() %>% as.data.frame() %>% rownames_to_column('var')
data <- data %>%
  mutate(category1 = c('catA', 'catB', 'catB', 'catC') %>% as.factor(),
         category2 = c('catAA', 'catBA', 'catBB', 'catCA') %>% as.factor())
names(data) <- c('var', as.character(date), 'category1', 'category2')
data_long <- data %>% 
  gather(date, value, -var, -category1, -category2) %>% 
  mutate(date = ymd(date))

data_long %>%
  ggplot(aes(date, value, fill = category1)) +
  geom_col(position = 'stack') +
  scale_x_date(breaks = '1 week', date_labels = '%Y-%m-%d', expand = c(.01, .01)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = .4)) +
  labs(fill = '')

使用上述示例数据和代码生成以下图表:enter image description here 我需要做的是删除列之间的空白。我发现了一些类似的话题,但它们建议使用position_dodge(),然而在我的情况下无法使用,因为我已经有position = 'stack',不能替换。那么我怎样才能让列相邻呢?
编辑
设置width = 1,如@camille所建议,对原始数据似乎可以正常工作,但对于按周或月聚合的数据则不行,请参见下面的代码:
data_long %>%
  mutate(date = floor_date(date, unit = 'week', week_start = 1)) %>% 
  group_by(category1, date) %>% 
  summarise(value = sum(value, na.rm = TRUE)) %>% 
  ungroup() %>% 
  ggplot(aes(date, value, fill = category1, width = 1)) +
  geom_col(position = 'stack') +
  scale_x_date(breaks = '1 month', date_labels = '%Y-%m', expand = c(.01, .01)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = .4)) +
  labs(fill = '')

enter image description here

编辑2。

正如@Camille指出的那样,在日期刻度的情况下,宽度为1可能是指1天。然而,以下操作未能产生预期输出并返回警告消息:position_stack requires non-overlapping x intervals

 data_long %>%
    mutate(date = floor_date(date, unit = 'month', week_start = 1)) %>% 
    group_by(category1, date) %>% 
    summarise(value = sum(value, na.rm = TRUE),
              n = n()) %>% 
    ungroup() %>% 
    ggplot(aes(date, value, fill = category1, width = n)) +
    geom_col(position = 'stack') +
    scale_x_date(breaks = '1 month', date_labels = '%Y-%m', expand = c(.01, .01)) +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 90, vjust = .4)) +
    labs(fill = '')

enter image description here


2
width = 1?根据 ?geom_col 的默认设置,宽度为0.9。 - camille
1
似乎在原始数据上工作正常,但是在聚合数据中不起作用 - 请参见我的编辑。 - jakes
你好,我该如何消除警告 position_stack requires non-overlapping x intervals?这真的很让人头疼。 - Death Metal
2个回答

5
geom_col的文档比我上面的评论更具体。宽度参数的更详细含义如下:

柱状图的宽度。默认情况下,设置为数据分辨率的90%。

在一般情况下,例如您的第一个例子,这可能只是指一个离散情况到另一个离散情况之间的距离。但是对于具有实际分辨率的日期而言,这似乎是指天数。我不确定是否有一种不同的方法来设置日期的分辨率,例如将一个单位设置为一周,而不是一天。
我减小alpha值只是为了能够看到柱状图是否重叠。
因此,如果没有设置宽度,则默认为观察值之间距离的90%,即一周的90%。
library(tidyverse)
library(lubridate)
...

summarized <- data_long %>%
  mutate(date = floor_date(date, unit = 'week', week_start = 1)) %>% 
  group_by(category1, date) %>% 
  summarise(value = sum(value, na.rm = TRUE)) %>% 
  ungroup()

ggplot(summarized, aes(date, value, fill = category1)) +
  geom_col(alpha = 0.6) +
  scale_x_date(breaks = '1 week', expand = c(.01, .01))

将宽度设置为1意味着宽度为1天。我感觉这里存在一些差异,可能有人能够解释,为什么这被读作1天而不是分辨率的100%。

ggplot(summarized, aes(date, value, fill = category1)) +
  geom_col(alpha = 0.6, width = 1) +
  scale_x_date(breaks = '1 week', expand = c(.01, .01))

如果想要设置一周(即7天)的宽度,请将宽度设置为7。这里可能需要一些其他人进行解释。

ggplot(summarized, aes(date, value, fill = category1)) +
  geom_col(alpha = 0.6, width = 7) +
  scale_x_date(breaks = '1 week', expand = c(.01, .01))

编辑:根据我评论中的链接,最好的方法可能就是将日期转换为字符串,这样您就可以像通常那样在离散的x轴上绘制图形。在调用as.character之前,您可以进行任何所需的格式化。

summarized %>%
  mutate(date = as.character(date)) %>%
  ggplot(aes(x = date, y = value, fill = category1)) +
    geom_col(width = 1)


这似乎有点奇怪。那么月份呢?有些月份有30天,有些有31天,当然还有可能有28或29天的二月。将n = n()添加到总结中,然后将width = n设置为不太有用(请参见编辑2)。那么我该怎么处理呢? - jakes
老实说,我不太清楚,尽管我认为你使用聚合函数的版本会出现问题,因为你通过width = n将一个向量分配给宽度,而不是一个单独的静态数字。 - camille
这里有一个类似的答案,建议将x变量转换为因子(或字符)并删除日期缩放。这可能是可行的方法,因为您仍然可以格式化字符串或因子标签以看起来像日期。链接:https://stackoverflow.com/a/33619670/5325862 - camille
2
为了进行堆叠,需要更多的数据准备工作,但您也可以考虑使用geom_rect来避免处理宽度。然后,它将在日/周/月甚至不均匀间隔的时间段中等效工作。 - Jon Spring

4

顺便提一下,在开头加上 set.seed() 可以确保我们使用相同的数据。我在这里使用了set.seed(42)

一个更灵活的替代方法是使用 geom_rectgeom_tile 代替 geom_col。然后,您可以根据需要将每个柱子设置为几天/几周/几个月宽度。但是需要进行一些准备工作。

例如,在此处,我通过按日期分组,并按 category2 排序和累加求和来预先计算每个柱子的累积 y 坐标。我还通过获取日期和下一个日期来确定 x 范围。 (我最后手动调整了一下右边图表的最后一列应该是一天宽。如果使用星期/月,请进行调整。可能有一种聪明的方法可以使用 padr::pad 或其他方法来自动推断增量应该是什么。)

data_long2 <- data_long %>%
  group_by(date) %>%
  arrange(desc(category2)) %>%
  mutate(top = cumsum(value),
         bottom = top - value) %>%
  ungroup() %>%
  group_by(category2) %>%
  mutate(next_date = lead(date, default = max(date) + 1)) %>%
  ungroup()

使用这个方法,您可以使用 geom_rectgeom_tile 来制作图表。它们是可互换的,但它们使用不同的坐标系,一个基于角落,另一个基于中心。

以下是使用 geom_rect 的示例,其中每个条形图的左侧边缘都与日期对齐。

ggplot(data_long2) +
  geom_rect(aes(xmin = date, xmax = next_date,
                ymin = bottom, ymax = top,
                fill = category1)) +
  scale_x_date(breaks = '1 week', date_labels = '%Y-%m-%d', expand = c(.01, .01)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = .4)) +
  labs(fill = '', y = "")

在这里输入图片描述

或者您可以使用geom_tile,在这种情况下,我会将日期对齐到每个矩形的中间。

ggplot(data_long2) +
  geom_tile(aes(x = date, width = as.numeric(next_date - date),
                y = (top + bottom)/2, height = (top - bottom),
                fill = category1)) +
  scale_x_date(breaks = '1 week', date_labels = '%Y-%m-%d', expand = c(.01, .01)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = .4)) +
  labs(fill = '')

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接