如何在 ggplot geom_tile 中正确使用刻面,同时保持纵横比不变?

How do I use facetting correctly in ggplot geom_tile, while keeping the aspect ratio intact?

我正在尝试创建一个 'likeliness plot' 旨在快速显示一个项目与 table 中其他项目的相似性。

一个简单的例子:

'property_data.csv' 要使用的文件:

"","Country","Town","Property","Property_value"
"1","UK","London","Road_quality","Bad"
"2","UK","London","Air_quality","Very bad"
"3","UK","London","House_quality","Average"
"4","UK","London","Library_quality","Good"
"5","UK","London","Pool_quality","Average"
"6","UK","London","Park_quality","Bad"
"7","UK","London","River_quality","Very good"
"8","UK","London","Water_quality","Decent"
"9","UK","London","School_quality","Bad"
"10","UK","Liverpool","Road_quality","Bad"
"11","UK","Liverpool","Air_quality","Very bad"
"12","UK","Liverpool","House_quality","Average"
"13","UK","Liverpool","Library_quality","Good"
"14","UK","Liverpool","Pool_quality","Average"
"15","UK","Liverpool","Park_quality","Bad"
"16","UK","Liverpool","River_quality","Very good"
"17","UK","Liverpool","Water_quality","Decent"
"18","UK","Liverpool","School_quality","Bad"
"19","USA","New York","Road_quality","Bad"
"20","USA","New York","Air_quality","Very bad"
"21","USA","New York","House_quality","Average"
"22","USA","New York","Library_quality","Good"
"23","USA","New York","Pool_quality","Average"
"24","USA","New York","Park_quality","Bad"
"25","USA","New York","River_quality","Very good"
"26","USA","New York","Water_quality","Decent"
"27","USA","New York","School_quality","Bad"

代码:

prop <- read.csv('property_data.csv')

Property_col_vector <- c("NA" = "#e6194b",
                "Very bad" = "#e6194B",
                "Bad" = "#ffe119",
                "Average" = "#bfef45",
                "Decent" = "#3cb44b",
                "Good" = "#42d4f4",
                "Very good" = "#4363d8")

plot_likeliness <- function(town_property_table){
    g <- ggplot(town_property_table, aes(Property, Town)) +
      geom_tile(aes(fill = Property_value, width=.9, height=.9)) +
      theme_classic() +
      theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5),
            strip.text.y = element_text(angle = 0)) +
      scale_fill_manual(values = Property_col_vector) +
      coord_fixed()
    return(g)
}

summary_town_plot <- plot_likeliness(prop)

输出:

这看起来很棒! 现在我创建了一个看起来不错的图,因为我使用了 coord_fixed() 函数,但现在我想创建相同的图,由 Country.

分面

为此,我创建了以下函数:

plot_likeliness_facetted <- function(town_property_table){
  g <- ggplot(town_property_table, aes(Property, Town)) +
    geom_tile(aes(fill = Property_value, width=.9, height=.9)) +
    theme_classic() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5),
          strip.text.y = element_text(angle = 0)) +
    scale_fill_manual(values = Property_col_vector) +
    facet_grid(Country ~ .,
               scale = 'free_y')
  return(g)
}

facetted_town_plot <- plot_likeliness_facetted(prop)
facetted_town_plot

结果:

但是,现在我的图块被拉伸了,如果我尝试使用“+ coords_fixed()”,我会收到错误消息:

Error: coord_fixed doesn't support free scales

我怎样才能让情节多面化,但保持纵横比?请注意,我将这些绘制成一个系列,因此使用手动值对绘图的高度进行硬编码并不是我想要的解决方案,我需要一些可以根据 table 中的值数量动态缩放的东西。

非常感谢您的帮助!

编辑:虽然同一个问题在其他地方的上下文略有不同,但它有多个答案 none 标记为解决问题。

