在R中处理列表列(data.table / dplyr)

3

我有一个大型数据集,使用列表列进行组织(20Gb)。

1-除了使用Data.table可以提高两倍速度之外,还有其他技巧可以用于类似情况的优化吗?

2-除了saveRDS之外,是否有其他更快的文件库(例如vroom、fst和fwrite不支持listcols),可以支持listcolumns?

3-我尝试了dt[,.(test=tib_sort[tib_sort[, .I[.N]], stringi::stri_sub(dt$ch, length = 5)],by=id)]但是它会抛出一个错误的维数数量。是否有一种方法可以使用listcolumn并自动设置DT和键/索引来执行by操作?

library(dplyr)
library(purrr)
library(data.table)
library(tictoc)

Toy数据

set.seed(123)
tib <-
  tibble(id = 1:20000) %>% mutate(k = map_int(id,  ~ sample(c(10:30), 1)))
tib <-
  tib %>% mutate(tib_df = map(k,  ~ tibble(
    ch = replicate(.x, paste0(
      sample(letters[1:24],
             size = sample(c(10:20), 1)),
      collapse = ""
    )),
    num = sample(1:1e10, .x,replace = F)
  )))

Dplyr

help <- function(df) {
  df <- df %>% top_n(1, num) %>% select(ch)
  stringi::stri_sub(df, length = 5)
}
tic("purrr")
tib <- tib %>% mutate(result = map_chr(tib_df, help))
toc(log = T, quiet = T)

Data.table

dt <- copy(tib)
setDT(dt)
tic("setDT w key")
dt[, tib_df := lapply(tib_df, setDT)]
dt[, tib_sort := lapply(tib_df, function(x)
  setindex(x, "num"))]
toc(log = T, quiet = T)
tic("dt w key")
dt[, result_dt_key := sapply(tib_df, function(x) {
  x[x[, .I[.N]], stringi::stri_sub(ch, length = 5)]
})]
toc(log=T, quiet = T)

计时

    tic.log(format = T)
[[1]]
[1] "purrr: 25.499 sec elapsed"

[[2]]
[1] "setDT w key: 4.875 sec elapsed"

[[3]]
[1] "dt w key: 12.077 sec elapsed"

添加了dplyr和data.table中已解嵌套版本后的修改和更新

1 purrr: 25.824 sec elapsed          
2 setDT wo key: 2.97 sec elapsed     
3 dt wo key: 13.724 sec elapsed      
4 setDT w key: 1.778 sec elapsed     
5 dt w key: 11.489 sec elapsed       
6 dplyr,unnest: 1.496 sec elapsed    
7 dt,I,unnest: 0.329 sec elapsed     
8 dt, join, unnest: 0.325 sec elapsed

tic("dt, join, unnest")
b <- unnest(tib)
setDT(b)
unnest.J <- b[b[, .(num=max(num)), by = 'id'], on=c('id','num')][,r2:=stringi::stri_sub(ch,length=5)][]
toc(log=T)

 res <- list(unnest.J$r2,tib2$result2,dt$result_dt_key,dt$result_dt,tib$result)
 sapply(res,identical,unnest.I$r2)
[1] TRUE TRUE TRUE TRUE TRUE

那么,我的结论是,虽然listcolumns作为数据结构在分析中看起来很诱人,但它们要慢得多slooower

enter image description here


需要保留数据结构吗?我认为大多数情况下“展平”更有意义。 - JBGruber
这并非绝对必要,但我发现这种结构可以增加分析的可读性。 - Misha
2
你可能还想检查一下,确保每种方法都能得到相同的结果。我认为setindex不会按照你期望的方式对数据进行排序(你需要使用setkey);如果在最大值处存在并列情况,使用join方式会返回多行df,而其他方式只返回一行。(我看到你是通过无重复抽样生成num的,但也许在实际应用中会有问题。) - Frank
1
@Frank,感谢您关于相等结果的建议。请查看编辑。 - Misha
1个回答

4

操作列表列的速度往往较慢,因为函数必须遍历条目并且不能进行向量化计算。通常情况下,将列表列 "unnest" 是有意义的:

tic("unnest")
tib2 <- tib %>% 
  tidyr::unnest(tib_df) %>%
  group_by(id) %>% 
  top_n(1, num) %>% 
  mutate(result = stringi::stri_sub(ch, length = 5))
toc(log = T, quiet = T)

结果:

> tic.log(format = T)
[[1]]
[1] "purrr: 39.54 sec elapsed"

[[2]]
[1] "setDT w key: 10.7 sec elapsed"

[[3]]
[1] "dt w key: 19.19 sec elapsed"

[[4]]
[1] "unnest: 1.62 sec elapsed"

使用您的模拟数据,最终对象仅略有不同。但如果需要恢复原始形式,您可能希望执行以下操作:

tic("unnest+reattach")
tib2 <- tib %>% 
  tidyr::unnest(tib_df) %>%
  group_by(id) %>% 
  top_n(1, num) %>% 
  mutate(result = stringi::stri_sub(ch, length = 5))

tib3 <- tib %>% 
  mutate(result = tib2$result[match(id, tib2$id)])

toc(log = T, quiet = T)

tic.log(format = T)[[5]]
[1] "unnest+reattach: 1.83 sec elapsed"

感谢您的输入。我会尽快恢复正常的数据结构。请查看我的编辑。 - Misha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接