我一直在使用stringr,因为它应该更快,但我今天发现它在处理因子项时要慢得多.我没有看到任何警告表明情况会如此,也没有看到为什么会如此.

例如:

string_options = c("OneWord", "TwoWords", "ThreeWords")

sample_chars = sample(string_options, 1e6, replace = TRUE)
sample_facts = as_factor(sample_chars)

正如预期的那样,当处理character个术语时,base R比stringr慢.但当处理factor个术语时,Base R速度快了30倍.

bench::mark(
    base_chars = grepl("Two", sample_chars),
    stringr_chars = str_detect(sample_chars, "Two"),
    base_facts = grepl("Two", sample_facts),
    stringr_facts = str_detect(sample_facts, "Two")
)

# A tibble: 4 × 13
#  expression         min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result            memory             time             gc      
#  <bch:expr>    <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>            <list>             <list>           <list>  
#1 base_chars     116.1ms 116.38ms      8.58    3.81MB        0     5     0      583ms <lgl [1,000,000]> <Rprofmem [1 × 3]> <bench_tm [5]>   <tibble>
#2 stringr_chars  86.04ms   88.2ms     11.3     3.81MB        0     6     0      532ms <lgl [1,000,000]> <Rprofmem [2 × 3]> <bench_tm [6]>   <tibble>
#3 base_facts      3.59ms   3.65ms    271.     11.44MB        0   136     0      501ms <lgl [1,000,000]> <Rprofmem [3 × 3]> <bench_tm [136]> <tibble>
#4 stringr_facts  90.71ms  91.29ms     10.9    11.44MB        0     6     0      549ms <lgl [1,000,000]> <Rprofmem [3 × 3]> <bench_tm [6]>   <tibble>

看起来stringrfactor项没有任何不同,但Base R正在显着优化它.这是预期行为吗?我应该将其报告为stringr问题吗?我是否完全缺少stringr个设置?我不想考虑数据的格式来确定我是使用stringr还是以R为基础.

推荐答案

搜索唯一值比搜索所有元素更快

正如MrFlick在 comments 中指出的那样,这种情况的发生是因为因子是grepl()中的一种特例,它匹配水平,而不是整个载体.您有一个包含一百万个元素但只有三个唯一值的载体,所以这会减少很多工作量.

我认为stringr没有理由告诉它你正在处理具有许多重复值的数据.但是,您可以编写一个将grepl()逻辑应用于stringr的因子的函数:

str_detect_factor <- function(fct, pattern, negate = FALSE) {
    out <- stringr::str_detect(
        c(levels(fct), NA_character_), pattern, negate
    )
    outna <- out[length(out)]
    out <- out[fct]
    out[is.na(fct)] <- outna
    out
}

这比grepl()稍微快一点:

bench::mark(
    base_facts = grepl("Two", sample_facts),
    stringr_facts = str_detect(sample_facts, "Two"),
    stringr_facts2 = str_detect_factor(sample_facts, "Two")
)

#   expression          min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
#   <bch:expr>     <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
# 1 base_facts       3.27ms   4.15ms    220.      11.4MB    112.     49    25      222ms
# 2 stringr_facts   182.1ms 182.44ms      5.44    11.4MB      0       3     0      552ms
# 3 stringr_facts2   3.33ms   3.75ms    252.      11.4MB     96.6    73    28      290ms

将此优化应用于字符载体

您的字符载体也有许多重复的值,因此您可以应用相同的逻辑.这不会像因子那么快,因为不同的值已经存储在对象中,所以您不必调用unique().但是,它应该比现有方法更快:

str_detect_rep <- function(string, pattern, negate = FALSE) {
    unique_string <- c(unique(string), NA_character_)
    out <- stringr::str_detect(
        unique_string, pattern, negate
    ) |> setNames(unique_string)
    outna <- out[length(out)]
    out <- out[string]
    out[is.na(string)] <- outna
    unname(out)
}

我们可以看到它比基本R或stringr版本快得多:

bench::mark(
    base_chars = grepl("Two", sample_chars),
    stringr_chars = str_detect(sample_chars, "Two"),
    stringr_chars2 = str_detect_rep(sample_chars, "Two")
)

# A tibble: 3 × 9
#   expression          min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
#   <bch:expr>     <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
# 1 base_chars      131.2ms  173.1ms      5.80    3.81MB     0        4     0    689.6ms
# 2 stringr_chars     213ms  217.5ms      4.60    3.81MB     2.30     2     1    434.9ms
# 3 stringr_chars2   37.3ms   37.3ms     26.8    42.33MB   295.       1    11     37.3ms

创建常规

由于您的问题是您不想考虑是否正在处理字符数据或因子数据,因此您可以创建一个通用:

str_detect2 <- function(string, ...) UseMethod("str_detect2")
str_detect2.default <- function(string, ...) str_detect_rep(string, ...)
str_detect2.factor <- function(string, ...) str_detect_factor(string, ...)

然后进行基准测试:

bench::mark(
    base_facts = grepl("Two", sample_facts),
    stringr_facts = str_detect(sample_facts, "Two"),
    str_detect2_facts = str_detect2(sample_facts, "Two"),
    base_chars = grepl("Two", sample_chars),
    stringr_chars = str_detect(sample_chars, "Two"),
    str_detect2_chars = str_detect2(sample_chars, "Two")
)

#   expression             min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
#   <bch:expr>        <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
# 1 base_facts          3.77ms    6.3ms    154.     11.44MB     40.3    42    11      273ms
# 2 stringr_facts     197.82ms  207.1ms      4.65   11.44MB      0       3     0      645ms
# 3 str_detect2_facts   3.55ms    4.8ms    190.     11.44MB     43.5    70    16      368ms
# 4 base_chars        121.17ms  125.8ms      7.47    3.81MB      0       4     0      536ms
# 5 stringr_chars     215.54ms  232.9ms      4.36    3.81MB      0       3     0      689ms
# 6 str_detect2_chars   27.5ms   29.2ms     31.2    42.33MB     85.8     4    11      128ms

现在您只需拨打str_detect2()而不必考虑它-字符和因子数据这里速度更快:

enter image description here

R相关问答推荐

try 在Powershell中运行R(编程语言)会重复最后一个命令

将coord_sf与geom_spatraster一起使用会更改分辨率

Facet_wrap具有不同bin宽度值的图表

保存shiny 的代码嗅探器:避免$ Symbol问题

有没有方法将paste 0功能与列表结合起来?

如何将具有重复名称的收件箱合并到R中的另一列中,而结果不同?

根据收件箱中的特定值提取列名

更改绘图上的x轴断点,而不影响风险?

获取一个数据库框架的摘要,该数据库框架将包含一列数据库框架,

derrr mutate case_when grepl不能在R中正确返回值

gganimate在使用shadow_mark选项时不保留所有过go 的标记

如果可能,将数字列转换为整数,否则保留为数字

plotly hover文本/工具提示在shiny 中不起作用

使用带有OR条件的grepl过滤字符串

用R ggplot2求上、下三角形中两个变量的矩阵热图

我如何使用tidyselect来传递一个符号数组,比如Pivot_Long?

按组和连续id计算日期差

如何在R中创建条形图,使条形图在y轴上围绕0.5而不是0构建条形图?

有毒元素与表观遗传年龄的回归模型

如何使用grepl()在数据帧列表中 Select 特定字符串?