将多个复杂图组合为单个图形中的面板

Posted

技术标签:

【中文标题】将多个复杂图组合为单个图形中的面板【英文标题】:Combining multiple complex plots as panels in a single figure 【发布时间】:2012-10-16 09:18:09 【问题描述】:

@backlin 介绍

可以使用layoutpar(mfrow=...) 将多个简单的图组合成一个图形中的面板。但是,更复杂的绘图往往会在内部设置自己的面板布局,从而使它们无法用作面板。有没有办法创建嵌套布局并将复杂的绘图封装到单个面板中?

我感觉grid 包可以做到这一点,例如通过在单独的视口中绘制面板,但无法弄清楚如何。这是一个演示问题的玩具示例:

my.plot <- function()
    a <- matrix(rnorm(100), 10, 10)
    plot.new()
    par(mfrow=c(2,2))
    plot(1:10, runif(10))
    plot(hclust(dist(a)))
    barplot(apply(a, 2, mean))
    image(a)

layout(matrix(1:4, 2, 2))
for(i in 1:4) my.plot()
# How to avoid reseting the outer layout when calling `my.plot`?

@alittleboy 的原始问题

我使用gplots 包中的heatmap.2 函数来生成热图。以下是单个热图的示例代码:

library(gplots)
row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
heatmap.2(row.scaled.expr, dendrogram ='row',
          Colv=FALSE, col=greenred(800), 
          key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
          trace='none', colsep=1:10,
          sepcolor='white', sepwidth=0.05,
          scale="none",cexRow=0.2,cexCol=2,
          labCol = colnames(row.scaled.expr),                 
          hclustfun=function(c)hclust(c, method='mcquitty'),
          lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),                 
)

但是,由于我想在单个图中比较多个热图,我使用 par(mfrow=c(2,2)) 然后调用 heatmap.2 四次,即

row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
arr <- array(data=row.scaled.expr, dim=c(dim(row.scaled.expr),4))
par(mfrow=c(2,2))
for (i in 1:4)
heatmap.2(arr[ , ,i], dendrogram ='row',
          Colv=FALSE, col=greenred(800), 
          key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
          trace='none', colsep=1:10,
          sepcolor='white', sepwidth=0.05,
          scale="none",cexRow=0.2,cexCol=2,
          labCol = colnames(arr[ , ,i]),                 
          hclustfun=function(c)hclust(c, method='mcquitty'),
          lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),                 
)

但是,结果不是单个图中的四个热图,而是四个单独的热图。换句话说,如果我使用pdf() 输出结果,文件是四页而不是一页。我需要在某处更改任何参数吗?非常感谢!

【问题讨论】:

如果你看heatmap.2的代码,例如使用page(heatmap.2),您会注意到它调用了plot.new(),它覆盖了您对par(mfrow=c(2,2)) 的调用。我尝试使用grid 引擎将每个heatmap.2 绘图限制在绘图区域的一个子部分中,但不知道该怎么做。 这个问题我之前在其他功能上也遇到过,我也遇到过这个问题。您介意我改写您的问题并添加更一般(但简短)的介绍吗? 我用常规的 heatmap() 函数完成了这个,方法是注释掉函数的一部分,然后使用 layout(),但这有点难看。 @Backlin:非常感谢 cmets!当然,如果您能改写我的问题并添加对该主题的介绍,我将不胜感激:) 我周末不在,但我们希望现在有人接听。 【参考方案1】:

gridGraphics 包可能会有所帮助,

library(gridGraphics)
library(grid)

grab_grob <- function()
  grid.echo()
  grid.grab()


arr <- replicate(4, matrix(sample(1:100),nrow=10,ncol=10), simplify = FALSE)

library(gplots)
gl <- lapply(1:4, function(i)
  heatmap.2(arr[[i]], dendrogram ='row',
            Colv=FALSE, col=greenred(800), 
            key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
            trace='none', colsep=1:10,
            sepcolor='white', sepwidth=0.05,
            scale="none",cexRow=0.2,cexCol=2,
            labCol = colnames(arr[[i]]),                 
            hclustfun=function(c)hclust(c, method='mcquitty'),
            lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),                 
  )
  grab_grob()
)

