使用mutate创建新变量时,Dplyr代码比预期的慢

使用mutate创建新变量时,Dplyr代码比预期的慢,r,dplyr,R,Dplyr,我正在使用dplyr在数据帧上创建三个新变量。数据帧为84253 obs。共有164个变量。下面是我的代码 # ptm <- proc.time() D04_Base2 <- D04_Base %>% mutate( birthyr = year(as.Date(BIRTHDT,"%m/%d/%Y")), age = (snapshotDt - as.Date(BIRTHDT,"%m/%d/%Y")) / 365.25,

我正在使用
dplyr
在数据帧上创建三个新变量。数据帧为84253 obs。共有164个变量。下面是我的代码

# ptm <- proc.time()
 D04_Base2 <- D04_Base %>% 
    mutate(
        birthyr = year(as.Date(BIRTHDT,"%m/%d/%Y")),
        age = (snapshotDt - as.Date(BIRTHDT,"%m/%d/%Y")) / 365.25,
        age = ifelse(age > 100, NA, age)
        )
# proc.time() - ptm
user  system elapsed 
12.34    0.03   12.42 
#ptm 100,NA,年龄)
)
#进程时间()-ptm
用户系统运行时间
12.34    0.03   12.42 

然而,我想知道我的代码是否有一个明显的问题,因为它运行的时间比我预期的要长得多,或者这是其他问题。如上所示,完成代码大约需要12秒。

