为具有不同面数的地块获得相同的高度,并且 coord_fixed?

Get same height for plots having different facet numbers, and coord_fixed?

让我用图片解释我的意思:

set.seed(1)  ## dummy data.frame:
df <- data.frame( value1 = sample(5:15, 20, replace = T), value2 = sample(5:15, 20, replace = T),
                  var1 = c(rep('type1',10), rep('type2',10)), var2 = c('a','b','c','d'))

## Plot 1 

ggplot() +
  geom_point(data = df, aes(value1, value2)) +
  facet_grid(~var1) +
  coord_fixed()

ggsave("plot_2facet.pdf", height=5, units = 'in')
    #Saving 10.3 x 5 in image

## Plot 2  which I want to save in a separate file (!)

ggplot() +
  geom_point(data = df, aes(value1, value2)) +
  facet_grid(~var2) +
  coord_fixed()

ggsave("plot_4facet.pdf", height=5, units = 'in')
    #Saving 10.3 x 5 in image

现在这里发生了什么,设备具有相同的高度,但地块具有不同的高度。但我想为地块获得相同的高度。

在上面的代码中,我试图只指定高度,但 ggsave 然后只为设备采用固定宽度尺寸。

我尝试了 theme(plot.margin = margin(t=1,b=1)),但这并没有改变任何东西。

取出coord_fixed()得到相同高度的地块:

但是我想用coord_fixed()

是否有解决方案,或者我是否需要 "guess" 设备的宽度尺寸以获得正确的绘图高度?

干杯


编辑

理想情况下,这些图应该在单独的设备/文件中创建。

这对 ggplot 来说有点棘手,所以请原谅冗长、令人费解且诚然有点老套的答案。基本问题是 coord_fixed,y-axis 的高度与 x-axis 的长度密不可分。

我们可以通过两种方式打破这种依赖关系:

  1. 通过使用 scale_y_continuousexpand 参数。这允许我们将 y 轴扩展超出数据范围的给定数量。棘手的一点是知道要扩展多少,因为这取决于 hard-to-predict 绘图的所有元素,包括有多少个面以及轴标题和标签的大小等。

  2. 允许两个图的宽度不同。如上所述,这里棘手的事情是如何找到正确的宽度,因为这取决于绘图的其他各个方面。

首先我展示了我们如何解决第一个版本(扩展了多少y-axis)。然后使用类似的方法和一些额外的技巧,我们也可以解决变宽版本。

找到扩展多少的解决方案y-axis

鉴于预测绘图区域有多大的困难(这取决于绘图所有元素的相对大小),我们可以做的是保存一个虚拟绘图,在其中我们对绘图区域进行阴影处理在黑色中,读回图像文件,然后测量黑色区域的大小以确定绘图区域有多大:

1) 让我们从将绘图分配给变量开始

p1 = ggplot(df1) +
  geom_point(aes(value1, value2)) +
  facet_grid(~var1) +
  coord_fixed()

p2 = ggplot(df1) +
  geom_point(aes(value1, value2)) +
  facet_grid(~var2) +
  coord_fixed() 

2) 现在我们可以保存这些图的一些虚拟版本,这些图只显示一个黑色矩形,其中绘图区域是:

t_blank = theme(strip.background = element_rect(fill = NA),
      strip.text = element_text(color=NA),
      axis.title = element_text(color = NA),
      axis.text = element_text(color = NA),
      axis.ticks = element_line(color = NA))

p1 + geom_rect(aes(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf), fill='black') + 
     t_blank
ggsave(fn1 <- tempfile(fileext = '.png'), height=5, units = 'in')

p2 + geom_rect(aes(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf), fill='black') + 
     t_blank
ggsave(fn2 <- tempfile(fileext = '.png'), height=5, units = 'in')

3) 然后我们将这些读入一个数组(只需要第一个色带就足够了)

library(png)
p1.saved = readPNG(fn1)[,,1]
p2.saved = readPNG(fn2)[,,1]

4) 计算每个绘图区域的高度(值为零的black-shaded区域)

p1.height = diff(row(p1.saved)[range(which(p1.saved==0))])
p2.height = diff(row(p2.saved)[range(which(p2.saved==0))])