grid.newpage()
library(gridExtra)
grid.arrange(grobs=gl, ncol=2, clip=TRUE)

【讨论】:

这是一个优雅的解决方案,它允许人们继续使用像 pheatmap 这样的包。顺便说一句,要使用 pheatmap,不需要调用 grid.echo(),因为 pheatmap 已经使用了 grid 包。 如何使用此解决方案在 grobs 之间添加填充?无法使用 ggplots 以正常方式完成【参考方案2】:

正如 Dinre 所说,“网格”包可以处理所有复杂的情节。对于@alittleboy 的原始问题,我认为来自 Bionconductor 的包“ComplexHeatmap”(也是基于网格的)可能是一个不错的解决方案(http://www.bioconductor.org/packages/release/bioc/vignettes/ComplexHeatmap/inst/doc/ComplexHeatmap.html

【讨论】:

【参考方案3】:

我遇到了类似的问题,并想出了一个非常简单但需要安装 imagemagick 的解决方案。这个想法是将热图绘制到单独的文件中,然后将它们与 montage 命令结合起来:

library(gplots)
row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
arr <- array(data=row.scaled.expr, dim=c(dim(row.scaled.expr),4))
par(mfrow=c(2,2))
for (i in 1:4) 
    ifile <- paste0(i,'_heatmap.pdf')
    pdf(ifile)
    heatmap.2(arr[ , ,i], dendrogram ='row',
                        Colv=FALSE, col=greenred(800), 
                        key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
                        trace='none', colsep=1:10,
                        sepcolor='white', sepwidth=0.05,
                        scale="none",cexRow=0.2,cexCol=2,
                        labCol = colnames(arr[ , ,i]),                 
                        hclustfun=function(c)hclust(c, method='mcquitty'),
                        lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),                 
    )
    dev.off()

system('montage -geometry 100% -tile 2x2 ./*_heatmap.pdf outfile.pdf')

【讨论】:

【参考方案4】:

好的。我想这个问题已经有足够长的时间没有得到解答,应该写下长长的答案。

最困难的图形问题的答案是(正如@backlin 建议的那样)'grid' 包的原始使用。许多预构建的图形包会覆盖所有当前的视口和绘图设备设置,因此,如果您想以一种非常具体的方式完成某些事情,您必须自己构建它。

我建议阅读 Paul Murrell 的“R Graphics”一书并阅读有关“网格”包的章节。这是一本非常有用的书,我的办公桌上一直放着一本。

对于您的热图,我编写了一个快速入门,可以帮助您快速入门。

需要了解的功能

grid.newpage() 这会初始化绘图设备。不带参数使用。 grid.rect() 这会绘制一个矩形。您的热图基本上只是一组巨大的彩色矩形,因此这将是您图形的大部分。它的工作原理如下:grid.rect(x=x_Position, y=y_Position, width=width_Value, height=height_Value, gp=gpar(col=section_Color, fill=section_Color), just=c("left", "bottom"), default.units="native") 'just' 参数指定矩形的哪个点将位于您指定的 (x, y) 坐标上。 grid.text() 这会绘制文本。它是这样工作的:grid.text("Label Text", x_Value, y_Value, gp=gpar(col=color_Value, cex=font_Size), just=c("right","center"), rot=rot_Degrees, default.units="native") grid.lines() 这画了一条线。它是这样工作的:grid.lines(c(x_Start,x_End), c(y_Start, y_End), gp=gpar(col=color_Value), default.units="native") dataViewport() 这定义了绘图窗口的属性,“网格”指的是“视口”。像这样使用它:pushViewport(dataViewport(xData=x_Data, yData=y_Data, xscale=c(x_Min, x_Max), yscale=c(y_Min, y_Max), x=x_Value, y=y_Value, width=width_Value, height=height_Value, just=c("left","center"))) 这里有一些东西要记住...查看更详细的视口解释。 pushViewport() 这用于初始化一个视口。您将其包裹在视口定义中以实际执行视口,如下所示:pushViewport(dataViewport([stuff in here])) popViewport() 这最终确定了一个视口,并将您在视口层次结构中上移一级。查看更详细的视口说明。