是的,您的代码中存在一些效率低下的问题:

  • BIRTHDT
    列转换为
    Date
    两次。(这是迄今为止最大的问题。)
  • base::as.Date
    不是很快
  • 您可以使用
    dplyr::if_else
    而不是
    base::ifelse
    来获得一点性能增益
  • 让我们做一些测试:

    library(microbenchmark)
    library(dplyr)
    library(lubridate)
    
    mbm = microbenchmark::microbenchmark
    
    # generate big-ish sample data
    n = 1e5
    dates = seq.Date(from = Sys.Date(), length.out = n, by = "day")
    raw_dates = format(dates, "%m/%d/%Y")
    df = data.frame(x = 1:n)
    
    日期转换 在这个特定的日期转换中,
    lubridate::mdy
    as.Date
    快2-3倍

    提取年份 类似地,
    lubridate::year
    (您似乎已经在使用)提取年份的速度大约是
    base::format
    的2倍

    添加列: 在这里我们看到基地做得很好。但是还要注意,这些时间是以微秒为单位的,而上面日期的时间是以毫秒为单位的。无论您是使用
    base
    还是
    dplyr
    添加列,它大约占执行日期转换所用时间的1%

    如果有 这里的计时仍然以毫秒为单位,但是
    ifelse
    dplyr::if_else
    之间的差异并没有那么大
    dplyr::if_else
    要求返回向量是相同的类型,因此我们必须指定
    NA_real\uu
    ,以便它处理数值输出。在弗兰克的建议下,我把
    base::replace
    也改为
    NA_real\uu
    ,速度大约快了10倍。我认为,这里的教训是“使用最简单的有效函数”


    总之,
    dplyr
    在添加列时比
    base
    慢,但与其他所有正在进行的操作相比,这两种操作都非常快。因此,使用哪种列添加方法并不重要。您可以通过不重复计算和使用更大操作的更快版本来加速代码。根据我们所了解的情况,更有效的代码版本是:

    library(dplyr)
    library(lubridate)
    D04_Base2 <- D04_Base %>% 
        mutate(
            birthdate = mdy(BIRTHDT),
            birthyr = year(birthdate),
            age = (snapshotDt - birthdate) / 365.25,
            age = replace(age > 100, NA_real_)
        )
    
    库(dplyr)
    图书馆(lubridate)
    D04_基准2%
    变异(
    出生日期=mdy(出生日期),
    生日年=年(生日),
    年龄=(快照-出生日期)/365.25,
    年龄=更换(年龄>100,不真实)
    )
    
    我们可以大致估计1e5行在180毫秒左右的速度增益,如下所示

    • 170毫秒(30毫秒时单次
      lubridate::mdy
      ,而不是每次100毫秒时两次
      as.Date
      调用)
    • 10毫秒(
      更换
      而不是
      ifelse

    添加一个列基准表明,不使用管道可以节省大约0.1毫秒。由于我们正在添加多个列,因此使用
    dplyr
    可能比单独使用
    $添加它们更有效。如果您只需将BIRTHDT添加到一个日期中,$将更快。因此,我测试了使用Base R创建变量,结果更快。在我做了所有的研究之后,似乎dplyr应该比Base快,这就是问题所在。您将其与之进行比较的等效Base R代码是什么?你真的应该包括一些数据,这样我们就可以自己运行它进行比较。是的,你应该从一开始就将日期存储为日期。以日期作为字符串进行任何分析都没有意义。另外,有人告诉我,
    system.time({commands})
    是一种更可靠的计时方法。添加一个列是一项非常简单的任务,一个base R在这方面做得非常好。它现在有点过时了,但它表明只要添加一个新列,
    dplyr
    就可以与
    base
    相媲美
    dplyr
    将比base做得更好,尤其是在连接和分组操作中。关于它们的ifelse,我认为
    replace(age,age>100,NA_real)
    看起来是一个更好的选择。不知道基准测试结果如何。好主意——我把它放进去了。事实证明,这并不重要:在我的100次试验中,中位数较低,但平均值较高。@Frank更好地想了想——我没有很好地阅读你的评论。现在我回去用了
    replace
    ,它比
    if_else
    快10倍。
    mbm(
        year = year(dates),
        format = format(dates, "%Y")
    )
    # Unit: milliseconds
    #    expr      min       lq     mean   median       uq      max neval cld
    #    year 29.10152 31.71873 44.84572 33.48525 40.17116 478.8377   100  a 
    #  format 77.16788 81.14211 96.42225 83.54550 88.11994 242.7808   100   b
    
    mbm(
        base_dollar = {dd = df; dd$y = 1},
        base_bracket = {dd = df; dd[["y"]] = 1},
        mutate = {dd = mutate(df, y = 1)},
        mutate_pipe = {dd = df %>% mutate(y = 1)},
        times = 100L
    )
    # Unit: microseconds
    #          expr     min       lq     mean   median       uq      max neval cld
    #   base_dollar 114.834 129.1715 372.8024 146.2275 408.4255 3315.964   100 a  
    #  base_bracket 118.585 139.6550 332.1661 156.3530 255.2860 3126.967   100 a  
    #        mutate 420.515 466.8320 673.9109 554.4960 745.7175 2821.070   100  b 
    #   mutate_pipe 522.402 600.6325 852.2037 715.1110 906.4700 3319.950   100   c
    
    x = rnorm(1e5)
    mbm(
        base_na = ifelse(x > 0, NA, x),
        base_na_real = ifelse(x > 0, NA_real_, x),
        base_replace = replace(x, x > 0, NA_real_),
        dplyr = if_else(x > 0, NA_real_, x),
        units = "ms"
    )
    # Unit: milliseconds
    #          expr      min        lq      mean    median        uq       max neval cld
    #       base_na 9.399593 13.399255 18.502441 14.734466 15.998573 138.33834   100  bc
    #  base_na_real 8.785988 12.638971 22.885304 14.075802 16.980263 132.18165   100   c
    #  base_replace 0.748265  1.136756  2.292686  1.384161  1.802833   9.05869   100 a  
    #         dplyr 5.141753  6.875031 14.157227 10.095069 11.561044 124.99218   100  b 
    
    library(dplyr)
    library(lubridate)
    D04_Base2 <- D04_Base %>% 
        mutate(
            birthdate = mdy(BIRTHDT),
            birthyr = year(birthdate),
            age = (snapshotDt - birthdate) / 365.25,
            age = replace(age > 100, NA_real_)
        )