【问题标题】:Speed up rendering of large heatmap from ggplot in R加快从 R 中的 ggplot 渲染大型热图
【发布时间】:2018-10-19 09:26:28
【问题描述】:

我正在尝试在 R 中绘制一个使用 ggplot 生成的大型热图。最终,我想使用 Illustrator 来“润色”这个热图。

示例代码:

# Load packages (tidyverse)
library(tidyverse)

# Create dataframe
df <- expand.grid(x = seq(1,100000), y = seq(1,100000))

# add variable: performance
set.seed(123)
df$z <- rnorm(nrow(df))

ggplot(data = df, aes(x = x, y = y)) +
  geom_raster(aes(fill = z)) 

虽然我将绘图保存为矢量图像(.pdf;不是那么大),但打开时 pdf 的加载速度非常慢。我希望在打开文件时呈现数据框中的每个单独的点。

我已阅读其他使用image() 可视化矩阵的帖子(例如Data exploration in R: display heatmap of large matrix, quickly?),但是我想使用 ggplot 来修改图像。

问题:如何加快这个情节的渲染速度?有没有办法(除了降低绘图的分辨率),同时保持图像矢量化,加快这个过程?是否可以对矢量化 ggplot 进行下采样?

【问题讨论】:

  • 尝试改用geom_raster()。要求grid 图形子系统生成那么多矩形对象不会有好的结果。另外:ggplo2 在渲染之前会复制所有数据,因此这也会占用大量内存,直到 ggplot2 对象消失。 image() 很可能是更好的方法或非 ggplot2 空间栅格绘图功能之一。
  • 由于有这么多点,您实际上无法理解单个点,您可以分箱(方形箱或十六进制箱)并绘制热图。对于要在绘图中突出显示的异常点,请根据 bin 中的点数进行过滤。例如,如果一个 bin 只有 1 个点,只需绘制点而不是 bin。
  • 我注意到在最近的一次编辑中,您将 expand.grid 从 1e5x1e5 更改为 1e6x1e6。这现在会引发 cannot allocate vector of size 3725.3 Gb 错误。建议回滚此编辑,因为几乎没有人拥有这么大的计算机。事实上,1e4x1e4 已经在突破界限。
  • 您的热图是 1M 行 x 1M 列,或 1 万亿像素。在 300 dpi 时,您需要一个 277 英尺宽的显示器来显示它。我想你的屏幕或页面比那个小? ;-)

标签: r pdf ggplot2 heatmap


【解决方案1】:

我尝试的第一件事是 stat_summary_2d 获得平均分箱,但它似乎很慢并且还在右侧和顶部边缘产生了一些伪影:

library(tidyverse)
df <- expand.grid(x = seq(1,1000), y = seq(1,1000))
set.seed(123)
df$z <- rnorm(nrow(df))
print(object.size(df), units = "Mb")
#15.4 Mb

ggplot(data = df, aes(x = x, y = y, z = z)) +
  stat_summary_2d(bins = c(100,100)) +  #10x downsample, in this case
  scale_x_continuous(breaks = 100*0:10) +
  labs(title = "stat_summary_2d, 1000x1000 downsampled to 100x100")  

尽管这比您建议的数据小得多,但在我的机器上绘制仍然需要大约 3 秒,并且在顶部和右侧边缘有伪影,我认为是因为这些 bin 是边缘较小的 bin,留下更多变化。

当我按照您的要求尝试更大的网格时,它变得更慢了。

(顺便说一句,像 PDF 这样的矢量图形文件与光栅图形不同,可以在不损失分辨率的情况下调整大小。但是,在这个用例中,输出是 10,000 兆像素的光栅文件, 远远超出人类感知的极限,即导出为矢量格式,其中每个“像素”在 PDF 中变成一个非常小的矩形。矢量格式的使用可能对某些不寻常的情况有用情况下,例如,如果您需要在不损失分辨率的情况下将热图炸毁到巨大的表面上,例如足球场。但听起来在这种情况下,它可能是错误的工具,因为您将大量将无法感知的矢量文件中的数据。)

更有效的方法是在ggplot 之前使用dplyr 进行平均。有了这个,我可以在发送到 ggplot 之前获取一个 10k x 10k 的数组并将其下采样 100 倍。这必然会降低分辨率,但我不理解在这个用例中保留分辨率超出人类感知能力的价值。

这里有一些代码可以自己进行分桶,然后绘制下采样版本:

# Using 10k x 10k array, 1527.1 Mb when initialized
downsample <- 100
df2 <- df %>%
  group_by(x = downsample * round(x / downsample),
           y = downsample * round(y / downsample)) %>%
  summarise(z = mean(z))

ggplot(df2, aes(x = x, y = y)) +
  geom_raster(aes(fill = z)) +
  scale_x_continuous(breaks = 1000*0:10) +
  labs(title = "10,000x10,000 downsampled to 100x100")

【讨论】:

    【解决方案2】:

    您的可重现示例只是显示噪音,因此很难知道您想要什么样的输出。

    一种方法是关注@dww's suggestion 并使用geom_hex 显示汇总数据。

    另一种方法,当您问“是否可以对矢量化 ggplot 进行下采样?”时,在 geom_raster 的数据参数中使用 dplyr::sample_fracdplyr::sample_n。不过,我必须采取比您的示例更小的样本,否则我无法构建 df。

    library(tidyverse)
    
    # Create dataframe
    df <- expand.grid(x = seq(1,1000), y = seq(1,1000))
    
    # add variable: performance
    set.seed(123)
    df$z <- rnorm(nrow(df))
    
    ggplot(data = df, aes(x = x, y = y)) +
      geom_raster(aes(fill = z), . %>% sample_frac(0.1)) 
    

    如果你想从你的高分辨率 ggplot 对象开始,你可以做同样的效果:

    gg <- ggplot(data = df, aes(x = x, y = y)) +
      geom_raster(aes(fill = z)) 
    
    gg$data <-  sample_frac(gg$data,0.1)
    gg
    

    【讨论】:

      猜你喜欢
      • 2020-11-25
      • 1970-01-01
      • 2013-12-07
      • 2019-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-09
      • 1970-01-01
      相关资源
      最近更新 更多