【问题标题】:split fill in ggplot geom_tile (or heatmap): two colors by third value分割填充ggplot geom_tile(或热图):两种颜色由第三个值
【发布时间】:2016-07-21 13:59:14
【问题描述】:

我有分类数据,我想绘制使用热图 (geom_tile) 的频率,就像下面的示例:

data("mtcars")
freq <- data.frame(xtabs(~cyl + gear, mtcars)) #count number of 4,6,8 cyl cars by gear
ggplot(freq, aes(cyl, gear)) +
  geom_tile(aes(fill = Freq)) + 
  scale_fill_gradient(low = "white",high = "steelblue")

但我想根据显着或非显着结果(0-1 值)的比例来拆分每个图块。在本例中,我将生成相同的频率计数,但要区分在自动和手动变速箱之间(am

freq_am <- data.frame(xtabs(~cyl + gear + am, mtcars))
print(freq_am)
   #cyl gear am Freq
      4    3  0    1
      6    3  0    2
      8    3  0   12
      4    4  0    2
      6    4  0    2
      8    4  0    0
      4    5  0    0
      6    5  0    0
      8    5  0    0
      4    3  1    0
      6    3  1    0
      8    3  1    0
      4    4  1    6
      6    4  1    2
      8    4  1    0
      4    5  1    2
      6    5  1    1
      8    5  1    2

生成的热图将(例如)blue 表示am==0 的值,red 表示am==1。每个图块将根据自动 (am==0) 或手动 (am==1) 类型汽车的比例划分(沿对角线?)。蓝色和红色的色调将与计数成比例,就像渐变已经反映一样。

例如:

  • 左上角的图块 (4,5) 将完全为浅红色,因为所有 4 缸、5 档汽车(计数 = 2)都是手动的

  • 中间的左侧图块 (4,4) 将是 1/4 蓝色和 3/4 红色,因为 25% 的 4 齿轮、4 缸汽车是自动的(计数 = 2),而 75% 是手动(计数 = 6)

  • 左下方的图块 (4,3) 将完全是最浅的蓝色,因为所有 4 缸、3 档汽车(计数 = 1)都是自动的

【问题讨论】:

  • 根据?mtcars am 定义为传输(0 = 自动,1 = 手动)。在您的问题中,您已经定义了 自动 (am==1) 或手动 (am==0)blue 用于am==1 的值和红色用于am==0 这只是另一种方式。请问,你能edit你的Q并澄清一下吗? - 谢谢。
  • 已修复。感谢您提供澄清和解决方案!

标签: r ggplot2 visualization heatmap


【解决方案1】:

这是通过操纵频率计数使am==1 变为负数来回答问题的第二次完整尝试。与first attempt 的区别在于geom_col(position = "fill") 用于绘图而不是geom_tile()

注意:我没有编辑 the first answer,因为 OP 已经对其发表了评论,我最终可能会删除第一个不完整的答案。

准备数据

freq_am <-data.frame(xtabs(~cyl + gear + am, mtcars))
freq_am$Freq_am <- freq_am$Freq * (-1)^as.integer(as.character(freq_am$am))

这将创建一个新列Freq_am,其中Freq 计数乘以-1,如果am == 1(手动)。使用逻辑值取幂是避免ifelse 的技巧。

绘图

有两种可能性可以实现所需的类似热图的外观。

变体 1

p <- ggplot(freq_am, (aes(x = cyl, y = Freq, fill = Freq_am))) + 
  geom_col(position = "fill", width = 1) + 
  scale_fill_gradient2() +
  facet_grid(gear ~ ., as.table = FALSE, switch = "y") + 
  scale_y_continuous(expand = c(0, 0)) + 
  scale_x_discrete(expand = c(0, 0))
p

这将使用geom_col() 创建Freqcyl 的堆叠条形图,其中条形垂直(position = "fill") 和水平(width = 1) 拉伸以填充绘图区域。此外,scale 函数的expand = c(0, 0) 参数告诉ggplot不要像往常一样扩展轴。请注意,x 轴是离散的,因为 xtabs() 已将 cyl 强制转换为因子。

facet_grid() 用于模拟 y 轴,grid 值按递增顺序排列 (as.table = FALSE)。 switch = "y" 将面板条移到左侧。

scale_fill_gradient2() 默认使用方便的发散配色方案,自动变速箱的汽车数量显示为蓝色,手动变速箱的汽车数量显示为红色。

现在,我们需要移除热图不需要的所有装饰和空间。最后重命名y轴标签:

p + theme(panel.grid = element_blank()
          , axis.ticks = element_blank()
          , axis.text.y = element_blank()
          , strip.background = element_blank()
          , panel.spacing.y = unit(0, "pt")
) + 
  ylab("gear")

这种方法的缺点是图块之间没有边框。因此,如果相邻的瓷砖具有相同的颜色,例如 6-cyl、3-gear 和 4-gear,resp.,则很难区分计数的份额。

变体 2

此变体在图块之间添加边框。边框宽度可灵活调整:

p <- ggplot(freq_am, (aes(x = 1, y = Freq, fill = Freq_am))) + 
  geom_col(position = "fill") + 
  scale_fill_gradient2() +
  facet_grid(gear ~ cyl, as.table = FALSE, switch = "both") +
  scale_y_continuous(expand = c(0, 0)) + 
  scale_x_continuous(expand = c(0, 0))
p

在这里,我们将facet_grid() 用于两个方向。对于每个面板,Freq 与虚拟变量 1 使用 geom_col() 进行绘制,如上所示。由于虚拟变量1 是数字,我们不需要width 参数到geom_col()。现在两个轴都是连续的。

同样,我们需要移除一些装饰并重命名 x 轴和 y 轴上的标签:

p + theme(panel.grid = element_blank()
        , axis.ticks = element_blank()
        , axis.text = element_blank()
        , strip.background = element_blank()
        # , panel.spacing = unit(0, "pt")
  ) + 
  xlab("cyl") + ylab("gear")

现在,我们确实有一个带有图块之间边界的热图。为了去除边框或调整宽度,您可以取消注释panel.spacing 并更改值。

【讨论】:

    【解决方案2】:

    这是第一次尝试通过操纵频率计数来找到 Q 的(不完整)答案,以便它们对am==0 变为负数。

    请注意,这个问题并不完全清楚。 ?mtcarsam 定义为

    变速箱(0 = 自动,1 = 手动)。

    虽然 OP 已经定义了

    自动 (am==1) 或手动 (am==0)

    正好相反。此外,OP 已要求热图为 am==1 的值显示 blue,为 am==0 的值显示 red

    准备数据

    freq_am <-data.frame(xtabs(~cyl + gear + am, mtcars))
    freq_am$Freq_am <- -freq_am$Freq * (-1)^as.integer(as.character(freq_am$am))
    freq_am$gear_am <- factor(paste(as.character(freq_am$gear), as.character(freq_am$am), sep = "_"))
    
    freq_am
    #freq_am
    #   cyl gear am Freq Freq_am gear_am
    #1    4    3  0    1      -1     3_0
    #2    6    3  0    2      -2     3_0
    #3    8    3  0   12     -12     3_0
    #4    4    4  0    2      -2     4_0
    #5    6    4  0    2      -2     4_0
    #6    8    4  0    0       0     4_0
    #7    4    5  0    0       0     5_0
    #8    6    5  0    0       0     5_0
    #9    8    5  0    0       0     5_0
    #10   4    3  1    0       0     3_1
    #11   6    3  1    0       0     3_1
    #12   8    3  1    0       0     3_1
    #13   4    4  1    6       6     4_1
    #14   6    4  1    2       2     4_1
    #15   8    4  1    0       0     4_1
    #16   4    5  1    2       2     5_1
    #17   6    5  1    1       1     5_1
    #18   8    5  1    2       2     5_1
    

    请注意,xtabs() 已将 am 强制转换为因子:

    str(freq_am$am)
    # Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 2 ...
    

    要将am 转换回数字,我们必须使用as.integer(as.character(freq_am$am))。 (您可以使用(as.integer(am) - 1) 将级别数字直接转换为原始数值,但这样节省的时间较少。)

    gear_am 将在绘制热图时用作新的 y 轴。

    绘图

    library(ggplot2)
    ggplot(freq_am, aes(cyl, gear_am, fill = Freq_am)) +
      geom_tile() + 
      scale_fill_gradient2() + 
      theme_minimal() + 
      theme(panel.grid = element_blank())
    

    scale_fill_gradient2() 默认使用方便的发散配色方案。 y 轴上gear 的图块现已拆分为具有am==0am==1 的图块。

    “不完整”的答案

    OP 已要求即使计数为零,现在拆分的图块也应完全填充。这可以通过进一步操作freq_am 来实现。但是,我发现当前图表以清晰、明确的方式传达了结果。

    【讨论】:

    • 这是一个很好的开始,但我觉得很难解释。现在离散的框(例如(4,5_1) vs.(4,5_0))是一个特征的二分值(am)。此图形不会引导查看者比较这些值。我建议将图块按比例分割以使对比更清晰,但这可能需要完全不同的图形方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-16
    • 2017-02-28
    • 2012-05-11
    相关资源
    最近更新 更多