如何将ggplot的单个图层转换为栅格图?

8

Matplotlib可以将绘图中的单个元素栅格化,并将其保存为混合像素/矢量图形(.pdf)(例如,请参见此答案)。如何在R中使用ggplot2实现相同的效果?


以下是一个玩具问题,我希望只能将geom_point层栅格化。

set.seed(1)
x <- rlnorm(10000,4)
y <- 1+rpois(length(x),lambda=x/10+1/x)
z <- sample(letters[1:2],length(x), replace=TRUE)

p <- ggplot(data.frame(x,y,z),aes(x=x,y=y)) +
  facet_wrap("z") +
  geom_point(size=0.1,alpha=0.1) +
  scale_x_log10()+scale_y_log10() +
  geom_smooth(method="gam",formula = y ~ s(x, bs = "cs"))
print(p)
ggsave("out.pdf", p)

当保存为.pdf文件后,Adobe Reader DC需要约1秒钟才能呈现图像。下面是.png版本的截图:out.png 当然,通常可以通过不绘制原始数据来避免这个问题。

这可能是一个真正的问题:考虑一篇科学文章的两个版本:https://arxiv.org/abs/1501.01332v2(所有图形矢量化)与https://arxiv.org/abs/1501.01332v3(所有图形光栅化)。第一个版本可能会卡住您的打印机或PDF查看器,而第二个版本则不够清晰,同时文件大小要大得多。 - jan-glx
1
https://dev59.com/cp7ha4cB1Zd3GeqPp95G#42059772 - baptiste
4
作为解决方法,将整个图以 dpi=600 或者 dpi=1200 的分辨率保存为 png 格式的图片,可以获得相当清晰的光栅图像而不会生成巨大的文件。png 特别设计用于线条图形。 - Claus Wilke
@YAK你有想要保存的示例绘图吗?我立刻想到的建议是让您查看grConvert和grImport2。https://www.stat.auckland.ac.nz/~paul/R/grImport2/grImport2.pdf。如果您提供一个示例绘图,我们可以根据它进行映射。 - Technophobe01
@Technophobe01 现在添加了一个。如果您是指修改生成的矢量图形,那么这是一个有趣的建议。您怎样保留图层关联性呢? - jan-glx
2个回答

14
感谢 Viktor Petukhov 和 Evan Biederstedt 创作的 ggrastr 包,现在可以对单个图层进行光栅化。但是,目前(2018-08-13)只支持 geom_point 和 geom_tile。同时,通过 Teun van den Brand 的工作,现在可以通过将其包装在 ggrastr::rasterise() 中来光栅化任何单个 ggplot 图层。
# install.packages('devtools')
# remotes::install_github('VPetukhov/ggrastr')

df %>% ggplot(aes(x=x, y=y)) +
      # this layer will be rasterized:
      ggrastr::rasterise(geom_point(size=0.1, alpha=0.1)) +
      # this one will still be "vector":
      geom_smooth()

以前,仅支持少数几个图形: 要使用它,您需要将geom_point替换为ggrastr::geom_point_rast

例如:

# install.packages('devtools')
# devtools::install_github('VPetukhov/ggrastr')
library(ggplot2)

set.seed(1)
x <- rlnorm(10000, 4)
y <- 1+rpois(length(x), lambda = x/10+1/x)
z <- sample(letters[1:2], length(x), replace = TRUE)

ggplot(data.frame(x, y, z), aes(x=x, y=y)) +
  facet_wrap("z") +
  ggrastr::geom_point_rast(size=0.1, alpha=0.1) +
  scale_x_log10() + scale_y_log10() +
  geom_smooth(method="gam", formula = y ~ s(x, bs = "cs"))
ggsave("out.pdf")

这将生成一个 pdf,其中只包含geom_point层作为光栅图像和其余内容作为矢量图形。总体上,该图像看起来与问题中的图像相同,但放大后会发现差别: zoom-in view of example picture 将其与全光栅图形进行比较: all-raster for comparison

