在处理嵌套的命名向量时,有比 tidyr::unnest_longer() 更快的替代方法吗?
Any speedier alternatives to tidyr::unnest_longer() when dealing with nested named vectors?
我有一个大数据集,它有一个列表列,其中包含嵌套的 named 向量。我想将这些向量取消嵌套到两个新列中:
- 向量元素的一列
- 矢量元素的另一列 names
现在,我发现的唯一直接的方法是 tidyr::unnest_longer()
。虽然它非常适合小数据对象,但我发现它在处理非常大的数据时太慢了。因此,我正在寻找更快的替代方案。
我已经看到 让我接近,但不是我所需要的:unnest_wider()
的基础 R 替代品,但速度要快得多。但是,我正在寻找一种模仿 unnest_longer 的快速解决方案。
无论是基于base R
、data.table
、rrapply
, or 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)))
所以 my_tbl
是 given 我需要“取消嵌套”的数据对象。数据结构的简要预览显示列 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
“取消嵌套”
如果我们只有 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
但是在 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
由 reprex 包 (v2.0.0) 创建于 2021-08-23
虽然 my_tbl
有 200,000 行,但我的真实数据在这种格式下有超过 100 万行。所以我正在寻找最快的解决方案。
谢谢!
您可以对每个元素尝试 tibble::enframe
,然后 unnest
。我正在研究这个答案,寻找在展平之前将所有命名向量转换为数据帧的方法:Convert Named Character Vector to data.frame.
my_tbl %>%
mutate(col_1 = map(col_1, enframe)) %>%
unnest(col_1)
快速基准测试
library(tidyverse) # purrr, tibble, dplyr, tidyr
microbenchmark::microbenchmark(
tidyrunnestlonger =
my_tbl %>%
unnest_longer(col_1),
enframe =
my_tbl %>%
mutate(col_1 = map(col_1, enframe)) %>%
unnest(col_1),
times = 1
)
#-------------
Unit: seconds
expr min lq mean median uq max neval
tidyrunnestlonger 101.419181 101.419181 101.419181 101.419181 101.419181 101.419181 1
enframe 6.140771 6.140771 6.140771 6.140771 6.140771 6.140771 1
您可以 unlist
您的专栏:
x<-unlist(my_tbl[[1]])
res<-tibble(col_1=unname(x),col_1_id=names(x))
res
## A tibble: 1,000,000 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 999,990 more rows
我有一个大数据集,它有一个列表列,其中包含嵌套的 named 向量。我想将这些向量取消嵌套到两个新列中:
- 向量元素的一列
- 矢量元素的另一列 names
现在,我发现的唯一直接的方法是 tidyr::unnest_longer()
。虽然它非常适合小数据对象,但我发现它在处理非常大的数据时太慢了。因此,我正在寻找更快的替代方案。
我已经看到 unnest_wider()
的基础 R 替代品,但速度要快得多。但是,我正在寻找一种模仿 unnest_longer 的快速解决方案。
无论是基于base R
、data.table
、rrapply
, or 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)))
所以 my_tbl
是 given 我需要“取消嵌套”的数据对象。数据结构的简要预览显示列 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
“取消嵌套”
如果我们只有 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
但是在 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
由 reprex 包 (v2.0.0) 创建于 2021-08-23
虽然 my_tbl
有 200,000 行,但我的真实数据在这种格式下有超过 100 万行。所以我正在寻找最快的解决方案。
谢谢!
您可以对每个元素尝试 tibble::enframe
,然后 unnest
。我正在研究这个答案,寻找在展平之前将所有命名向量转换为数据帧的方法:Convert Named Character Vector to data.frame.
my_tbl %>%
mutate(col_1 = map(col_1, enframe)) %>%
unnest(col_1)
快速基准测试
library(tidyverse) # purrr, tibble, dplyr, tidyr
microbenchmark::microbenchmark(
tidyrunnestlonger =
my_tbl %>%
unnest_longer(col_1),
enframe =
my_tbl %>%
mutate(col_1 = map(col_1, enframe)) %>%
unnest(col_1),
times = 1
)
#-------------
Unit: seconds
expr min lq mean median uq max neval
tidyrunnestlonger 101.419181 101.419181 101.419181 101.419181 101.419181 101.419181 1
enframe 6.140771 6.140771 6.140771 6.140771 6.140771 6.140771 1
您可以 unlist
您的专栏:
x<-unlist(my_tbl[[1]])
res<-tibble(col_1=unname(x),col_1_id=names(x))
res
## A tibble: 1,000,000 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 999,990 more rows