视口简介

视口是定义“网格”对象的绘制位置和方式的临时绘图空间。视口内的所有内容都relative 绘制到视口。如果视口旋转,里面的所有东西都会旋转。视口可以嵌套,可以重叠,并且几乎可以无限灵活,但有一个例外:它们始终是一个矩形。

最初让很多人感到困惑的是坐标系。每个视口,包括初始的“grid.newpage()”视口,在 x 和 y 轴上都从 0 变为 1。原点 (0,0) 是最左下角,最大值 (1,1) 是最右上角。这是“npc”单位系统,所有没有指定一组单位的东西都可能最终根据这个系统绘制。这对您来说意味着两件事:

    在指定视口大小和位置时使用“npc”系统。只需假设您的视口必须使用“npc”坐标,您就会省去很多麻烦。这意味着如果我想绘制两个相邻的图,两个视口的定义将类似于: viewport(x=0, y=0, width=0.5, height=1, just=c("left","lower"))viewport(x=0.5, y=0, width=0.5, height=1, just=c("left","lower")) 如果您的视口具有不同的坐标系(例如用于绘制图形的视口),则需要为您绘制的每个“网格”对象指定“default.units”参数。例如,如果您尝试在 (2,4) 处绘制一个点,您将永远看不到该点,因为它会远离屏幕。指定 default.units="native" 将告诉该点使用视口自己的坐标系并正确绘制该点。

可以直接导航和写入视口,但除非您正在执行非常自动化的操作,否则指定视口、在其中绘制然后“弹出”(最终确定)视口会更容易。这会将您返回到父视口,您可以从下一个视口开始。弹出每个视口是一种简洁的方法,适合大多数用途(并且更容易调试!)。

“dataViewport”功能在绘制图表时非常重要。这是一种特殊类型的视口,可以为您处理所有坐标和比例,只要您告诉它您正在使用什么数据。这是我用于任何绘图区域的那个。当我第一次开始使用“grid”包时,我调整了所有值以适应“npc”坐标系,但这是一个错误!只要您记得为每个绘图项使用“本机”单位,“dataViewport”功能就很容易了。

免责声明

数据可视化是我的强项,我不介意花半天时间编写一个好的视觉效果脚本。 “网格”包让我能够比我发现的任何其他东西更快地创建非常复杂的视觉效果。我将我的视觉效果编写为函数,因此我可以快速加载各种数据。我再高兴不过了。

但是,如果您不喜欢编写脚本,“网格”将是您的敌人。此外,如果你认为半天的时间对于视觉来说太长了,那么“网格”对你的帮助不会太大。 (不)著名的“ggplot2”包是大多数人所接受的,我衷心推荐它,尽管我个人认为它没有用。

如果有人想要帮助学习“网格”图形,我非常愿意帮助教学。它彻底改变了我创建快速、智能和美观的数据视觉效果的能力。

【讨论】:

@Dinre:为什么你觉得 'ggplot2' 没用?到目前为止,(大约 1 年)我还没有发现它限制了我的工作(经济学和信息系统)。很高兴阅读您的想法! @PeterLustig 'ggplot2' 包严格绑定到你提供给它的数据的形状,它有一些关于如何渲染图层的细节。我倾向于以“ggplot2”不允许的方式组合数据显示(请参阅***.com/questions/15043172/…),因此我需要“网格”提供的较低级别的访问权限。实际上,“ggplot2”只是“网格”函数的一组包装函数。 @Dinre 感谢您提供有关网格包的其他文章的链接。我去看看。

以上是关于将多个复杂图组合为单个图形中的面板的主要内容,如果未能解决你的问题,请参考以下文章

将多个文本标签添加到单个 facet_grid 面板

将 BigQuery 中的多个 Firebase 数据集聚合为单个数据集

如何设计一个复杂的图形用户界面,同时使用构建器设计模式和抽象设计模式?

Java图形界面(GUI)——如何将JTable成功放入面板

如何在 JFrame 中排列多个面板

(转载) Java Swing 之 JScrollPane (滚动面板) 使用方法