pivot_longer 运行 在小对象上的内存使用

Memory usage of pivot_longer run on small object

我正在处理一个包含 528 列和 2,643,246 行的数据框。其中八个是字符变量,其余是整数。总共有 11.35 GiB 的数据,我的可用 RAM 为 164 GiB。

我现在想 运行 在所述数据框上 pivot_longer,每列一行 + 两个 ID 变量(年份和机构)。 76年以上机构总数671370所。 所以 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 内存的对象开始,它在返回“无法分配大小为 x Gb 的向量”错误之前迅速增加到 150 GiB 以上。

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

我无法在您的数据上测试代码,但这是一个想法。

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

如果不行,试试data.table中的melt是否可以更有效地转换数据。

另一个可能有用的想法。也许通过在从宽到长的转换之前删除第 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)

对于这种大小的数据,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>