有效地将大列表融化成长格式

Melt large list into long format efficiently

我得到了一个大的list,格式如下:

example <- list("12908430751", "12908453145", c("12908453145","12908472085","453145472085"), c("12908453145", "12908472085", "453145472085"), "12908453145", c("12908453145", "12908472085", "453145472085"))

example
[[1]]
[1] "12908430751"

[[2]]
[1] "12908453145"

[[3]]
[1] "12908453145"  "12908472085"  "453145472085"

[[4]]
[1] "12908453145"  "12908472085"  "453145472085"

[[5]]
[1] "12908453145"

[[6]]
[1] "12908453145"  "12908472085"  "453145472085"

虽然使用 library(reshape2); melt(example) 适用于较小的数据集,但我的实际数据(约 600 万个元素)需要很长时间。我想知道是否有更有效的方法来实现这一点。

Output
      value     L1
1   12908430751  1
2   12908453145  2
3   12908453145  3
4   12908472085  3
5  453145472085  3
6   12908453145  4
7   12908472085  4
8  453145472085  4
9   12908453145  5
10  12908453145  6
11  12908472085  6
12 453145472085  6

我发现了一些类似的东西 Melt data.frame containing list to long format (efficiently) 但没能适应我的情况。

结果 example1 有 100 万个元素

system.time({foo <- unlist(lapply(example1, function(x) length(x)))
result <- data.frame(value = unlist(example1), 
L1 = unlist(sapply(1:length(foo), function(x) rep(x, foo[x]))))})

用户系统已过

9.63 0.10 9.73

system.time({
df <- structure(list(value = example1 , id = 1:length(example1)), .Names = 
c("value", "L1"), row.names = 1:length(example), class = "data.frame")
result1 <- setDT(df)[, .(value = unlist(value)), by = .(L1)]})

用户系统已过

1.25 0.00 1.26

system.time({result3 <- tibble(L1 = 1:length(example1), value = example1) %>% unnest()})

用户系统已过

5.99 0.00 5.98

system.time({ stack(setNames(example1, seq_along(example)))})

用户系统已过

1.08 0.00 1.08

无法让并行版本以结果结束,但可能会站在我这边。尽管我没有定义效率,但我采用最快的方法。

您可能会看到使用 parallel 不费吹灰之力的改进

library(parallel)
library(dplyr)
library(reshape2)
library(data.table)  # for rleid

cl <- makeCluster(detectCores())   # automatically detect number of cores
clusterEvalQ(cl, { library(reshape2) })  # need to export package to workers

# Split your data into chunks
nchunks <- 2   # does not need to equal number of cores (can be > # of cores but should be close to number of cores)
chunks <- split(example, cut(seq_along(example), nchunks))
result <- parLapply(cl, chunks, function(i) { melt(i) })
stopCluster(cl)

# combine back into data.frame
df <- Reduce("rbind", result)
answer <- df %>%
        mutate(L1 = rleid(L1))

输出

          value L1
1   12908430751  1
2   12908453145  2
3   12908453145  3
4   12908472085  3
5  453145472085  3
6   12908453145  4
7   12908472085  4
8  453145472085  4
9   12908453145  5
10  12908453145  6
11  12908472085  6
12 453145472085  6

如果您乐于使用 tidyverse 方法,不妨制作一个 tibble 然后 unnest(我不确定这对您的使用效率如何案例虽然):

library(tidyverse)

tibble(L1 = 1:length(example), value = example) %>% unnest()

#> # A tibble: 12 x 2
#>       L1        value
#>    <int>        <chr>
#>  1     1  12908430751
#>  2     2  12908453145
#>  3     3  12908453145
#>  4     3  12908472085
#>  5     3 453145472085
#>  6     4  12908453145
#>  7     4  12908472085
#>  8     4 453145472085
#>  9     5  12908453145
#> 10     6  12908453145
#> 11     6  12908472085
#> 12     6 453145472085

你可能想试试这个:

df <- structure(list(value = example , id = 1:length(example)), .Names = c("value", "L1"), 
            row.names = 1:length(example), class = "data.frame")

library(data.table)
setDT(df)[, .(value = unlist(value)), by = .(L1)]

##     L1        value
##  1:  1  12908430751
##  2:  2  12908453145
##  3:  3  12908453145
##  4:  3  12908472085
##  5:  3 453145472085
##  6:  4  12908453145
##  7:  4  12908472085
##  8:  4 453145472085
##  9:  5  12908453145
## 10:  6  12908453145
## 11:  6  12908472085
## 12:  6 453145472085

如果你四处挖掘,可能会有更快的方法,但基础 R 有 stack 工作得非常快:

stack(setNames(example, seq_along(example)))

#         values ind
#1   12908430751   1
#2   12908453145   2
#3   12908453145   3
#4   12908472085   3
#5  453145472085   3
#6   12908453145   4
#7   12908472085   4
#8  453145472085   4
#9   12908453145   5
#10  12908453145   6
#11  12908472085   6
#12 453145472085   6

它的内部结构基本上是一个unlist然后重复names(x)的每个值,相应的lengths(x)次。请参阅 utils:::stack.default 以阅读代码。