4
我认为你设置了自己不会得到答案的问题。你写道:
“我期望得到一个扩展ggplot2的答案,允许导出具有栅格化图层的绘图,而对现有绘图命令的更改最小,即作为geom_…命令的包装器或这些命令的附加参数,或者作为ggsave命令的一部分,它需要绘图命令的未评估部分列表(每隔一次进行栅格化),而不是提供在链接问题中提供的hacky解决方法。”
这是一个主要的开发工作,可能需要高技能开发人员数周甚至更长时间的努力。由于Stack Overflow问题,没有人会这样做。如果没有实现,我将在此处描述如何实现您所要求的内容以及为什么这相当具有挑战性。
主要参与者
让我们从我们将要处理的主要参与者开始。在最高层上是ggplot2库。它接受数据框并将其转换为图形。ggplot2本身并不知道任何关于低级绘图的东西。它只处理线条、多边形、文本等,将它们以图形对象(grobs)的形式交给grid库。
grid库本身是一个相当高级别的库。它也不知道太多关于低级绘图的东西。它主要处理线条、多边形、文本等,将它们交给一个R图形设备。设备进行实际绘制。
有许多不同的R图形设备。在R命令行中输入?Devices以查看不完整的列表。有矢量图形设备,例如pdf、postscript或svg,光栅设备,如png、jpeg或tiff,以及交互式设备,如X11或quartz。显然,作为概念的栅格化仅对矢量图形设备有意义,因为栅格设备无论如何都会栅格化所有内容。重要的是,ggplot2和grid都不知道或关心您当前正在绘制的图形设备。它们处理可以在任何设备上绘制的图形对象。
理想的高级接口
高级接口应该由ggplot2的layer()函数中的rasterize选项组成。通过这种方式,可以简单地编写,例如,geom_point(rasterize = TRUE)来栅格化点图层。对于所有geoms和stats,这将透明地工作,因为它们都调用layer()。
可能的实现
我看到四种可能的实现路线,从最不可能到最不可能。

1. 理想情况下,layer()函数会将rasterize选项简单地传递给grid库,后者会将其传递给图形设备,以告诉它要栅格化绘图的哪些部分。这种方法需要对图形设备API进行重大更改。我认为这不可能发生。至少在我的有生之年内不会。

2. 或者,可以编写一个新的grob类型,在绘制grob时可以按需栅格化任何任意的grob。这种方法不需要对图形设备API进行更改,但需要详细了解grid库的低级实现。这也可能使此类图形的交互查看非常缓慢。

3. 与第二种方法相比稍微简单一些的替代方案是只在grob构建时对任意的grob进行一次栅格化,然后在每次绘制该grob时重复使用。这在交互式图形设备上会快得多,但如果交互式更改纵横比,则绘图会变形。然而,由于此功能的主要用途将是生成pdf输出(我假设),因此此选项可能已足够。

4. 最后,栅格化也可以在layer()函数中进行,该函数可以将常规栅格grob放置到grob树中。该解决方案类似于此处描述的技术。从技术上讲,它与第3种方法并没有太大区别。无论哪种方式,都需要编写代码来对grob树进行栅格化,然后用栅格grob替换它。

技术障碍

要将grob树的部分栅格化,我们必须将它们发送到R栅格图形设备以进行渲染。但是,没有一个将其渲染到内存的设备。因此,我们必须渲染到临时文件中(例如使用png()),然后再读回文件。这是可能的,但很丑陋。它还依赖于不保证在每个R安装中都可用的功能(如png())。

其次,为了将grob树的部分与整体渲染分开渲染,我们将不得不打开一个新的图形设备,除了当前打开的设备。这是可能的,但可能会导致意外的错误。我一直在处理这样的错误,例如在这里这里中与使用此技术的代码相关的问题。实现栅格化功能的人将不得不处理这些问题。

最终,我们需要使光栅化的代码被ggplot2库接受,因为我们需要替换layer()函数,我认为没有办法从一个单独的包中完成这个功能。考虑到光栅化解决方案可能会有多么粗糙(参见前两段),这可能是一个困难的任务。

很棒的分析!希望这能够激励某人去实现一个解决方案,而不仅仅是回答我的问题!你知道在哪里可以找到“图形设备API”的文档吗?当你说“至少不是在我的有生之年”时,你实际上是指:“不是在R核心团队的有生之年内”吗?还是有其他悲观的原因导致了这种说法? - jan-glx
更改图形设备的API是一项重大的任务,需要更改所有设备驱动程序和所有客户端代码。这不会轻易发生。我们已经缺少许多显然重要的东西,例如渐变填充、图案填充、图像填充等。光栅化是一个太具体的问题,不能被视为高优先级。回应可能会是:在更高层次上进行光栅化,然后将光栅化图像交给图形设备。这并不完全是不合理的。 - Claus Wilke
我认为方案#2可能是最通用和最可行的。 我相信它主要涉及从“rastergrob”复制代码并重新实现drawDetails函数,因此它会在运行时将光栅化的grob发送到低级图形设备。 @baptiste可能比我更好地理解这一点,并且可能会看到我不知道的一些问题。 - Claus Wilke
(如果您的Twitter上还没有显示)您可能想要查看ggrastr包,它为一些与@baptiste相似的几何对象提供了解决方法,但基于grid::grid.cap - jan-glx
1
ggraster现在适用于所有几何图形,并使用ggrastr::rasterize功能,该功能似乎实现了方法4 - jan-glx

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