处理嵌套命名向量时,有比 tidyr::unnest_longer() 更快的替代方法吗?

Emm*_*man 4 performance r data.table tidyr

我有一个大型数据集,其中有一个包含嵌套命名向量的列表列。我想将这些向量取消嵌套到两个新列中:

  • 一列用于向量元素
  • 另一列用于向量的元素名称

目前,我发现的唯一直接的方法是使用tidyr::unnest_longer(). 虽然它对于小数据对象非常有用,但我发现在处理非常大的数据时它太慢了。因此,我正在寻找更快的替代方案。

我看到一个答案让我很接近,但并不完全是我需要的:一个基本的 R 替代品,unnest_wider()但速度更快。但是,我正在寻找一种模仿 unnest_ long所做的快速解决方案。

无论是基于base Rdata.tablerrapply、 或collapse- 的解决方案,只要能减少处理时间,都受欢迎。

可重复的例子

数据

library(stringi)
library(tidyr)
library(magrittr, warn.conflicts = FALSE)

# simulate data
set.seed(123)
vec_n <- 1e6
vec_vals  <- 1:vec_n
vec_names <- stringi::stri_rand_strings(vec_n, 5)

my_named_vec <- setNames(vec_vals, vec_names)

split_func <- function(x, n) {
  unname(split(x, rep_len(1:n, length(x))))
} 

my_tbl <-
  tibble(col_1 = sample(split_func(my_named_vec, n = vec_n / 5)))
Run Code Online (Sandbox Code Playgroud)

我需要“解除嵌套”的给定my_tbl数据对象也是如此。数据结构的简要预览揭示了每行列中的嵌套命名向量。col_1

# preview my_tbl
my_tbl
#> # A tibble: 200,000 x 1
#>    col_1    
#>    <list>   
#>  1 <int [5]>
#>  2 <int [5]>
#>  3 <int [5]>
#>  4 <int [5]>
#>  5 <int [5]>
#>  6 <int [5]>
#>  7 <int [5]>
#>  8 <int [5]>
#>  9 <int [5]>
#> 10 <int [5]>
#> # ... with 199,990 more rows

head(my_tbl$col_1)
#> [[1]]
#>  9YAGC  hTjlr  vgxjQ  y4qG2  R1fUE 
#>  56356 256356 456356 656356 856356 
#> 
#> [[2]]
#>  nz5rk  ZvEe6  ustHv  2TdA8  Rreqn 
#> 119257 319257 519257 719257 919257 
#> 
#> [[3]]
#>  ubbWp  aw6zR  ab0Ax  N747j  GY1xU 
#>   4663 204663 404663 604663 804663 
#> 
#> [[4]]
#>  JHo4w  otk4s  BTZ3h  zlAKU  svSgH 
#>  75297 275297 475297 675297 875297 
#> 
#> [[5]]
#>  A1pxZ  T7y0l  0ixE2  DRBxP  IBqxe 
#>  19495 219495 419495 619495 819495 
#> 
#> [[6]]
#>  fDkau  Z7tmy  TIzgx  nKANU  Bqwo1 
#> 184074 384074 584074 784074 984074
Run Code Online (Sandbox Code Playgroud)

“解除嵌套”

如果我们只有 10 行

my_tbl[1:10, ] %>%
  tidyr::unnest_longer(col_1)
#> # A tibble: 50 x 2
#>     col_1 col_1_id
#>     <int> <chr>   
#>  1  56356 9YAGC   
#>  2 256356 hTjlr   
#>  3 456356 vgxjQ   
#>  4 656356 y4qG2   
#>  5 856356 R1fUE   
#>  6 119257 nz5rk   
#>  7 319257 ZvEe6   
#>  8 519257 ustHv   
#>  9 719257 2TdA8   
#> 10 919257 Rreqn   
#> # ... with 40 more rows
Run Code Online (Sandbox Code Playgroud)

但其中my_tbl有 200,000 行,因此这tidyr::unnest_longer()需要很多时间:

my_benchmark <-
  bench::mark(tidyrunnestlonger = 
              my_tbl %>%
              tidyr::unnest_longer(col_1)
)
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.

my_benchmark
#> # A tibble: 1 x 6
#>   expression             min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>        <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tidyrunnestlonger    2.47m    2.47m   0.00674     309MB     1.50
Run Code Online (Sandbox Code Playgroud)

由 reprex 包 (v2.0.0) 于 2021-08-23 创建


虽然my_tbl有 200,000 行,但我的真实数据有超过 100 万行这种格式。所以我正在寻找尽可能最快的解决方案。

谢谢!

nic*_*ola 6

你可以只是unlist你的专栏:

\n
x<-unlist(my_tbl[[1]])\nres<-tibble(col_1=unname(x),col_1_id=names(x))\nres\n## A tibble: 1,000,000 x 2\n#    col_1 col_1_id\n#    <int> <chr>   \n# 1  56356 9YAGC   \n# 2 256356 hTjlr   \n# 3 456356 vgxjQ   \n# 4 656356 y4qG2   \n# 5 856356 R1fUE   \n# 6 119257 nz5rk   \n# 7 319257 ZvEe6   \n# 8 519257 ustHv   \n# 9 719257 2TdA8   \n#10 919257 Rreqn   \n## \xe2\x80\xa6 with 999,990 more rows\n
Run Code Online (Sandbox Code Playgroud)\n