首页 > 解决方案 > 在ggplot中为分类和数值数据制作堆叠比例条形图

问题描述

我有一个如下的数据框:

df = data.frame(Age = c(rep(NA, 10), runif(40, 1, 100)), 
                Duration = c(rep(NA, 20), runif(30, 0, 4)), 
                cat = rep(c("A", "B", "C", "D", NA), each = 10), 
                cat2 = rep(c("X", "Y", "Z", NA, "W"), each = 10))

值得注意的是,它包含两个数值列和两个分类列。每列都有一些 NA 值。

我想制作一个堆叠条形图,显示分类列中每个组的比例和数字列的颜色条。类别的顺序对于分类无关紧要,除了我希望 NA 始终位于顶部,并且数字应该从底部的最低到顶部的最大排序(但 NA 也位于顶部)。

下面是我试图做的一个简短的草图,但没有取得多大成功。对于数字颜色条,我想在条上的 5 个点处注释四舍五入到最接近整数的值。

在此处输入图像描述

我首先将数据框融化以使其变长,但不确定如何从这里开始。

library(reshape) 
df_m = melt(df, id = c())

如果您能对此提供帮助,我将不胜感激。

谢谢,杰克

标签: rggplot2

解决方案


这可能是一个比您希望的要复杂得多的问题,并且需要几个步骤,所以这个解决方案感觉有点hacky。它也可能不是您想要的,但仍有调整的余地。

我首先要做的是将数值列分成区间,即因子,确保字符向量是因子,并为每一列赋予明确的因子级别"NA",而不是值类型NA。这是一个微妙的区别(您可以将此级别称为其他级别),但它允许您将此级别放在每个因素的末尾,因此NA所有条形都会放在顶部。但是,填充比例会自动为 分配一个灰度值NA,因此您必须手动执行此操作。我通过拉动 ColorBrewer 调色板“Blues”来做到这一点,然后在scale_fill_manual.

library(tidyverse)
library(patchwork)

set.seed(123)
df <- data_frame(Age = c(rep(NA, 10), runif(40, 1, 100)), 
                 Duration = c(rep(NA, 20), runif(30, 0, 4)), 
                 cat = rep(c("A", "B", "C", "D", NA), each = 10), 
                 cat2 = rep(c("X", "Y", "Z", NA, "W"), each = 10))

df_breaks <- df %>%
  arrange(Age) %>%
  mutate(Age = cut(Age, breaks = seq(0, 100, by = 25)),
         Duration = cut(Duration, breaks = seq(0, 4, by = 1))) %>%
  mutate_if(is.character, as.factor) %>%
  mutate_all(~fct_explicit_na(., na_level = "NA"))

df_breaks
#> # A tibble: 50 x 4
#>    Age     Duration cat   cat2 
#>    <fct>   <fct>    <fct> <fct>
#>  1 (0,25]  (3,4]    NA    W    
#>  2 (0,25]  (1,2]    C     Z    
#>  3 (0,25]  NA       B     Y    
#>  4 (0,25]  (0,1]    C     Z    
#>  5 (0,25]  (1,2]    D     NA   
#>  6 (0,25]  (3,4]    NA    W    
#>  7 (0,25]  (1,2]    NA    W    
#>  8 (25,50] (0,1]    C     Z    
#>  9 (25,50] NA       B     Y    
#> 10 (25,50] (3,4]    D     NA   
#> # ... with 40 more rows

palette <- RColorBrewer::brewer.pal(4, "Blues")

为了为每一列制作单独的图,我使用purrr::imap在每一列上调用一个函数,使用该列的名称和列本身创建一个新的数据框,计算中断,并制作一个条形图。我添加了一个geom_text来制作标签,它也可以让你跳过图例。(就像我在评论中所说的那样,传说会给你带来麻烦,因为所有的比例都是不同的。)我还删除了左侧和右侧的情节边距,这样你就可以将情节放在每个旁边其他,并删除 x 轴标题,这将是多余的。

p <- imap(df_breaks, function(col, term) {
  data_frame(term = term, group = col) %>%
    count(term, group) %>%
    ggplot(aes(x = term, y = n, fill = fct_rev(group))) +
      geom_col(position = "fill") +
      geom_text(aes(label = fct_rev(group)), position = position_fill(vjust = 0.5)) +
      scale_fill_manual(values = c("gray70", palette)) +
      theme_minimal() +
      theme(legend.position = "none", plot.margin = margin(10, 0, 10, 0, "pt")) +
      labs(x = NULL)
})

这会给你一个ggplot对象列表。我把它重新排列成你显示的顺序。

p <- p[c("Age", "cat", "Duration", "cat2")]

然后使用patchwork::wrap_plots,您可以将绘图列表放在一起。

wrap_plots(p, nrow = 1)

如果你想让它看起来像一个单一的情节,有一些冗余,所以你可以从情节 2、3 和 4 中删除左侧的主题元素,然后wrap_plots再次使用原来的p$Age

p_no_y <- map(p[2:4], function(plot) {
  plot +
    theme(axis.title.y = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank())
})
wrap_plots(p$Age, p_no_y$cat, p_no_y$Duration, p_no_y$cat2, nrow = 1)

使用patchworkover的优点cowplotpatchwork函数知道每个图中的坐标轴占用的空间,因此列的宽度相同,尽管一个图也有 y 轴。要了解我的意思,请替换wrap_plotscowplot::plot_grid.

所以这已经很多了!还有很多空间可以做更多:

  • 您可以进一步调整边距和其他主题元素以及 x 轴和 y 轴,以根据需要将绘图组合在一起。
  • 如果你想为不同的列使用不同的调色板——例如我在这里为数字变量显示的连续调色板,但为分类变量显示定性调色板——你可以单独分配填充比例,而不是像我在imap函数中所做的那样。
  • 您可能希望通过在cut.

推荐阅读