堆积条形图:如果每个子类别(填充)都是唯一的怎么办?

Stacked bar chart : what if each subcategory (fill) is unique?

上下文
我有一个数据集,其中使用类别和子类别来描述观察结果。子类别是这样的,给定的类别仅链接到一个类别(想想 "category ~ car brand" 和 "subcategory ~ car model")。

想象一下,我数了数我每天能看到的通过我的 window 的汽车数量。我应该得到一个类似于这样的数据集:

 _______________________________________________________
| Date      | Brand         | Model         | Count     |
|===========:===============:===============:===========|
| 18-01-01  | Ford          | Model T       | 1         |
| 18-01-01  | Ford          | Focus         | 13        |
| 18-01-01  | Tesla         | Model X       | 17        |
| 18-01-02  | Ford          | Model T       | 1         |
| 18-01-02  | Honda         | Civic         | 210       |
| _         | _             | _             | _         |
|___________|_______________|_______________|___________|

问题
我想创建一个(水平)条形图,显示按类别分组的条目数(即每个条形代表一个品牌)。此外,我想按模型对每个条形图进行细分(即,最接近条形图轴的部分代表品牌中计数最多的模型,然后是计数第二多的模型,等等)。

我可以生成这样的图表,但每个子类别随后都表示为单个图例项(请参见下面示例中的第一张图表)。但在我的真实示例中,我有十几个类别——每个类别都使用十几个子类别(因此总共有大约 150 个子类别)。这使得这样的解决方案不是 "usable".

问题
如何生成这样的图表,其中每个条形 (~brand) 都有一种颜色 (blue/red/yellow),每个子类别都是它的单色 shade/tint 变体(深色 blue/medium blue/light蓝色;深红色/……)?
(实际上,子类别是车型,因此特定于每个品牌 — 而不是 generic/multi-brand 类别,例如 "the most observed model of the brand"。此外,每个品牌的车型数量各不相同。)


例子

我可以使用以下代码生成接近的结果(请参阅示例底部的数据):

library(ggplot2)
df = read.csv('fake-data.csv', header = TRUE)
df <- df[order(df$car_brand, decreasing = FALSE),]
ggplot(df, aes(x=car_brand, y=count, fill = car_model)) +
  geom_bar(position = 'stack', stat = 'identity') +
  coord_flip()

但是,我得到了每个模型的单一颜色和图例条目。 我更喜欢每个品牌一种颜色,每个品牌型号的给定颜色 shade/tint 有变化。

这里是fake-data.csv的内容,明显是假数据:

