英文:
How to adjust heights of subplots in rmarkdown
问题
我想在一个图中绘制6个散点图子图。我尝试了不同的方法来调整子图的高度,但我没有找到令人满意的解决方案。
要么图太窄,无法正确查看信息,要么(如果我尝试使用绝对值调整高度)总是出现错误,说我的图区域太大,即使我选择了非常小的值。
我在RMarkdown中工作,输出为PDF
我迄今为止尝试过的方法:
- 使用相对宽度和高度的layout():
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black",bg="yellow", cex=0.7)
导致子图太窄:
- 使用绝对高度的layout():
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(lcm(4),lcm(4),lcm(4)))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
给我返回错误信息
"Error in plot.new()
:
! figure region too large
Backtrace:
- base::plot(...)
- graphics::plot.default(...)
- graphics::plot.new()
Execution halted"
即使我将每个图的高度减小到1厘米,这显然太小了。但是我仍然会收到错误消息,说图太大了。
- 仅使用par()来指定列和行:
par(mfrow=c(3,2))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
再次得到与尝试1(layout())一样的非常窄的图。
英文:
I want to plot 6 scatterplots as subplots in one figure. I tried different ways of adjusting the height of the subplots but I do not find a satisfying solution.
Either the plots are very narrow so information can not be seen properly or - if I try to adjust height with absoulte values - I always get the error, my figure region is too large, even if I chose very small values.
I am working in RMarkdown and output to PDF
What I tried so far:
- using layout() with relative widths and heights:
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
resulting in too narrow subplots:
- using layout() with absolute heights:
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(lcm(4),lcm(4),lcm(4)))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
giving me back the error
"Error in plot.new()
:
! figure region too large
Backtrace:
- base::plot(...)
- graphics::plot.default(...)
- graphics::plot.new()
Execution halted"
Even if I reduce the plot height to 1 cm each which is obviouls too small. But still I get the error, the figure is too large.
- only using par() to specify columns and rows:
par(mfrow=c(3,2))
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
again getting very narrow plots like with attempt no. 1 (layout())
答案1
得分: 0
前方建议使用本地分面图,如 ggplot2
,而不是使用基本图形。
数据的起始向量
我认为您的数据只是您本地环境中的一组向量。
set.seed(42)
totDistM <- runif(20)
totDistW <- runif(20)
OAM <- runif(20)
OAW <- runif(20)
PA_M <- runif(20)
PA_W <- runif(20)
UA_M <- runif(20)
UA_W <- runif(20)
head(totDistM)
# [1] 0.9148060 0.9370754 0.2861395 0.8304476 0.6417455 0.5190959
我建议将这些数据放入一个 data.frame
中,可以是一个大的数据框,也可以分为 M
和 W
。稍后会详细说明。
基本图形
这可以通过压缩图形的顶部和底部边距来解决。当您没有标题或 x 轴标签时,默认值 par(mar=c(5,4,4,2)+0.1)
是浪费的,考虑使用 par(mar = c(2,4,1,0) + 0.1)
。这会有所帮助。
仅通过这个变化,我们可以看到显著的改进:
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
par(mar = c(2,4,1,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
mar=
中的数字顺序在 ?par
中解释如下:
‘mar’ A numerical vector of the form ‘c(bottom, left, top, right)’
which gives the number of lines of margin to be specified on
the four sides of the plot. The default is ‘c(5, 4, 4, 2) +
0.1’.
我将 0
用于右侧,明显没有什么要显示的。2
用于底部,我们需要它来保留 x 刻度标记;如果添加 x 轴标签,那么可能需要增加这个值(通常为 4
,与左侧相同)。可以将顶部的 1
减少到 0
,以挤出更多空间,假设您没有标题或副标题。
注意:人们经常在这种情况下采取的一个方法是_"我不需要每个图上都有 x 刻度标记/标签,只有底部两个图需要"。这是正确的,也不难做到,只需更改对 plot
的各个调用的参数即可。然而,请注意,layout(.)
将保留每个子图的总体大小,而不考虑轴标记的存在,因此这样做会使某些图形比其他图形更大。 (对于 y 刻度也是如此。)
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
par(mar = c(0,4,2,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7, xaxt='n', main="totDistM")
par(mar = c(0,0,2,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7, xaxt='n', yaxt='n', ylab='', main="totDistW")
par(mar = c(0,4,0,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7, xaxt='n')
par(mar = c(0,0,0,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7, xaxt='n', yaxt='n', ylab='')
par(mar = c(2,4,0,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
par(mar = c(2,0,0,0) + 0.1) # 这是您的代码唯一的变化
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7, yaxt='n', ylab='')
请注意,左下角是最小的,其他的变化
英文:
Up front, I recommend going with a native-faceting plot such as ggplot2
instead of staying with base graphics.
Starting vectors of data
I believe your data is merely a set of vectors in your local environment.
set.seed(42)
totDistM <- runif(20)
totDistW <- runif(20)
OAM <- runif(20)
OAW <- runif(20)
PA_M <- runif(20)
PA_W <- runif(20)
UA_M <- runif(20)
UA_W <- runif(20)
head(totDistM)
# [1] 0.9148060 0.9370754 0.2861395 0.8304476 0.6417455 0.5190959
I suggest this should likely be in a data.frame
, either one big one or separated by M
/W
. More on that in a moment.
Base graphics
This can likely be remedied by compressing the top and bottom mar
gins in the plot. The defaults of par(mar=c(5,4,4,2)+0.1)
are wasteful when you have no title or x-axis labels, consider including par(mar = c(2,4,1,0) + 0.1)
. It'll help a little.
With just that change, we can see dramatic improvements:
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
par(mar = c(2,4,1,0) + 0.1) # the only change to your code
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7)
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7)
The order of the mar=
numbers are in ?par
:
‘mar’ A numerical vector of the form ‘c(bottom, left, top, right)’
which gives the number of lines of margin to be specified on
the four sides of the plot. The default is ‘c(5, 4, 4, 2) +
0.1’.
The 0
I have is for the right-side, clearly nothing to show there. The 2
is for the bottom, we need that to preserve the x-ticks; if you add an x-axis label then you'll need to increase this value (likely to 4
as on the left-side). You can reduce the 1
to 0
for the top to squeeze a little more space, assuming you have no title or subtitle.
Note: one direction people often go with plots like this is "I don't need x ticks/labels on every plot, only on the bottom two plots". This is correct, and not hard to do, just changing the arguments to individual calls to plot
. However, note that layout(.)
is going to preserve the overall side of each sub-plot regardless of the presence of axis ticks, so doing so will make some plots larger than others. (Similarly for y-ticks.)
nf <- layout(matrix(c(1,2,3,4,5,6), ncol=2,nrow=3, TRUE),widths = c(1,1), heights=c(1,1,1))
par(mar = c(0,4,2,0) + 0.1) # the only change to your code
plot(totDistM,OAM,pch = 21,col="black",bg="orange", cex=0.7, xaxt='n', main="totDistM")
par(mar = c(0,0,2,0) + 0.1) # the only change to your code
plot(totDistW,OAW,pch = 21,col="black",bg="yellow", cex=0.7, xaxt='n', yaxt='n', ylab='', main="totDistW")
par(mar = c(0,4,0,0) + 0.1) # the only change to your code
plot(totDistM,PA_M,pch = 21,col="black",bg="orange", cex=0.7, xaxt='n')
par(mar = c(0,0,0,0) + 0.1) # the only change to your code
plot(totDistW,PA_W,pch = 21,col="black",bg="yellow", cex=0.7, xaxt='n', yaxt='n', ylab='')
par(mar = c(2,4,0,0) + 0.1) # the only change to your code
plot(totDistM,UA_M,pch = 21,col="black",bg="orange", cex=0.7)
par(mar = c(2,0,0,0) + 0.1) # the only change to your code
plot(totDistW,UA_W,pch = 21,col="black", bg="yellow", cex=0.7, yaxt='n', ylab='')
Notice how the lower-left is the smallest, and the other vary. Can you work around this? Likely with a lot of playing, sure, but once the plotting canvas (all six combined) changes size, then other things happen and you will likely need to repeat to get the reshaping done just right.
(This is the main reason why I recommend ggplot2
or another plotting engine that handles these dimensions for you.)
On top of the use of par(mar=..)
, in the rmarkdown R chunk you can set the figure dimensions directly using fig.height
or fig.dim
in the chunk options, see https://bookdown.org/yihui/rmarkdown/tufte-figures.html.
ggplot2
This approach benefits from (and in some use-cases may require) the use of a data.frame
in your data. Even if not required, it really makes many things much easier.
quuxM <- data.frame(totDistM, OAM, PA_M, UA_M)
quuxW <- data.frame(totDistW, OAW, PA_W, UA_W)
head(quuxM) # same for quuxW
# totDistM OAM PA_M UA_M
# 1 0.9148060 0.37955924 0.5816040 0.3567220
# 2 0.9370754 0.43577158 0.1579052 0.4106351
# 3 0.2861395 0.03743103 0.3590283 0.5734759
# 4 0.8304476 0.97353991 0.6456319 0.5896783
# 5 0.6417455 0.43175125 0.7758234 0.7196573
# 6 0.5190959 0.95757660 0.5636468 0.3949730
Now here it might get a little murky, but ... many plotting engines do much better with "long" data, especially when it comes time to faceting. I'll reshape/pivot it from the above "wide" format to a "long" format, handling the "X" columns separately from the "Y" columns. I'll also remove the M
and W
from the ends of the variables so that they can be compared side-by-side (trust me, see the pic at the end).
quuxMlong <- reshape2::melt(quuxM, id.vars = "totDistM", variable.name = "Ytype", value.name = "Y")
quuxWlong <- reshape2::melt(quuxW, id.vars = "totDistW", variable.name = "Ytype", value.name = "Y")
head(quuxWlong)
# totDistW Ytype Y
# 1 0.90403139 OAW 0.6756073
# 2 0.13871017 OAW 0.9828172
# 3 0.98889173 OAW 0.7595443
# 4 0.94666823 OAW 0.5664884
# 5 0.08243756 OAW 0.8496897
# 6 0.51421178 OAW 0.1894739
names(quuxMlong)[1] <- "X"
quuxMlong$Xtype <- "totDistM"
names(quuxWlong)[1] <- "X"
quuxWlong$Xtype <- "totDistW"
quuxlong <- rbind(quuxMlong, quuxWlong)
quuxlong$Ytype <- gsub("_?[WM]", "", quuxlong$Ytype)
head(quuxlong)
# X Ytype Y Xtype
# 1 0.9148060 OA 0.37955924 totDistM
# 2 0.9370754 OA 0.43577158 totDistM
# 3 0.2861395 OA 0.03743103 totDistM
# 4 0.8304476 OA 0.97353991 totDistM
# 5 0.6417455 OA 0.43175125 totDistM
# 6 0.5190959 OA 0.95757660 totDistM
unique(quuxlong$Xtype)
# [1] "totDistM" "totDistW"
unique(quuxlong$Ytype)
# [1] "OA" "PA" "UA"
(There are other ways to get from your six vectors to a long frame. I'm not focusing on the options for this. Know that "wide form" frames make some things easier and "long form" frames make other things easier. In both cases, though, having frames is generally much better than work on lone vectors.)
From here, a single call handles all comparisons you had:
library(ggplot2)
ggplot(quuxlong, aes(X, Y)) +
facet_grid(Ytype ~ Xtype, scales = "free") +
geom_point(aes(fill = Xtype), shape = 21L) +
scale_fill_manual(values = c(totDistM = "orange", totDistW = "yellow")) +
labs(x = NULL, y = NULL) +
theme(legend.position = "bottom")
With this, you have no need to try to collapse blank space between plots. Granted, ggplot2
does have a small learning curve from base graphics, but it brings with it many capabilities that require strict control and attention to detail in base graphics to be able to replicate safely and consistently.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论