这可能不是一个完美的答案,但无论如何我都会试一试。基本上,使用基本 ggplot 很难做到这一点,因为 - 正如你提到的 - coord_fixed()theme(aspect.ratio = ...) 不能很好地处理小平面。

我建议的第一个解决方案是使用 gtables 以编程方式设置面板的宽度以匹配 x-axis:

上的变量数量
plot_likeliness_gtable <- function(town_property_table){
  g <- ggplot(town_property_table, aes(Property, Town)) +
    geom_tile(aes(fill = Property_value, width=.9, height=.9)) +
    theme_classic() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5),
          strip.text.y = element_text(angle = 0)) +
    scale_fill_manual(values = Property_col_vector) +
    facet_grid(Country ~ .,
               scale = 'free_y', space = "free_y")
  # Here be the gtable bits
  gt <- ggplotGrob(g)
  # Find out where the panel is stored in the x-direction
  panel_x <- unique(gt$layout$l[grepl("panel", gt$layout$name)])[1]
  # Set that width based on the number of x-axis variables, plus 0.2 because
  # of the expand arguments in the scales
  gt$widths[panel_x] <- unit(nlevels(droplevels(town_property_table$Property)) + 0.2, "null")
  # Respect needs to be true to have 'null' units match in x- and y-direction
  gt$respect <- TRUE
  return(gt)
}

这将按以下方式工作:

library(grid)
x <- plot_likeliness_gtable(prop)
grid.newpage(); grid.draw(x)

并给出了这个情节:

这一切都运行良好,但在这一点上,讨论使用 gtables 而不是 ggplot 对象的一些缺点可能会很好。首先,你不能再用 ggplot 编辑它,所以你不能添加另一个 + geom_myfavouriteshape() 或任何类似的东西。不过,您仍然可以在 gtable/grid 中编辑部分情节。其次,它有古怪的 grid.newpage(); grid.draw() 语法,需要网格库。第三,我们有点依赖 ggplot 分面来正确设置 y-direction 面板高度(在您的示例中为 2.2 和 1.2 null-units),但这可能并不适用于所有情况。从好的方面来说,您仍在灵活地定义维度 null-units,因此它可以很好地适应您使用的任何绘图设备。

我提出的第二个解决方案对很多人来说可能有点老套,但它会消除使用 gtables 的前两个缺点。前段时间,我在分面时遇到了奇怪的面板尺寸行为的类似问题,所以我写了 these functions 来设置面板尺寸。其本质是从您正在制作的任何绘图中复制面板绘图函数,并将其包装在一个新函数中,该函数将面板大小设置为一些 pre-defined 数字。它必须在任何分面函数之后调用。它会像这样工作:

plot_likeliness_forcedsizes <- function(town_property_table){
  g <- ggplot(town_property_table, aes(Property, Town)) +
    geom_tile(aes(fill = Property_value, width=.9, height=.9)) +
    theme_classic() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5),
          strip.text.y = element_text(angle = 0)) +
    scale_fill_manual(values = Property_col_vector) +
    facet_grid(Country ~ .,
               scale = 'free_y', space = "free_y") +
    force_panelsizes(cols = nlevels(droplevels(town_property_table$Property)) + 0.2,
                     respect = TRUE)
  return(g)
}

myplot <- plot_likeliness_forcedsizes(prop)
myplot

虽然它仍然依赖于 ggplot 正确设置 y-direction 高度,但如果出现问题,您可以在 force_panelsizes() 内覆盖这些。

希望对您有所帮助,祝您好运!

theme(aspect.ratio = 1)space = 'free' 似乎有效。

plot_likeliness_facetted <- function(town_property_table){
  g <- ggplot(town_property_table, aes(Property, Town)) +
    geom_tile(aes(fill = Property_value, width=.9, height=.9)) +
    theme_classic() +
    theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5),
          strip.text.y = element_text(angle = 0), aspect.ratio = 1) +
    scale_fill_manual(values = Property_col_vector) +
    facet_grid(Country ~ .,
               scale = 'free_y', space = 'free')
  return(g)
}