5) 根据这些找出我们需要将绘图区域扩大多少。请注意,我们从 1.1 中减去高度的比率,以说明原始地块已经在每个方向上按默认量 0.05 扩展的事实。 免责声明 -- 此公式适用于您的示例。我没有时间更广泛地检查它,它可能还需要调整以确保其他地块的普遍性

height.expand = 1.1 - p2.height / p1.height

6) 现在我们可以使用这个扩展因子保存图

ggsave("plot_2facet.pdf", p1, height=5, units = 'in')
ggsave("plot_4facet.pdf", p2 + scale_y_continuous(expand=c(height.expand, 0)), 
        height=5, units = 'in')

找到宽度改变多少的解决方案

首先,让我们将第一个图的宽度设置为我们想要的

p1.width = 10

现在,使用与上一节中相同的方法,我们可以找到该图中绘图区域的高度。

p1 + geom_rect(aes(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf), fill='black') + 
     t_blank
ggsave(fn1 <- tempfile(fileext = '.png'), height=5, width = p1.width, units = 'in')
p1.saved = readPNG(fn1, info = T)[,,1]
p1.height = diff(row(p1.saved)[range(which(p1.saved==0))])

接下来,我们找到第二个绘图必须具有的最小宽度才能获得相同的高度(注意 - 我们在这里寻找最小值,因为任何比这更大的宽度都不会增加高度,它已经填满了垂直 space,但会简单地在左边和右边添加白色space)

我们将使用函数 uniroot 求解宽度,该函数会找到函数过零的位置。要使用 uniroot,我们首先定义一个函数,该函数将在给定宽度作为参数的情况下计算绘图的高度。然后 returns 该高度与我们想要的高度之间的差异。此函数中的 if (x==0) x = -1e-8 行是一个肮脏的技巧,它允许 uniroot 求解一个达到零但不跨越它的函数 - see here.

fn2 <- tempfile(fileext = '.png')
find.p2 = function(w){
  p = p2 + geom_rect(aes(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf), fill='black') + 
           t_blank
  ggsave(fn2, p, height=5, width = w, units = 'in')
  p2.saved = readPNG(fn2, info = T)[,,1]
  p2.height = diff(row(p2.saved)[range(which(p2.saved==0))])
  x = abs(p1.height - p2.height)
  if (x==0) x = -1e-8
  x
}

N1 = length(unique(df$var1)) 
N2 = length(unique(df$var2)) 
p2.width = uniroot(find.p2, c(p1.width, p1.width*N2/N1))

现在我们已准备好保存具有正确宽度的绘图,以确保它们具有相同的高度。

p1
ggsave("plot_2facet.pdf", height=5, width = p1.width, units = 'in')
p2
ggsave("plot_4facet.pdf", height=5, width = p2.width$root, units = 'in')

您可以使用很棒的 egg 包来做到这一点(事实证明)。我实际上并不知道这是如何工作的,或者它是否比这种情况更普遍;我只是在 ggarrange 计算出对齐的基础上下了赌注。如果有人能阐明这一点,那就太好了!

library(egg)
getScale <- ggarrange(p1, p2, draw = F, ncol=2)
p1_sc <- ggarrange(p1, heights = getScale$heights[2])
ggsave("plot_2facet.pdf", plot=p1_sc, height=5, units = 'in')

p2_sc <- ggarrange(p2, heights = getScale$heights[2])
ggsave("plot_4facet.pdf", plot=p2_sc, height=5, units = 'in')

是的,我真的不知道这是怎么回事:

getScale$heights[2]
# [1] max(1*1null, 1*1null)
class(getScale$heights[2])
# [1] "unit.list" "unit"   

编辑 ..它似乎确实概括了

p3 <- ggplot() +
    geom_point(data = df, aes(value1, value2)) +
    facet_wrap(~var2, nrow=2) +
    coord_fixed()

getScale <- ggarrange(p1, p2, p3, draw = F, ncol=3)

p1_sc <- ggarrange(p1, heights = getScale$heights[2])
ggsave("plot_2facet.pdf", plot=p1_sc, height=5, units = 'in')

p2_sc <- ggarrange(p2, heights = getScale$heights[2])
ggsave("plot_4facet.pdf", plot=p2_sc, height=5, units = 'in')

p3_sc <- ggarrange(p3, heights = getScale$heights[2])
ggsave("plot_4facet_2row.pdf", plot=p3_sc, height=5, units = 'in')