可以创建插入式图表吗?

37
我知道当你使用par(fig=c( ... ), new=T)时,你可以创建插图。然而,我想知道是否可能使用ggplot2库来创建“插图”。
更新1:我尝试使用par()与ggplot2一起使用,但它不起作用。
更新2:我在ggplot2 GoogleGroups找到了一个可行的解决方案,使用grid::viewport()

1
这篇《Learning R》博客上的文章介绍了如何在一个图形中绘制另一个图形。该博客还有许多其他关于ggplot2的精彩文章。 - Erik Shilts
6个回答

28

该书的第8.4节讲解了如何做到这一点。诀窍是使用grid软件包的viewport

#Any old plot
a_plot <- ggplot(cars, aes(speed, dist)) + geom_line()

#A viewport taking up a fraction of the plot area
vp <- viewport(width = 0.4, height = 0.4, x = 0.8, y = 0.2)

#Just draw the plot twice
png("test.png")
print(a_plot)
print(a_plot, vp = vp)
dev.off()

3
需要指出的是,如果想要将另一个图作为插图,关键的一行代码应该是 print(another_plot, vp = vp)。我花了一些时间才明白这一点。+1 - mts

24

使用ggplot2egg可以得到更简单的解决方案。最重要的是,这种解决方案适用于ggsave

library(ggplot2)
library(egg)
plotx <- ggplot(mpg, aes(displ, hwy)) + geom_point()
plotx + 
  annotation_custom(
    ggplotGrob(plotx), 
    xmin = 5, xmax = 7, ymin = 30, ymax = 44
  )
ggsave(filename = "inset-plot.png")

10
嘿 @stackinator。感谢您提供简明的示例并提到了“egg”包。请注意,只使用“library(ggplot2)”就足够了(不需要额外使用“tidyverse”或“egg”)。 - Valentin_Ștefan
完全正确,@valentin。已将 tidyverse 依赖更改为 ggplot2。 - andschar

22
另外,可以使用 Claus O. Wilke 开发的 R 包 cowplotcowplotggplot2 的一个强大扩展)。作者在 此介绍性文章 中提供了关于在较大的图表内绘制插图的示例代码。以下是一些经过修改的代码:
library(cowplot)

main.plot <- 
  ggplot(data = mpg, aes(x = cty, y = hwy, colour = factor(cyl))) + 
  geom_point(size = 2.5)

inset.plot <- main.plot + theme(legend.position = "none")

plot.with.inset <-
  ggdraw() +
  draw_plot(main.plot) +
  draw_plot(inset.plot, x = 0.07, y = .7, width = .3, height = .3)

# Can save the plot with ggsave()
ggsave(filename = "plot.with.inset.png", 
       plot = plot.with.inset,
       width = 17, 
       height = 12,
       units = "cm",
       dpi = 300)

输入图片描述


我认为你也可以将 main.plot 传递给 ggdraw(),以节省一行代码。 - crsh
1
@Skiea,当然可以,每次需要新的插图时,只需添加一个新的draw_plot()函数即可。 - Valentin_Ștefan
@crsh,不确定你的意思,但我更喜欢将main.plotinset.plot分别创建,然后在上面的示例中“自然地”跟随plot.with.inset(命名不太好)。以下是我的想法:我认为这里(以及通常情况下)的重点是尽可能使代码对一般读者来说尽可能熟悉(影响可读性、理解和调试)。因此,我相信有时候较少的语法实际上可能会阻碍理解。然而,熟悉程度高度主观,我经常会让其他人和我未来的自己感到困惑 :/ - Valentin_Ștefan

16

'ggplot2' >= 3.0.0 允许使用新的方法添加插图,因为现在可以将包含列表作为成员列的 tibble 对象作为数据传递。该列表列中的对象甚至可以是完整的 ggplots... 我的软件包 'ggpmisc' 的最新版本提供了 geom_plot()geom_table()geom_grob(),还提供使用 npc 单位而非 native data 单位定位插图的版本。这些几何对象可以在一次调用中添加多个插图并遵守分面,而 annotation_custom() 则不行。我从帮助页面复制了一个示例,该示例将主绘图的放大细节作为插图添加到其中。

library(tibble)
library(ggpmisc)
p <-
  ggplot(data = mtcars, mapping = aes(wt, mpg)) +
  geom_point()
