如何在超过3000万行的数据库中让lapply更快?

问题描述 投票:0回答:1

我有一个非常大的数据库(超过 3000 万条记录),其结构如下:

test <- as.data.table(list(v1 = c("150,10001,11,Bien", "151,10002,11,Bien", 
"152,10003,11,Bien", "153,10004,11,Mal", 
"154,10005,11,Regular")))

我将该数据库下载为每行一个字符串,因为我在尝试使用

fread
直接阅读时错过了很多信息。例如,我收到与此类似的错误:
"Stopped early on line 101. Expected 1099 fields but found 1100. Consider fill=TRUE and comment.char=. First discarded non-empty line"
因此我无法按预期下载整个数据库。

我想要实现的是从这些字符串创建四个变量。此代码在我的虚拟数据库中完美运行:

test <- test[, `:=`(id = lapply(strsplit(test$v1, split=","), "[", 1),
                              question_id = lapply(strsplit(test$v1, split=","), "[", 2),
                              answer_id = lapply(strsplit(test$v1, split=","), "[", 3),
                              answer = lapply(strsplit(test$v1, split=","), "[", 4))]

在我的实际数据库中,这段代码花费了很多时间。我等了两个多小时,没有得到任何结果。我想这种方法可能是错误的,因为我不是自己创建变量而是列表,这样使得过程比应有的速度慢:

Classes ‘data.table’ and 'data.frame':  5 obs. of  5 variables:
 $ v1         : chr  "150,10001,11,Bien" "151,10002,11,Bien" "152,10003,11,Bien" "153,10004,11,Mal" ...
 $ id         :List of 5
  ..$ : chr "150"
  ..$ : chr "151"
  ..$ : chr "152"
  ..$ : chr "153"
  ..$ : chr "154"
 $ question_id:List of 5
  ..$ : chr "10001"
  ..$ : chr "10002"
  ..$ : chr "10003"
  ..$ : chr "10004"
  ..$ : chr "10005"
 $ answer_id  :List of 5
  ..$ : chr "11"
  ..$ : chr "11"
  ..$ : chr "11"
  ..$ : chr "11"
  ..$ : chr "11"
 $ answer     :List of 5
  ..$ : chr "Bien"
  ..$ : chr "Bien"
  ..$ : chr "Bien"
  ..$ : chr "Mal"
  ..$ : chr "Regular"
 - attr(*, ".internal.selfref")=<externalptr>

我读到,使用 Windows 时,

parLapply
(来自
parallel
库的函数)往往比
lapply
慢,所以我放弃了该解决方案。 任何建议将不胜感激。

r database parallel-processing data.table lapply
1个回答
0
投票

首先,可以避免在同一字符串上多次调用

tstrsplit
来获取特定列:

test <- as.data.table(list(v1 = c("150,10001,11,Bien", "151,10002,11,Bien","152,10003,11,Bien", "153,10004,11,Mal","154,10005,11,Regular")))
test[, c("id","question_id","answer_id","answer") := tstrsplit(v1, ",")][]
#                      v1     id question_id answer_id  answer
#                  <char> <char>      <char>    <char>  <char>
# 1:    150,10001,11,Bien    150       10001        11    Bien
# 2:    151,10002,11,Bien    151       10002        11    Bien
# 3:    152,10003,11,Bien    152       10003        11    Bien
# 4:     153,10004,11,Mal    153       10004        11     Mal
# 5: 154,10005,11,Regular    154       10005        11 Regular

既然你说你的行中有太多逗号,那么这仍然会失败。对于这些,从您当前(太慢)的方法来看,您只需要每个字符串的前四个元素,因此我们可以先将其删除。我将修改示例数据,以便我们可以在这里看到效果。

test <- as.data.table(list(v1 = c("150,10001,11,Bien", "151,10002,11,Bien","152,10003,11,Bien", "153,10004,11,Mal","154,10005,11,Regular")))

test[4, v1 := paste0(v1, ",quux")]
test[, c("id","question_id","answer_id","answer") := tstrsplit(v1, ",")][]
# Error in `[.data.table`(test, , `:=`(c("id", "question_id", "answer_id",  : 
#   Supplied 4 columns to be assigned 5 items. Please see NEWS for v1.12.2.

您当前(太慢)的方法表明您只想要前四个,并且愿意放弃后面的所有内容,所以我建议使用条件

sub

test[nchar(gsub("[^,]+", "", v1)) > 3, v1 := sub(",[^,]*$", "", v1)]
test[, c("id","question_id","answer_id","answer") := tstrsplit(v1, ",")][]
#                      v1     id question_id answer_id  answer
#                  <char> <char>      <char>    <char>  <char>
# 1:    150,10001,11,Bien    150       10001        11    Bien
# 2:    151,10002,11,Bien    151       10002        11    Bien
# 3:    152,10003,11,Bien    152       10003        11    Bien
# 4:     153,10004,11,Mal    153       10004        11     Mal
# 5: 154,10005,11,Regular    154       10005        11 Regular

但是,我认为所有这些都是不必要的,因为您可以指示

fread
在发现提取列时进行填充(如警告/错误消息所示)。

fread("quux.csv")
# Warning in fread("quux.csv") :
#   Stopped early on line 4. Expected 4 fields but found 5. Consider fill=TRUE and comment.char=. First discarded non-empty line: <<153,10004,11,Mal,quux>>
#       V1    V2    V3     V4
#    <int> <int> <int> <char>
# 1:   150 10001    11   Bien
# 2:   151 10002    11   Bien
# 3:   152 10003    11   Bien
fread("quux.csv", fill = TRUE)
#       V1    V2    V3      V4     V5
#    <int> <int> <int>  <char> <char>
# 1:   150 10001    11    Bien       
# 2:   151 10002    11    Bien       
# 3:   152 10003    11    Bien       
# 4:   153 10004    11     Mal   quux
# 5:   154 10005    11 Regular       

这当然是一条更安全的路径,因为它允许您评估额外的列是否可以丢弃,或者其他列之一是否应该在带引号的字符串中嵌入逗号。

© www.soinside.com 2019 - 2024. All rights reserved.