R Plyr Sapply 似乎真的很慢
R Plyr Sapply seems to be really slow
我以为我有一个非常简单的数据帧转换,但出于某种原因我无法理解它似乎需要永恒,这让我怀疑它可能没有按照我的希望进行。任何人都可以解释一下吗?
第 1 部分 - 将源数据转换为单独的列(实际 df 有 260 万行)。
鉴于...
> V1 <- c("E11 2286 1", "ECAT 2286 1", "M11 2286 1", "M12 2286 1", "MCAT 2286 1", "C24 2287 1")
> df <- data.frame(V1)
> df
V1
1 E11 2286 1
2 ECAT 2286 1
3 M11 2286 1
4 M12 2286 1
5 MCAT 2286 1
6 C24 2287 1
我想创建两个新列(itemID 和主题)并用 V1 中相应行的子字符串填充每个列。
这个我可以用;
> require(stringr)
> df$itemID <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[2]] )
> df$topic <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[1]] )
但这需要几分钟时间,而且似乎应该有更有效的方法。所以首先我尝试使用 sapply;
> sapply(1:nrow(df), function(i) {
t <- str_split(df[i,"V1"]," ")
df$itemID <- t[[1]][[2]]
df$topic <- t[[1]][[1]]
})
一个多小时后,什么也没有。所以我保释了,因为当单个命令花费不到 20 分钟时,这显然毫无进展。
下一个选项是尝试在单个任务上使用 ddply,但也失败了。
> require(plyr)
> require(stringr)
> df$itemID <- ddply(df, .(V1), str_split(df$V1," ")[[1]][[2]], .progress="text" )
Error in get(as.character(FUN), mode = "function", envir = envir) :
object '2286' of mode 'function' was not found
所以对于这个任务的第一部分,任何人都可以;
- i) 告诉我哪种方法可能更快(sapply 或 ddply),并且
- ii) 使用该方法提供了如何将列 V1 拆分为两个所需的组件列的解决方案?
第 2 部分 - 收集项目 ID 的所有主题
为了加分...我需要的任务的第二部分是获取 2.6M 行(现在在 3 列中)并折叠每一行以获得 itemID,以便所有主题都保存在一个单元格中。
输出应该看起来像...
itemID topic
1 2286 E11,ECAT,M11,M12,MCAT
2 2287 C24
任何人都可以建议一个简单的方法来将这样的行聚集到一个单元格中吗?
我们可以使用几个选项来提高速度。
1. stringi
stringi
包中的函数通常更快。我们可以使用 stri_extract_all_regex
和适当的 regex
来提取字母数字字符。在这里,我根据显示的示例使用 [[:alnum:]]{2,}
。 rbind
列表元素 (do.call(rbind.data.frame,..)
),使用 setNames
更改列名称,将 'data.frame' 转换为 'data.table' (setDT
),以及 paste
按 'itemID' 分组的 'topic' 元素(toString
- 是 paste(., collapse=', ')
的包装器)。
library(stringi)
library(data.table)
setDT(setNames(do.call(rbind.data.frame,stri_extract_all_regex(df$V1,
'[[:alnum:]]{2,}')), c('topic', 'itemID')))[,
list(topic=toString(topic)), itemID]
# itemID topic
#1: 2286 E11, ECAT, M11, M12, MCAT
#2: 2287 C24
2。 dplyr/tidyr
我们可以使用 tidyr
中的 extract
通过指定适当的正则表达式将单列转换为多列,并且 paste
'topic' 由 [=43] 分组的元素=]
library(dplyr)
library(tidyr)
extract(df, V1, into= c('topic', 'itemID'), '([^ ]+) ([^ ]+).*',
convert=TRUE) %>%
group_by(itemID) %>%
summarise(topic=toString(topic))
# itemID topic
#1 2286 E11, ECAT, M11, M12, MCAT
#2 2287 C24
这个怎么样?使用 data.table v1.9.5:
require(data.table)
cols = c("topic", "itemID", "tmp")
setDT(df)[, c(cols) := tstrsplit(V1, " ", fixed=TRUE, type.convert=TRUE)]
df[, .(topic=paste(topic, collapse=", ")), by=itemID]
# itemID topic
# 1: 2286 E11, ECAT, M11, M12, MCAT
# 2: 2287 C24
260 万行的基准:
N = 2.6e6L
x = paste(rep(letters, length.out=N), sample(1e4, N, TRUE), "1", sep=" ")
dat = data.frame(x, stringsAsFactors=FALSE)
nrow(dat) # 2.6 million
# dplyr+tidyr
system.time({ans1 <- extract(dat, x, into= c('topic', 'itemID'),
'([^ ]+) ([^ ]+).*', convert=TRUE) %>%
group_by(itemID) %>%
summarise(topic=toString(topic))})
# user system elapsed
# 45.643 0.854 46.777
# data.table
system.time({
cols = c("topic", "itemID", "tmp")
setDT(dat)[, c(cols) := tstrsplit(x, " ", fixed=TRUE, type.convert=TRUE)]
ans2 <- dat[, .(topic=paste(topic, collapse=", ")), by=itemID]
})
# user system elapsed
# 1.906 0.064 1.981
identical(as.data.frame(ans1), setDF(ans2[order(itemID)]))
# [1] TRUE
加速为~24x。
更新: 运行 data.table
先回答再dplyr
回答结果运行次7s 和 44s,产生 ~6.3x 的加速。在 dplyr
.
之后 运行 时,data.table 方法似乎有一些缓存效率
我以为我有一个非常简单的数据帧转换,但出于某种原因我无法理解它似乎需要永恒,这让我怀疑它可能没有按照我的希望进行。任何人都可以解释一下吗?
第 1 部分 - 将源数据转换为单独的列(实际 df 有 260 万行)。
鉴于...
> V1 <- c("E11 2286 1", "ECAT 2286 1", "M11 2286 1", "M12 2286 1", "MCAT 2286 1", "C24 2287 1")
> df <- data.frame(V1)
> df
V1
1 E11 2286 1
2 ECAT 2286 1
3 M11 2286 1
4 M12 2286 1
5 MCAT 2286 1
6 C24 2287 1
我想创建两个新列(itemID 和主题)并用 V1 中相应行的子字符串填充每个列。
这个我可以用;
> require(stringr)
> df$itemID <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[2]] )
> df$topic <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[1]] )
但这需要几分钟时间,而且似乎应该有更有效的方法。所以首先我尝试使用 sapply;
> sapply(1:nrow(df), function(i) {
t <- str_split(df[i,"V1"]," ")
df$itemID <- t[[1]][[2]]
df$topic <- t[[1]][[1]]
})
一个多小时后,什么也没有。所以我保释了,因为当单个命令花费不到 20 分钟时,这显然毫无进展。
下一个选项是尝试在单个任务上使用 ddply,但也失败了。
> require(plyr)
> require(stringr)
> df$itemID <- ddply(df, .(V1), str_split(df$V1," ")[[1]][[2]], .progress="text" )
Error in get(as.character(FUN), mode = "function", envir = envir) :
object '2286' of mode 'function' was not found
所以对于这个任务的第一部分,任何人都可以;
- i) 告诉我哪种方法可能更快(sapply 或 ddply),并且
- ii) 使用该方法提供了如何将列 V1 拆分为两个所需的组件列的解决方案?
第 2 部分 - 收集项目 ID 的所有主题 为了加分...我需要的任务的第二部分是获取 2.6M 行(现在在 3 列中)并折叠每一行以获得 itemID,以便所有主题都保存在一个单元格中。
输出应该看起来像...
itemID topic
1 2286 E11,ECAT,M11,M12,MCAT
2 2287 C24
任何人都可以建议一个简单的方法来将这样的行聚集到一个单元格中吗?
我们可以使用几个选项来提高速度。
1. stringi
stringi
包中的函数通常更快。我们可以使用 stri_extract_all_regex
和适当的 regex
来提取字母数字字符。在这里,我根据显示的示例使用 [[:alnum:]]{2,}
。 rbind
列表元素 (do.call(rbind.data.frame,..)
),使用 setNames
更改列名称,将 'data.frame' 转换为 'data.table' (setDT
),以及 paste
按 'itemID' 分组的 'topic' 元素(toString
- 是 paste(., collapse=', ')
的包装器)。
library(stringi)
library(data.table)
setDT(setNames(do.call(rbind.data.frame,stri_extract_all_regex(df$V1,
'[[:alnum:]]{2,}')), c('topic', 'itemID')))[,
list(topic=toString(topic)), itemID]
# itemID topic
#1: 2286 E11, ECAT, M11, M12, MCAT
#2: 2287 C24
2。 dplyr/tidyr
我们可以使用 tidyr
中的 extract
通过指定适当的正则表达式将单列转换为多列,并且 paste
'topic' 由 [=43] 分组的元素=]
library(dplyr)
library(tidyr)
extract(df, V1, into= c('topic', 'itemID'), '([^ ]+) ([^ ]+).*',
convert=TRUE) %>%
group_by(itemID) %>%
summarise(topic=toString(topic))
# itemID topic
#1 2286 E11, ECAT, M11, M12, MCAT
#2 2287 C24
这个怎么样?使用 data.table v1.9.5:
require(data.table)
cols = c("topic", "itemID", "tmp")
setDT(df)[, c(cols) := tstrsplit(V1, " ", fixed=TRUE, type.convert=TRUE)]
df[, .(topic=paste(topic, collapse=", ")), by=itemID]
# itemID topic
# 1: 2286 E11, ECAT, M11, M12, MCAT
# 2: 2287 C24
260 万行的基准:
N = 2.6e6L
x = paste(rep(letters, length.out=N), sample(1e4, N, TRUE), "1", sep=" ")
dat = data.frame(x, stringsAsFactors=FALSE)
nrow(dat) # 2.6 million
# dplyr+tidyr
system.time({ans1 <- extract(dat, x, into= c('topic', 'itemID'),
'([^ ]+) ([^ ]+).*', convert=TRUE) %>%
group_by(itemID) %>%
summarise(topic=toString(topic))})
# user system elapsed
# 45.643 0.854 46.777
# data.table
system.time({
cols = c("topic", "itemID", "tmp")
setDT(dat)[, c(cols) := tstrsplit(x, " ", fixed=TRUE, type.convert=TRUE)]
ans2 <- dat[, .(topic=paste(topic, collapse=", ")), by=itemID]
})
# user system elapsed
# 1.906 0.064 1.981
identical(as.data.frame(ans1), setDF(ans2[order(itemID)]))
# [1] TRUE
加速为~24x。
更新: 运行 data.table
先回答再dplyr
回答结果运行次7s 和 44s,产生 ~6.3x 的加速。在 dplyr
.