作为R中data.table包相对不太熟练的用户,我一直在尝试将一个文本列处理成大量的指示器列(虚拟变量),其中每个列中的1表示在字符串列中找到了特定的子字符串。例如,我想处理这个:
ID String
1 a$b
2 b$c
3 c
转化为以下内容:
ID String a b c
1 a$b 1 1 0
2 b$c 0 1 1
3 c 0 0 1
我已经想出如何处理数据,但它运行的时间比我想象中要长,而且我怀疑我的代码效率不高。下面是一个包含虚拟数据的可复现代码版本。请注意,在真实数据中,有2000多个子字符串需要搜索,每个子字符串大约有30个字符长,并且可能有数百万行数据。如果需要,我可以并行化处理并投入很多资源,但我想尽可能地优化代码。我已经尝试过运行Rprof,但没有发现明显的(对我来说)改进。
set.seed(10)
elements_list <- c(outer(letters, letters, FUN = paste, sep = ""))
random_string <- function(min_length, max_length, separator) {
selection <- paste(sample(elements_list, ceiling(runif(1, min_length, max_length))), collapse = separator)
return(selection)
}
dt <- data.table(id = c(1:1000), messy_string = "")
dt[ , messy_string := random_string(2, 5, "$"), by = id]
create_indicators <- function(search_list, searched_string) {
y <- rep(0, length(search_list))
for(j in 1:length(search_list)) {
x <- regexpr(search_list[j], searched_string)
x <- x[1]
y[j] <- ifelse(x > 0, 1, 0)
}
return(y)
}
timer <- proc.time()
indicators <- matrix(0, nrow = nrow(dt), ncol = length(elements_list))
for(n in 1:nrow(dt)) {
indicators[n, ] <- dt[n, create_indicators(elements_list, messy_string)]
}
indicators <- data.table(indicators)
setnames(indicators, elements_list)
dt <- cbind(dt, indicators)
proc.time() - timer
user system elapsed
13.17 0.08 13.29
编辑
感谢大家提供的出色回答--都比我使用的方法好得多。下面是一些速度测试的结果,每个函数都稍作修改,在我的代码中使用0L和1L,按方法将结果存储在单独的表中,并对顺序进行了标准化。这些是从单个速度测试中获得的经过时间(而不是从许多测试中获得的中位数),但较大的运行时间很长。
Number of rows in dt 2K 10K 50K 250K 1M
OP 28.6 149.2 717.0
eddi 5.1 24.6 144.8 1950.3
RS 1.8 6.7 29.7 171.9 702.5
Original GT 1.4 7.4 57.5 809.4
Modified GT 0.7 3.9 18.1 115.2 473.9
GT4 0.1 0.4 2.26 16.9 86.9
很明显,改进版的GeekTrader方法是最好的。我对每个步骤的具体作用还有点模糊,但我可以慢慢理解。虽然有些超出了原问题的范围,如果有人能更有效地解释GeekTrader和Ricardo Saporta的方法,那么我和未来访问此页面的其他人都会感激。我特别想了解为什么有些方法比其他方法更具可扩展性。
*****编辑#2*****
我试图通过这条评论编辑GeekTrader的答案,但似乎不起作用。我对GT3函数进行了两个非常小的修改,a)对列进行排序,这会增加一些时间,b)将0和1替换为0L和1L,这会加快速度。将结果称为GT4函数。上面的表已编辑以添加GT4在不同表大小下的时间。显然是最佳选择,而且具有直观性的优势。