r - 在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())
如果您能对此提供帮助,我将不胜感激。
谢谢,杰克
解决方案
这可能是一个比您希望的要复杂得多的问题,并且需要几个步骤,所以这个解决方案感觉有点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)
使用patchwork
over的优点cowplot
是patchwork
函数知道每个图中的坐标轴占用的空间,因此列的宽度相同,尽管一个图也有 y 轴。要了解我的意思,请替换wrap_plots
为cowplot::plot_grid
.
所以这已经很多了!还有很多空间可以做更多:
- 您可以进一步调整边距和其他主题元素以及 x 轴和 y 轴,以根据需要将绘图组合在一起。
- 如果你想为不同的列使用不同的调色板——例如我在这里为数字变量显示的连续调色板,但为分类变量显示定性调色板——你可以单独分配填充比例,而不是像我在
imap
函数中所做的那样。 - 您可能希望通过在
cut
.