Amy*_*y M 0 performance r stringr data.table
我有一个大型 data.table,包含超过 700 万行和 38 列。其中一列是字符向量,其中包含很长的描述性句子。我知道每个句子的第一个单词是一个类别,第二个单词是一个名称,我需要将这两个单词放入两个新列中以供以后分析。
这可能不能很好地说明时间差异,因为它太小了(实际上system.time()在这个例子中给出了 0),但这里有一个玩具字符串来说明我想要做的事情:
# Load libraries:
library(data.table)
library(stringr)
# Create example character string:
x <- c("spicy apple cream", "mild peach melba", "juicy strawberry tart")
id <- c(1,2,3)
# Create dt:
mydt <- data.table(id = id, desert = x)
Run Code Online (Sandbox Code Playgroud)
假设在我的真实数据中,我想从每个字符串中提取第一个单词,并将其放入一个名为“category”的新变量中,然后从每个字符串中提取第二个单词并将其放入一个名为“fruit_name”的新变量中。
词法上最简单的方法似乎是使用stringr::word()which 很有吸引力,因为它避免了计算复杂的正则表达式的需要:
# Add a new category column:
mydt[, category := stringr::word(desert, 1)]
# Add a new fruit name column:
mydt[, fruit_name := stringr::word(desert, 2)]
Run Code Online (Sandbox Code Playgroud)
虽然这在小数据集上工作得很好,但在我的真实数据集上却花了很长时间(我怀疑它挂起了,尽管我杀死了它并在 10 分钟后重新启动了 R)。就上下文而言,该数据集中的其他字符向量类型操作大约需要 20 秒才能运行,因此该函数似乎特别耗费人力和计算资源。
相反,如果我使用正则表达式,sub()它不会挂起,并且似乎以与其他字符向量操作相同的速度运行:
# Create category column with regex:
mydt[, category := sub("(^\\w+).*", "\\1", desert)]
# Create fruit name column with regex:
mydt[, fruit_name := sub("^\\w+\\s+(\\w+).*", "\\1", desert)]
Run Code Online (Sandbox Code Playgroud)
谁能阐明这两种方法之间的速度差异?有趣的是,即使使用这个玩具示例,在给出结果之前运行system.time()也会stringr::word()挂起几秒钟,但这可能只是因为我的真实(大)数据集已加载到我的环境中。
是否stringr::word()以某种方式破坏了 data.table 通过引用替换的约定(创建新列而不复制整个表)?不知何故,我认为sub()这样做会更糟,因为它可能会复制整个字符串,然后替换为与正则表达式模式匹配的位,但实际上它要快得多。
任何见解非常感谢!
> sessionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)
Matrix products: default
locale:
[1] LC_COLLATE=English_United Kingdom.1252 LC_CTYPE=English_United Kingdom.1252
[3] LC_MONETARY=English_United Kingdom.1252 LC_NUMERIC=C
[5] LC_TIME=English_United Kingdom.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] officer_0.4.1 flextable_0.6.9 data.table_1.14.2 lubridate_1.8.0
[5] forcats_0.5.1 stringr_1.4.0 dplyr_1.0.7 purrr_0.3.4
[9] readr_2.1.0 tidyr_1.1.4 tibble_3.1.6 ggplot2_3.3.5
[13] tidyverse_1.3.1
loaded via a namespace (and not attached):
[1] xfun_0.28 tidyselect_1.1.1 haven_2.4.3 colorspace_2.0-2
[5] vctrs_0.3.8 generics_0.1.1 htmltools_0.5.2 base64enc_0.1-3
[9] utf8_1.2.2 rlang_0.4.12 pillar_1.6.4 glue_1.5.0
[13] withr_2.4.2 DBI_1.1.1 gdtools_0.2.3 dbplyr_2.1.1
[17] uuid_1.0-3 modelr_0.1.8 readxl_1.3.1 lifecycle_1.0.1
[21] munsell_0.5.0 gtable_0.3.0 cellranger_1.1.0 zip_2.2.0
[25] rvest_1.0.2 evaluate_0.14 knitr_1.36 fastmap_1.1.0
[29] tzdb_0.2.0 fansi_0.5.0 broom_0.7.10 Rcpp_1.0.7
[33] scales_1.1.1 backports_1.3.0 jsonlite_1.7.2 fs_1.5.0
[37] systemfonts_1.0.3 digest_0.6.28 hms_1.1.1 stringi_1.7.5
[41] grid_4.1.2 cli_3.1.0 tools_4.1.2 magrittr_2.0.1
[45] crayon_1.4.2 pkgconfig_2.0.3 ellipsis_0.3.2 xml2_1.3.2
[49] reprex_2.0.1 rmarkdown_2.11 assertthat_0.2.1 httr_1.4.2
[53] rstudioapi_0.13 R6_2.5.1 compiler_4.1.2
Run Code Online (Sandbox Code Playgroud)
这没有链接到data.table.
sub依赖于内部 C 代码调用:
function (pattern, replacement, x, ignore.case = FALSE, perl = FALSE,
fixed = FALSE, useBytes = FALSE)
{
if (is.factor(x) && length(levels(x)) < length(x)) {
sub(pattern, replacement, levels(x), ignore.case, perl,
fixed, useBytes)[x]
}
else {
if (!is.character(x))
x <- as.character(x)
.Internal(sub(as.character(pattern), as.character(replacement),
x, ignore.case, perl, fixed, useBytes))
}
}
Run Code Online (Sandbox Code Playgroud)
而stringr::word依赖于多个lapply//vapply调用mapply:
function (string, start = 1L, end = start, sep = fixed(" "))
{
n <- max(length(string), length(start), length(end))
string <- rep(string, length.out = n)
start <- rep(start, length.out = n)
end <- rep(end, length.out = n)
breaks <- str_locate_all(string, sep)
words <- lapply(breaks, invert_match)
len <- vapply(words, nrow, integer(1))
neg_start <- !is.na(start) & start < 0L
start[neg_start] <- start[neg_start] + len[neg_start] +
1L
neg_end <- !is.na(end) & end < 0L
end[neg_end] <- end[neg_end] + len[neg_end] + 1L
start[start > len] <- NA
end[end > len] <- NA
starts <- mapply(function(word, loc) word[loc, "start"],
words, start)
ends <- mapply(function(word, loc) word[loc, "end"], words,
end)
str_sub(string, starts, ends)
}
Run Code Online (Sandbox Code Playgroud)
对于单个字符串,没有太大区别:
desert <-"spicy apple cream"
microbenchmark::microbenchmark(
stringr::word(desert, 1),
sub("(^\\w+).*", "\\1", desert))
Unit: microseconds
expr min lq mean median uq max neval
stringr::word(desert, 1) 50.3 58.35 95.816 71.80 115.35 323.8 100
sub("(^\\\\w+).*", "\\\\1", desert) 46.3 51.05 68.810 53.85 63.20 265.1 100
Run Code Online (Sandbox Code Playgroud)
但如果你复制 10^6 次,sub速度就会快 20 倍:
desert <- rep("spicy apple cream",10^6)
microbenchmark::microbenchmark(
stringr::word(desert, 1),
sub("(^\\w+).*", "\\1", desert),times=5)
Unit: milliseconds
expr min lq mean median uq max
stringr::word(desert, 1) 11605.1720 13724.731 14484.9069 14043.3454 16066.1067 16985.1798
sub("(^\\\\w+).*", "\\\\1", desert) 696.2793 752.516 771.5857 797.5788 803.7969 807.7577
Run Code Online (Sandbox Code Playgroud)