df <- tibble(x = 0.01, y = 0.01, plot = list(p + coord_cartesian(xlim = c(3, 4), ylim = c(13, 16)) + labs(x = NULL, y = NULL) + theme_bw(10))) p + expand_limits(x = 0, y = 0) + geom_plot_npc(data = df, aes(npcx = x, npcy = y, label = plot))

ggplot with ggplot as inset

或者从包的使用说明中获取一个条形图插图。

library(tibble)
library(ggpmisc)
p <- ggplot(mpg, aes(factor(cyl), hwy, fill = factor(cyl))) +
  stat_summary(geom = "col", fun.y = mean, width = 2/3) +
  labs(x = "Number of cylinders", y = NULL, title = "Means") +
  scale_fill_discrete(guide = FALSE)
data.tb <- tibble(x = 7, y = 44, plot = list(p + theme_bw(8)))
ggplot(mpg, aes(displ, hwy, colour = factor(cyl))) + geom_plot(data = data.tb, aes(x, y, label = plot)) + geom_point() + labs(x = "Engine displacement (l)", y = "Fuel use efficiency (MPG)", colour = "Engine cylinders\n(number)") + theme_bw()

enter image description here

下一个示例展示了如何在分面绘图的不同面板中添加不同的插图。下一个示例使用相同的示例数据,根据世纪拆分数据。该特定数据集在拆分后添加了一个插图中缺少级别的问题。由于这些绘图是独立构建的,因此我们需要使用手动比例来确保颜色和填充在所有绘图中保持一致。对于其他数据集,可能不需要这样做。

library(tibble)
library(ggpmisc)
my.mpg <- mpg
my.mpg$century <- factor(ifelse(my.mpg$year < 2000, "XX", "XXI"))
my.mpg$cyl.f <- factor(my.mpg$cyl)
my_scale_fill <- scale_fill_manual(guide = FALSE, 
                                   values = c("red", "orange", "darkgreen", "blue"),
                                   breaks = levels(my.mpg$cyl.f))

p1 <- ggplot(subset(my.mpg, century == "XX"),
             aes(factor(cyl), hwy, fill = cyl.f)) +
  stat_summary(geom = "col", fun = mean, width = 2/3) +
  labs(x = "Number of cylinders", y = NULL, title = "Means") +
  my_scale_fill

p2 <- ggplot(subset(my.mpg, century == "XXI"),
             aes(factor(cyl), hwy, fill = cyl.f)) +
  stat_summary(geom = "col", fun = mean, width = 2/3) +
  labs(x = "Number of cylinders", y = NULL, title = "Means") +
  my_scale_fill

data.tb <- tibble(x = c(7, 7), 
                  y = c(44, 44), 
                  century = factor(c("XX", "XXI")),
                  plot = list(p1, p2))

ggplot() +
  geom_plot(data = data.tb, aes(x, y, label = plot)) +
  geom_point(data = my.mpg, aes(displ, hwy, colour = cyl.f)) +
  labs(x = "Engine displacement (l)", y = "Fuel use efficiency (MPG)",
       colour = "Engine cylinders\n(number)") +
  scale_colour_manual(guide = FALSE, 
                    values = c("red", "orange", "darkgreen", "blue"),
                    breaks = levels(my.mpg$cyl.f)) +
  facet_wrap(~century, ncol = 1)

在这里输入图像描述