month,car_brand,car_model,count
18-01,Tesla,Model X,8
18-01,Ford,Model T,11
18-01,Ford,Focus,9
18-01,Ford,Focus,19
18-01,Tesla,Model 3,8
18-01,BMW,1 series,4
18-01,Ford,Model T,18
18-01,Honda,Civic,13
18-01,Ford,Model T,9
18-01,Tesla,Model S,18
18-01,BMW,1 series,6
18-01,Ford,Focus,10
18-01,Honda,Civic,9
18-01,Audi,A6,14
18-01,Audi,R8,19
18-01,Ford,Focus,13
18-01,BMW,1 series,7
18-01,Tesla,Model 3,12
18-01,BMW,1 series,11
18-01,BMW,1 series,9
18-01,BMW,1 series,4
18-01,BMW,1 series,11
18-01,Ford,Model T,17
18-01,Honda,Civic,10
18-01,BMW,1 series,9
18-01,Ford,Focus,19
18-01,Honda,Civic,9
18-01,Ford,Focus,15
18-01,Audi,A8,12
18-01,Tesla,Model X,6
18-01,Honda,Civic,14
18-01,BMW,1 series,16
18-01,Tesla,Model X,18
18-01,Tesla,Model X,16
18-01,Audi,TT,20
18-01,Tesla,Model 3,9
18-01,Tesla,Model X,21
18-01,BMW,1 series,9
18-01,Audi,A8,18
18-01,BMW,1 series,2
18-01,Ford,Focus,2
18-01,Honda,Civic,7
18-01,Tesla,Model X,9
18-01,Honda,Civic,3
18-01,BMW,1 series,5
18-01,Ford,Focus,14
18-01,Honda,Civic,4
18-01,Tesla,Model S,4
18-01,Honda,Civic,7
18-01,Honda,Civic,13
18-01,Tesla,Model 3,3
18-01,Tesla,Model 3,9
18-01,BMW,1 series,13
18-01,Ford,Model T,11
18-01,Ford,Focus,10
18-01,Tesla,Model S,18
18-01,Audi,Q3,6
18-01,Audi,R8,13
18-01,Tesla,Model X,21
18-01,BMW,1 series,13
18-01,Ford,Focus,17
18-01,Tesla,Model X,14
18-01,Audi,TT,3
18-01,Ford,Model T,11
18-01,Honda,Civic,7
18-01,Ford,Focus,4
18-01,Honda,accord,6
18-01,Ford,Focus,10
18-01,Ford,Model T,10
18-01,Honda,Civic,15
18-01,Ford,Model T,2
18-01,Tesla,Model X,10
18-01,Ford,Focus,11
18-01,Tesla,Model X,14
18-01,Honda,Civic,13
18-01,BMW,1 series,19
18-01,BMW,1 series,21
18-01,Ford,Focus,8
18-01,Tesla,Model X,12
18-01,Honda,Civic,5
18-01,Honda,Civic,14
18-01,Honda,Civic,17
18-01,Audi,R8,16
18-01,Honda,Civic,12
18-01,Audi,A6,20
18-01,Tesla,Model X,4
18-01,Audi,TT,4
18-01,Ford,Focus,16
18-01,Audi,Q3,16
18-01,BMW,1 series,12
18-01,Audi,A8,18
18-01,Honda,Civic,1
18-01,Audi,A8,7
18-01,Audi,Q3,10
18-01,Tesla,Model X,18
18-01,Ford,Focus,19
18-01,Ford,Model T,2
18-01,Tesla,Model 3,15
18-01,Ford,Model T,13
18-01,Ford,Model T,2
18-01,Audi,Q3,14
18-01,BMW,1 series,4
18-01,Audi,R8,1
18-01,Honda,Civic,2
18-01,Tesla,Model 3,4
18-01,BMW,1 series,16
18-01,Audi,A8,5
18-01,Ford,Model T,18
18-01,Tesla,Model X,21
18-01,Ford,Focus,4
18-01,Ford,Focus,7
18-01,BMW,1 series,16
18-01,Tesla,Model X,16
18-01,Tesla,Model 3,14
18-01,BMW,1 series,8
18-01,BMW,1 series,13
18-01,Tesla,Model 3,7
18-01,Ford,Focus,21
18-01,BMW,1 series,14
18-01,BMW,1 series,10
18-01,Ford,Focus,11
18-01,Tesla,Model 3,13
18-01,Honda,Civic,4
18-01,Ford,Focus,11
18-01,Ford,Focus,8
18-01,BMW,1 series,18
18-01,Honda,Civic,18
18-01,Honda,Civic,15
18-01,Ford,Focus,9
18-01,Tesla,Model 3,4
18-01,BMW,1 series,5
18-01,Tesla,Model S,5
18-01,Audi,TT,12
18-01,Honda,Civic,17
18-01,BMW,1 series,9
18-01,Honda,Civic,7
18-01,Tesla,Model 3,15
18-01,Audi,A8,21
18-01,Ford,Model T,21
18-01,Ford,Model T,9
18-01,BMW,1 series,18
18-01,Tesla,Model 3,7
18-01,BMW,1 series,15
18-01,BMW,1 series,2
18-01,Ford,Model T,18
18-01,Audi,R8,17
18-01,Tesla,Model 3,3
18-01,Audi,A8,9
18-01,BMW,1 series,10
18-01,Audi,Q3,4
18-01,BMW,1 series,8
18-01,Honda,accord,19
18-01,Tesla,Model S,6
18-01,Audi,TT,18
18-01,Audi,Q3,21
18-01,Tesla,Model S,3
18-01,Tesla,Model S,9
18-01,Audi,Q3,1
18-01,Tesla,Model X,18
18-01,Honda,Civic,8
18-01,Audi,R8,14
18-01,Honda,Civic,21
18-01,Tesla,Model X,9
18-01,Audi,TT,16
18-01,Audi,A8,19
18-01,Ford,Focus,2
18-01,BMW,1 series,12
18-01,Ford,Model T,9
18-01,Tesla,Model X,9
18-01,Audi,R8,18
18-01,Honda,Civic,3
18-01,Honda,accord,7
18-01,Audi,A6,13
18-01,Audi,A8,13
18-01,Ford,Focus,8
18-01,Honda,accord,10
18-01,Audi,R8,20
18-01,Honda,Civic,18
18-01,Ford,Focus,7
18-01,Audi,R8,10
18-01,Audi,A6,13
18-01,Honda,Civic,4
18-01,Audi,A8,7
18-01,Audi,Q3,15
18-01,Honda,Civic,10
18-01,Audi,A8,6
18-01,Honda,Civic,1
18-01,Tesla,Model 3,21
18-01,Ford,Model T,7
18-01,BMW,1 series,6
18-01,Honda,Civic,4
18-01,Audi,A6,12
18-01,Honda,Civic,6
18-01,Tesla,Model S,17
18-01,Tesla,Model S,2
18-01,Tesla,Model X,6
18-01,Audi,A8,2
18-01,Tesla,Model 3,14
18-01,BMW,1 series,4
18-01,BMW,1 series,20
18-01,Honda,accord,17
18-01,Honda,Civic,14
18-01,BMW,1 series,16
18-01,Audi,A8,17
18-01,Audi,A6,11
18-01,Ford,Model T,1
18-01,BMW,1 series,18
18-01,Tesla,Model 3,11
18-01,Honda,Civic,21

