筛选R/Rcpp中data.frame列表列内容的最快方法
我有一个data.frame:筛选R/Rcpp中data.frame列表列内容的最快方法,r,performance,data.table,dplyr,rcpp,R,Performance,Data.table,Dplyr,Rcpp,我有一个data.frame: df <- structure(list(id = 1:3, vars = list("a", c("a", "b", "c"), c("b", "c"))), .Names = c("id", "vars"), row.names = c(NA, -3L), class = "data.frame") 我想根据setdiff(vars,删除此项) 但是要删除字符(0)变量,我必须执行以下操作: res %>% unnest(vars) # and
df <- structure(list(id = 1:3, vars = list("a", c("a", "b", "c"), c("b",
"c"))), .Names = c("id", "vars"), row.names = c(NA, -3L), class = "data.frame")
我想根据setdiff(vars,删除此项)
但是要删除字符(0)
变量,我必须执行以下操作:
res %>% unnest(vars) # and then do the equivalent of nest(vars) again after...
实际数据集:
- 560K行和3800K行,它们还有10个以上的列(要随身携带)
R
中,最快的方法是什么?
- 是否有
/dplyr
/其他更快的方法数据表
- 如何使用
Rcpp
- 是否可以就地修改列,而不是复制
结果)lappy(vars,setdiff(…
- 如果必须是单独的步骤,那么过滤
最有效的方法是什么vars==character(0)
data.table
解决方案会自动变得更快,因为您不必为了添加列而复制整个内容:
library(data.table)
dt = as.data.table(df) # or use setDT to convert in place
dt[, newcol := lapply(vars, setdiff, 'a')][sapply(newcol, length) != 0]
# id vars newcol
#1: 2 a,b,c b,c
#2: 3 b,c b,c
您还可以通过在末尾添加[,vars:=NULL]
来删除原始列(成本基本为0)。或者,如果您不需要该信息,也可以简单地覆盖初始列,即dt[,vars:=lappy(vars,setdiff,'a')]
现在,就算法改进而言,假设您的
id
值对于每个vars
都是唯一的(如果不是,则添加一个新的唯一标识符),我认为这会更快,并自动进行过滤:
dt[, unlist(vars), by = id][!V1 %in% 'a', .(vars = list(V1)), by = id]
# id vars
#1: 2 b,c
#2: 3 b,c
要继续其他列,我认为最简单的方法是合并回:
dt[, othercol := 5:7]
# notice the keyby
dt[, unlist(vars), by = id][!V1 %in% 'a', .(vars = list(V1)), keyby = id][dt, nomatch = 0]
# id vars i.vars othercol
#1: 2 b,c a,b,c 6
#2: 3 b,c b,c 7
还有一种方法:
# prep
DT <- data.table(df)
DT[,vstr:=paste0(sort(unlist(vars)),collapse="_"),by=1:nrow(DT)]
setkey(DT,vstr)
get_badkeys <- function(x)
unlist(sapply(1:length(x),function(n) combn(sort(x),n,paste0,collapse="_")))
# choose values to exclude
baduns <- c("a","b")
# subset
DT[!J(get_badkeys(baduns))]
模拟:
讨论:
对于eddi()
和hannahh()
,结果几乎不会随着nvals
、nbads
和maxlen
而改变。相反,当baduns
超过20时,frank()
变得异常缓慢(大约20秒以上);它还随着nbads
和maxlen
的增加而增大,比其他两个稍微差一点
按比例增加nobs
,eddi()
领先hannahh()
的比例保持不变,大约是10倍。与frank()
相比,它有时缩小,有时保持不变。在nobs=1e5
最好的情况下,对于frank()
,eddi()
的速度仍然是3倍
如果我们从字符的valset
切换到frank()
必须强制为其by rowpaste0
操作的字符,那么eddi()
和hannahh()
都会随着nobs
的增长而击败它
重复执行此操作的基准。这可能是显而易见的,但如果您必须“多次”执行此操作(…很难说有多少次),创建键列比为每组
baduns
进行子集设置要好。在上面的模拟中,eddi()
的速度大约是frank()的5倍
,所以如果我做了10多次子集设置,我会选择后者
maxbadlen <- 2
set_o_baduns <- replicate(10,sample(valset,size=sample(maxbadlen,1)))
system.time({
DT <- data.table(exdf)
DT[,vstr:=paste0(sort(unlist(vars)),collapse="_"),by=1:nrow(DT)]
setkey(DT,vstr)
for (i in 1:10) DT[!J(get_badkeys(set_o_baduns[[i]]))]
})
# user system elapsed
# 0.29 0.00 0.29
system.time({
dt = as.data.table(exdf)
for (i in 1:10) dt[,
unlist(vars), by = id][!V1 %in% set_o_baduns[[i]],
.(vars = list(V1)), keyby = id][dt, nomatch = 0]
})
# user system elapsed
# 0.39 0.00 0.39
system.time({
for (i in 1:10) hannahh(exdf,set_o_baduns[[i]])
})
# user system elapsed
# 4.10 0.00 4.13
maxbadlen还有一个想法:
df %>%
rowwise() %>%
do(id = .$id, vars = .$vars, newcol = setdiff(.$vars, "a")) %>%
mutate(length = length(newcol)) %>%
ungroup()
其中:
# id vars newcol length
#1 1 a 0
#2 2 a, b, c b, c 2
#3 3 b, c b, c 2
# id vars newcol length
#1 2 a, b, c b, c 2
#2 3 b, c b, c 2
然后可以在length>0上进行筛选,以仅保留非空newcol
df %>%
rowwise() %>%
do(id = .$id, vars = .$vars, newcol = setdiff(.$vars, "a")) %>%
mutate(length = length(newcol)) %>%
ungroup() %>%
filter(length > 0)
其中:
# id vars newcol length
#1 1 a 0
#2 2 a, b, c b, c 2
#3 3 b, c b, c 2
# id vars newcol length
#1 2 a, b, c b, c 2
#2 3 b, c b, c 2
注意:正如@Arun在评论中提到的,这种方法非常慢。你最好使用数据表
解决方案。通过“过滤器”你似乎是指删除行,在setdiff
之后,你会得到字符(0)
…只有当列表是单例列表(“a”)时才会出现这种情况
。是这样吗?无论如何,要谈论速度,您可能需要生成一些示例数据。@Frank是的。通常我想删除setdiff
计算结果为字符(0)的行
。控制是否删除会很方便。我认为只有第一个@name会被ping。eddi意味着data.table
方式会进行适当的修改。我认为最好的做法是,如果内容是分析密集型的,就不要使用列表列。相反,为子集创建虚拟对象或分类/字符串变量(如我的回答所示)。[续…]它在逻辑上不如对列表列进行子集设置和筛选那么优雅,但出于美观考虑,您也可以保留后者……只是不要将其用于昂贵或重复的分析操作。就automagic而言,键入分类变量确实会使子集设置变得相当即时(至少在我回答中的示例数据中)--这是数据。table
显示了它的强度。@Frank在简单的情况下,是的,但如果OP一次取出几个字母,则需要检查所有combinations@Frank别忘了OP提到他们有一堆额外的列要携带,我认为使用rowwise()
+do())
I应该只保留id
和newcol
,然后在原始数据帧上使用id
进行left_join()
,对吗?@stevenbaupré如果id
是行号(看起来),简单地用它子集要比加入它快得多。不过,我不知道这是否可以在单个管道表达式中实现。好的,你的方法比我的快5倍。我还研究了更广泛的模拟参数集,发现当baduns
(要排除的元素)集“非常大”时,我的方法速度会大大减慢(超过20人),在这一点上,它比OP的慢。在处理非字符时,它也慢得多。如果我们可以将by=
与列表列一起使用,以便我的paste0
可以按组而不是按行进行,那么我的方法可能会赶上。如果多次使用键,那么使用键的点仍然成立。我想每个键都对dif有用不同的上下文。您的是目前为止最快的一次性操作。do()
maxbadlen <- 2
set_o_baduns <- replicate(10,sample(valset,size=sample(maxbadlen,1)))
system.time({
DT <- data.table(exdf)
DT[,vstr:=paste0(sort(unlist(vars)),collapse="_"),by=1:nrow(DT)]
setkey(DT,vstr)
for (i in 1:10) DT[!J(get_badkeys(set_o_baduns[[i]]))]
})
# user system elapsed
# 0.29 0.00 0.29
system.time({
dt = as.data.table(exdf)
for (i in 1:10) dt[,
unlist(vars), by = id][!V1 %in% set_o_baduns[[i]],
.(vars = list(V1)), keyby = id][dt, nomatch = 0]
})
# user system elapsed
# 0.39 0.00 0.39
system.time({
for (i in 1:10) hannahh(exdf,set_o_baduns[[i]])
})
# user system elapsed
# 4.10 0.00 4.13
df %>%
rowwise() %>%
do(id = .$id, vars = .$vars, newcol = setdiff(.$vars, "a")) %>%
mutate(length = length(newcol)) %>%
ungroup()
# id vars newcol length
#1 1 a 0
#2 2 a, b, c b, c 2
#3 3 b, c b, c 2
df %>%
rowwise() %>%
do(id = .$id, vars = .$vars, newcol = setdiff(.$vars, "a")) %>%
mutate(length = length(newcol)) %>%
ungroup() %>%
filter(length > 0)
# id vars newcol length
#1 2 a, b, c b, c 2
#2 3 b, c b, c 2