+1 这是唯一一个对我有效的解决方案,因为我在使用 hms / difftime 对象时遇到了问题,并且想要将绘图与另一个绘图结合起来(在我的情况下使用 patchwork 包)。使用其他解决方案时,我发现 rescale 函数无法处理带有 difftime 对象的数据。此外,可以使用参数 vp.widthvp.height 调整插图的尺寸。感谢 ggpmisc 包。 - Pascal Martin
你的回答对我解决了一个类似的问题,非常感谢!但是,不幸的是,我不明白你所说的“可以在一次调用中添加多个插图并遵守分面”的意思 - 我希望它能够实现,但我无法让它工作。目前,我有一个通过分面分成四个部分的图。但是当我尝试添加插图时,要么在每个分面中都有四个分面的插图,要么(如果在facetin之前构建插图)所有四个分面中都有相同的插图显示所有数据。你有什么想法可以改变吗?谢谢 :) - Apatura
@Apatura 最简单的方法是将插图想象成文本字符串,映射到标签美学,这就是我重用“标签”的原因。如果您想要四个插图,则需要在tibble的不同行中拥有每个插图。如果您想将它们放在特定的面板中,则此tibble需要包括具有与分面使用的名称和级别相同的因子列。相应行中因子的级别确定将插入哪个面板中。您不限于每个面板一个插图,它仅由数据驱动。 - Pedro J. Aphalo
谢谢您的快速回复!您是指类似于fourinsets <- tibble(x = 0,y = 10, plot = list(plot_fourfacets $ 1 plot_fourfacets $ 2))吗?还是我需要四行,每行都有plot =等等? 另外,您所说的包括一列是什么意思?而且,我可以先进行分面处理,然后从分面图中提取内嵌到tibble中,然后将该tibble添加到分面中,这样正确吗?也许我应该再提一个问题。 - Apatura
https://dev59.com/RFIG5IYBdhLWcg3w21iA - Apatura
请看上面。我已经添加了一个示例,其中包含两个面板,每个面板都有不同的插图。这个示例只是为了保持简短,但应该能够让您了解我在上面评论中所说的意思。 - Pedro J. Aphalo

16

我更喜欢能够与ggsave兼容的解决方案。经过大量搜索,我最终得到了以下内容(这是一个通用的公式,用于定位和调整插入的图形的大小)。

library(tidyverse)

plot1 = qplot(1.00*mpg, 1.00*wt, data=mtcars)  # Make sure x and y values are floating values in plot 1
plot2 = qplot(hp, cyl, data=mtcars)
plot(plot1)

# Specify position of plot2 (in percentages of plot1)
# This is in the top left and 25% width and 25% height
xleft   = 0.05
xright  = 0.30
ybottom = 0.70
ytop    = 0.95 

# Calculate position in plot1 coordinates
# Extract x and y values from plot1
l1 = ggplot_build(plot1)
x1 = l1$layout$panel_ranges[[1]]$x.range[1]
x2 = l1$layout$panel_ranges[[1]]$x.range[2]
y1 = l1$layout$panel_ranges[[1]]$y.range[1]
y2 = l1$layout$panel_ranges[[1]]$y.range[2]
xdif = x2-x1
ydif = y2-y1
xmin  = x1 + (xleft*xdif)
xmax  = x1 + (xright*xdif)
ymin  = y1 + (ybottom*ydif)
ymax  = y1 + (ytop*ydif) 

# Get plot2 and make grob
g2 = ggplotGrob(plot2)
plot3 = plot1 + annotation_custom(grob = g2, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)
plot(plot3)

ggsave(filename = "test.png", plot = plot3)

# Try and make a weird combination of plots
g1 <- ggplotGrob(plot1)
g2 <- ggplotGrob(plot2)
g3 <- ggplotGrob(plot3)

library(gridExtra)
library(grid)

t1 = arrangeGrob(g1,ncol=1, left = textGrob("A", y = 1, vjust=1, gp=gpar(fontsize=20)))
t2 = arrangeGrob(g2,ncol=1, left = textGrob("B", y = 1, vjust=1, gp=gpar(fontsize=20)))
t3 = arrangeGrob(g3,ncol=1, left = textGrob("C", y = 1, vjust=1, gp=gpar(fontsize=20)))

final = arrangeGrob(t1,t2,t3, layout_matrix = cbind(c(1,2), c(3,3)))
grid.arrange(final)

ggsave(filename = "test2.png", plot = final)

展示内嵌和相对复杂的布局的图片


2
我认为他们已经更新了ggplot包,现在从plot1坐标中提取位置的格式应该像这样:l1$layout$panel_ranges[[1]]$x.range[1]。请注意l1$layout$panel_ranges.... - s_scolary
1
你说得对。我已经相应地更新了答案。 - pallevillesen

7
在2019年,patchwork包被引入舞台,你可以使用inset_element()函数轻松地创建插入物(insets)
require(ggplot2)
require(patchwork)

gg1 = ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
  geom_point()

gg2 = ggplot(iris, aes(Sepal.Length)) +
  geom_density()

gg1 +
  inset_element(gg2, left = 0.65, bottom = 0.75, right = 1, top = 1)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接