这是一种映射到 alpha 的方法 - 这是我所知道的最接近您所追求的东西。

library(tidyverse)
df %>%
  group_by(car_brand, car_model) %>%
  summarise_at(vars(count), sum) %>%
  group_by(car_brand) %>%
  mutate(
    model_rank = car_model %>% rank(),
  ) %>%
  ggplot(aes(x = car_brand, y = count, fill = car_brand, alpha = model_rank)) +
  scale_alpha_continuous(range = c(1, .25)) + 
  theme(legend.position = 'none') + 
  geom_bar(position = 'stack', stat = 'identity') + 
  coord_flip()

这是一个老实说比需要的更复杂的解决方案。在大多数情况下,我只推荐@Melissa Key 的答案,即映射到 alpha,但我对执行此操作的方法感到好奇并且可以扩展。这利用了将多个调色板粘贴在一起,每个调色板一个,并根据品牌为该矢量中的每个项目分配名称。

我做的第一件事是按品牌和型号总结总计数的数据框。然后我将 make 和 model 都变成了因素,并使用 forcats 函数对它们进行排序,按 make 排序(只是按字母顺序排列,但你可以更改它),然后按最常见的模型排序。

library(tidyverse)
library(RColorBrewer)

df_sums <- df %>%
  group_by(car_brand, car_model) %>%
  summarise(count = sum(count)) %>%
  ungroup() %>%
  mutate(car_brand = as.factor(car_brand)) %>%
  mutate(car_model = as.factor(car_model) %>% fct_reorder2(car_brand, count, .desc = T)) %>%
  arrange(car_brand, car_model)

df_sums
#> # A tibble: 13 x 3
#>    car_brand car_model count
#>    <fct>     <fct>     <int>
#>  1 Audi      A8          154
#>  2 Audi      R8          128
#>  3 Audi      Q3           87
#>  4 Audi      A6           83
#>  5 Audi      TT           73
#>  6 BMW       1 series    413
#>  7 Ford      Focus       284
#>  8 Ford      Model T     189
#>  9 Honda     Civic       346
#> 10 Honda     accord       59
#> 11 Tesla     Model X     250
#> 12 Tesla     Model 3     169
#> 13 Tesla     Model S      82

据此,我沿着数据框的拆分版本和调色板名称向量进行映射,这样我就可以按名称获得 Color Brewer 调色板,每个品牌一个。单色调顺序刻度很适合这个,而且取起来也很方便。这里的问题是你需要在 brewer.pal 中给出颜色的数量,但是这个 returns 至少有 3 种颜色,所以你需要对颜色向量进行子集化以给出正确的颜色数量.在每个调色板有更多颜色或使用 brewer.pal 以外的其他颜色来获取调色板的情况下,这不是必需的。然后我将调色板的名称设置为模型的名称,以在 scale_fill_manual.

中保持秩序。
pals <- c("Blues", "Purples", "Greens", "Reds", "Oranges")

colors <- df_sums %>%
  split(.$car_brand) %>%
  map2(pals, function(d, pal) {
    n <- nrow(d)
    brewer.pal(n, pal)[1:n] %>%
      setNames(d$car_model)
  }) %>%
  reduce(c)

head(colors)
#>        A8        R8        Q3        A6        TT  1 series 
#> "#EFF3FF" "#BDD7E7" "#6BAED6" "#3182BD" "#08519C" "#EFEDF5"

df_sums %>%
  ggplot(aes(x = car_brand, y = count, fill = car_model)) +
    geom_col() +
    coord_flip() +
    scale_fill_manual(values = colors)

我还没弄明白为什么图例会按这个顺序出现,但是您可以更改标签的顺序,或者完全放弃图例,具体取决于您想要的输出。希望这不是太复杂!