我正在尝试读取几十万个JSON文件,最终将它们放入dplyr对象中。但是JSON文件不是简单的键值解析,它们需要大量的预处理。预处理是经过编码的,效率相当高。但我遇到的挑战是将每条记录有效地加载到单个对象(data.table或dplyr对象)中。
这是非常稀疏的数据,我将有超过2000个变量,这些变量大部分都会丢失。每条记录可能有一百个变量集。变量将是字符,逻辑和数字的混合,我知道每个变量的模式。
我认为避免R为每次更新复制对象(或一次添加一行)的最佳方法是创建一个空数据帧,然后在从JSON文件中提取后更新特定字段。但是在数据框中执行此操作非常慢,移动到数据表或dplyr对象要好得多,但仍希望将其减少到几分钟而不是几小时。请参阅下面的示例:
timeMe <- function() {
set.seed(1)
names = paste0("A", seq(1:1200))
# try with a data frame
# outdf <- data.frame(matrix(NA, nrow=100, ncol=1200, dimnames=list(NULL, names)))
# try with data table
outdf <- data.table(matrix(NA, nrow=100, ncol=1200, dimnames=list(NULL, names)))
for(i in seq(100)) {
# generate 100 columns (real data is in json)
sparse.cols <- sample(1200, 100)
# Each record is coming in as a list
# Each column is either a character, logical, or numeric
sparse.val <- lapply(sparse.cols, function(i) {
if(i < 401) { # logical
sample(c(TRUE, FALSE), 1)
} else if (i < 801) { # numeric
sample(seq(10), 1)
} else { # character
sample(LETTERS, 1)
}
}) # now we have a list with values to populate
names(sparse.val) <- paste0("A", sparse.cols)
# and here is the challenge and what takes a long time.
# want to assign the ith row and the named column with each value
for(x in names(sparse.val)) {
val=sparse.val[[x]]
# this is where the bottleneck is.
# for data frame
# outdf[i, x] <- val
# for data table
outdf[i, x:=val]
}
}
outdf
}
我认为每个列的模式可能已经设置并在每次更新时重置,但我也通过预先设置每个列类型尝试了这一点,但这没有帮助。
对我来说,使用data.frame运行此示例(上面已注释掉)大约需要22秒,转换为data.table是5秒。我希望有人知道幕后发生的事情,并且可以提供一种更快的方法来填充数据表。
答案 0 :(得分:13)
我遵循您的代码,但构建sparse.val
的部分除外。分配列的方式有一些小错误。不要忘记检查答案是否正确尝试优化:)。
data.table
:由于您说您已经知道列的类型,因此预先生成正确的类型非常重要。否则,当您这样做时:DT[, LHS := RHS]
和RHS
类型不等于LHS
,RHS将被强制转换为LHS类型。在您的情况下,所有数值和字符值都将转换为逻辑,因为所有列都是逻辑类型。这不是你想要的。
因此创建矩阵将无济于事(所有列都属于同一类型)+它也很慢。相反,我会这样做:
rows = 100L
cols = 1200L
outdf <- setDT(lapply(seq_along(cols), function(i) {
if (i < 401L) rep(NA, rows)
else if (i >= 402L & i < 801L) rep(NA_real_, rows)
else rep(NA_character_, rows)
}))
现在我们有正确的类型集。接下来,我认为它应该是i >= 402L & i < 801L
。否则,您将前401列指定为逻辑列,然后将前801列指定为数字列,如果您知道前面列的类型,则没有多大意义,对吧?
names(.) <-
:该行:
names(sparse.val) <- paste0("A", sparse.cols)
会创建一个副本,但并不是必需的。因此,我们将删除此行。
for(x in names(sparse.val)) {
val=sparse.val[[x]]
outdf[i, x:=val]
}
实际上并没有做你认为它正在做的事情。它没有将val
的值分配给分配给x
的名称。相反,它(过度)写入(每次)一个名为x
的列。检查你的输出。
这不是优化的一部分。这只是为了让你知道你真正想要做什么。
for(x in names(sparse.val)) {
val=sparse.val[[x]]
outdf[i, (x) := val]
}
请注意(
周围的x
。现在,它将被评估,x
中包含的值将是val
的值将被分配到的列。我明白,这有点微妙。但是,这是必要的,因为它允许将x
列创建为DT[, x := val]
,您实际上希望将val
分配给x
。
回到优化,好消息是,你耗时的循环只是:
set(outdf, i=i, j=paste0("A", sparse.cols), value = sparse.val)
这是data.table
按引用分配功能派上用场的地方!
你的最终功能如下:
timeMe2 <- function() {
set.seed(1L)
rows = 100L
cols = 1200L
outdf <- as.data.table(lapply(seq_len(cols), function(i) {
if (i < 401L) rep(NA, rows)
else if (i >= 402L & i < 801L) rep(NA_real_, rows)
else sample(rep(NA_character_, rows))
}))
setnames(outdf, paste0("A", seq(1:1200)))
for(i in seq(100)) {
sparse.cols <- sample(1200L, 100L)
sparse.val <- lapply(sparse.cols, function(i) {
if(i < 401L) sample(c(TRUE, FALSE), 1)
else if (i >= 402 & i < 801L) sample(seq(10), 1)
else sample(LETTERS, 1)
})
set(outdf, i=i, j=paste0("A", sparse.cols), value = sparse.val)
}
outdf
}
通过这样做,您的解决方案在我的系统上 9.84秒,而上述功能需要 0.34秒,这是〜29倍的改进。我认为这是你正在寻找的结果。请验证。
HTH