【问题标题】:Memory usage of pivot_longer run on small object在小对象上运行 pivot_longer 的内存使用情况
【发布时间】:2025-12-21 17:10:11
【问题描述】:

我正在处理由 528 列和 2,643,246 行组成的数据框。其中八个是字符变量,其余是整数。这总共增加了 11.35 GiB 的数据,我的可用 RAM 为 164 GiB。

我现在想在所述数据框上运行pivot_longer,每列有一行 + 两个 ID 变量(年份和机构)。 76年来共有机构671,370个。 所以 atm 数据的结构是这样的:

Institution Year X Y Z
A 1 2 1 3
A 2 3 4 4
B 1 3 4 2
B 2 5 3 2

我想在哪里改变它,使结构变成:

Institution Year G N
A 1 X 2
A 1 Y 1
A 1 Z 3
A 2 X 3
A 2 Y 1
A 2 Z 4
B 1 X 3
B 1 Y 4
B 1 Z 2
B 2 X 5
B 2 Y 3
B 2 Z 2

为了实现这一点,我尝试了以下代码:

library(tidyverse)
Df <- Df  %>% pivot_longer(17:527,
           names_to = "G",
           values_to = "N"
           )

在小样本数据上运行此程序时,我设法达到了预期的结果,但是当尝试对整个数据集执行相同操作时,我很快耗尽了内存。从使用 11 GiB 内存的对象开始,它迅速增加到 150 GiB 以上,然后返回“无法分配大小为 x Gb 的向量”错误。

由于我没有添加任何数据,我不太明白额外的内存使用量是从哪里来的。因此,我想知道是什么造成了这种增加,以及是否有更有效的方法可以通过其他一些代码来解决这个问题。 提前感谢您的帮助!

【问题讨论】:

    标签: r memory dplyr


    【解决方案1】:

    对于这个大小的数据,reshape2data.table 的旋转可能比 tidyr 的内存效率更高。在较小的 800MB 样本上,reshape2::melt 需要大约是原始数据的 2.6 倍内存,data.table::melt 需要大约 3.6 倍,而在这种情况下,tidyr::pivot_longer 方法需要大约 12 倍的内存,大约是慢 20 倍。

    编辑:在查看警告后,我意识到当我打电话给data.table::melt 时,我的早期草稿实际上是在后台使用reshape2,因为我给它喂了一个小玩意。添加了明确的 data.table::melt 解决方案,其中包括将数据转换为 data.table。对于这些数据,reshape2::melt 似乎更快,内存效率更高。

    样本数据

    n1 = as.integer(2E8/26)
    set.seed(42)
    data_long <- data.frame(Institution = 1:n1,
                            G = rep(letters, each = n1),
                            Z = rpois(26*n1, 10))
    data_wide <- data_long %>% pivot_wider(names_from = G, values_from = Z)
    

    基准测试

    bench::mark(
      tidyr::pivot_longer(data_wide, -1),
      reshape2::melt(data_wide, id.vars = 1),
      data.table::melt(data.table::as.data.table(data_wide), id.vars = 1),
      check = FALSE,
      iterations = 10
    )
    
    # A tibble: 3 x 13
      expression                                                               min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
      <bch:expr>                                                          <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
    1 tidyr::pivot_longer(data_wide, -1)                                    26.77s   37.38s    0.0269   10.52GB   0.0538    10    20       6.2m
    2 reshape2::melt(data_wide, id.vars = 1)                                 1.25s    1.73s    0.519     2.23GB   0.156     10     3      19.3s
    3 data.table::melt(data.table::as.data.table(data_wide), id.vars = 1)    1.82s    2.41s    0.332     3.01GB   0.232     10     7      30.1s
    # … with 4 more variables: result <list>, memory <list>, time <list>, gc <list>
    

    【讨论】:

    • reshape2::melt 命令完美运行!以防万一将来有人读到这篇文章,我必须删除所有 id 变量,但只有一个才能让它工作。包含这些后,我最终遇到了同样的问题,但是,它只使用了 4 倍的内存来完成。
    【解决方案2】:

    我无法根据您的数据测试代码,但这里有一个想法。

    这个想法是一次对一大块行进行从宽到长的转换,将结果存储在一个列表中。最后,将列表组合到最终的数据框。希望这可以减少内存使用量。

    如果不行,试试看melt from data.table能不能更高效的转换数据。

    另一个可能有用的想法。也许通过在宽到长转换之前删除第 1 到 16 列来子集Df,只需保留一个ID 列。您可以稍后将第 1 列到第 16 列连接回转换后的数据框。

    library(tidyverse)
    
    Df_list <- list()
    
    Institution <- unique(DF$Institution)
    
    for (i in Institution){
      Df_temp <- Df  %>%
        filter(Institution %in% i) %>%
        pivot_longer(17:527, names_to = "G", values_to = "N")
      Df_list[[i]] <- Df_temp
    }
    
    Df <- bind_rows(Df_list)
    

    【讨论】:

    • 谢谢!似乎该解决方案可能已经奏效,但是,在 16 小时后,我最终停止了该过程。那时它已经完成了 19 次迭代。