R 基准和处理时间结果的差异

R 基准和处理时间结果的差异,r,dplyr,R,Dplyr,我一直在尝试一些最有效的方法来替换数据帧中的NA 我首先比较了100万行12列数据集上NA和0的替换解决方案。 将所有具有管道功能的组件放入microbenchmark我得到了以下结果 问题1:是否有办法测试子集左赋值语句(例如:df1[Is.na(df1)]Book1 system.time({ +Book1%>%mutate_all(funs(ifelse(is.na(.),0,))}) 用户系统运行时间 52.79 24.66 77.45 > >系统时间({ +Book1%>%

我一直在尝试一些最有效的方法来替换数据帧中的NA

我首先比较了100万行12列数据集上NA和0的替换解决方案。 将所有具有管道功能的组件放入
microbenchmark
我得到了以下结果

问题1:是否有办法测试子集左赋值语句(例如:df1[Is.na(df1)]Book1 system.time({ +Book1%>%mutate_all(funs(ifelse(is.na(.),0,))}) 用户系统运行时间 52.79 24.66 77.45 > >系统时间({ +Book1%>%mutate_at(funs(ifelse(is.na(.),0,),.cols=c(1:12))) 用户系统运行时间 52.74 25.16 77.91 > >系统时间({ +第1册[is.na(第1册)] >系统时间({ +Book1%>%replace_na(列表(var1=0,var2=0,var3=0,var4=0,var5=0,var6=0,var7=0,var8=0,var9=0,var10=0,var11=0,var12=0))) 用户系统运行时间 3.54 2.13 5.68 > >系统时间({ +Book1%>%mutate_at(funs(replace(,is.na(.,0)),.cols=c(1:12))) 用户系统运行时间 3.37 2.26 5.63 > >系统时间({ +Book1%>%mutate_all(funs(replace(,is.na(,,0))) 用户系统运行时间 3.33 2.26 5.58 > >系统时间({ +Book1%>%替换(,is.na(,,0)}) 用户系统运行时间 3.42 1.09 4.51 在这些测试中,基本的
replace()
首先出现。 在基准测试中,
replace
在排名中进一步落后,而tidyr
replace\u na()
获胜(以微弱优势) 在不同形状和大小的数据帧上反复运行单一测试总是发现base
replace()
处于领先地位

问题2:它的基准性能怎么可能是唯一与简单测试结果不符的结果

更令人困惑的是- 问题3:所有的
变种(replace())
怎么能比简单的
替换()更快呢?
许多人报告:(以及文章中的所有链接)但我仍然没有找到为什么除了哈希和C++之外的解释。
特别感谢Tyler Rinker:

和akrun:

您可以在
microbenchmark
中包含一个复杂/多语句,方法是使用
{}
将其包装,基本上可以将其转换为单个表达式:

microbenchmark(expr1 = { df1[is.na(df1)] = 0 }, 
               exp2 = { tmp = 1:10; tmp[3] = 0L; tmp2 = tmp + 12L; tmp2 ^ 2 }, 
               times = 10)
#Unit: microseconds
#  expr        min         lq       mean     median         uq        max neval cld
# expr1 124953.716 137244.114 158576.030 142405.685 156744.076 284779.353    10   b
#  exp2      2.784      3.132     17.748     23.142     24.012     38.976    10  a 
值得注意的是,这种做法的副作用:

tmp
#[1]  1  2  0  4  5  6  7  8  9 10
与之相反,比如说:

rm(tmp)
microbenchmark(expr1 = { df1[is.na(df1)] = 0 },  
               exp2 = local({ tmp = 1:10; tmp[3] = 0L; tmp2 = tmp + 12L; tmp2 ^ 2 }), 
               times = 10)
#Unit: microseconds
#  expr       min         lq        mean     median         uq        max neval cld
# expr1 127250.18 132935.149 165296.3030 154509.553 169917.705 314820.306    10   b
#  exp2     10.44     12.181     42.5956     54.636     57.072     97.789    10  a 
tmp
#Error: object 'tmp' not found
注意到基准测试的副作用,我们看到删除
NA
值的第一个操作为以下备选方案留下了相当轻松的工作:

# re-assign because we changed it before
set.seed(24)
df1 = as.data.frame(matrix(sample(c(NA, 1:5), 1e6 * 12, TRUE), 
                           dimnames = list(NULL, paste0("var", 1:12)), ncol = 12))
unique(sapply(df1, typeof))
#[1] "integer"
any(sapply(df1, anyNA))
#[1] TRUE
system.time({ df1[is.na(df1)] <- 0 })
# user  system elapsed 
# 0.39    0.14    0.53 
如果没有,则应考虑替换
NA
,对输入不做任何操作

除此之外,请注意,在所有备选方案中,您将“double”(
typeof(0)
)分配给“integer”列向量(
sappy(df1,typeof)
)。然而,我认为(在上述备选方案中,
df1
在创建“data.frame”之后)不会被修改,存在存储的信息以在修改时复制其向量列),在强制为“double”并存储为“double”时,仍然存在较小但可避免的开销。在替换“integer”向量中的元素之前,R将分配和复制(在“integer”替换的情况下)或分配和强制(在“double”替换的情况下)。此外,在第一次强制之后(如上所述,由于基准的副作用),R将在“double”s上运行,并且包含比“integer”s更慢的操作。我找不到一种简单的R方法来调查这种差异,但简而言之(可能不完全准确),我们可以通过以下方式模拟这些操作:

# simulate R's copying of int to int
# allocate a new int and copy
int2int = inline::cfunction(sig = c(x = "integer"), body = '
    SEXP ans = PROTECT(allocVector(INTSXP, LENGTH(x)));
    memcpy(INTEGER(ans), INTEGER(x), LENGTH(x) * sizeof(int));
    UNPROTECT(1);
    return(ans);
')
# R's coercing of int to double
# 'coerceVector', internally, allocates a double and coerces to populate it
int2dbl = inline::cfunction(sig = c(x = "integer"), body = '
    SEXP ans = PROTECT(coerceVector(x, REALSXP));
    UNPROTECT(1);
    return(ans);
')
# simulate R's copying form double to double
dbl2dbl = inline::cfunction(sig = c(x = "double"), body = '
    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));
    memcpy(REAL(ans), REAL(x), LENGTH(x) * sizeof(double));
    UNPROTECT(1);
    return(ans);
')
在基准上:

x.int = 1:1e7; x.dbl = as.numeric(x.int)
microbenchmark(int2int(x.int), int2dbl(x.int), dbl2dbl(x.dbl), times = 50)
#Unit: milliseconds
#           expr      min       lq     mean   median       uq      max neval cld
# int2int(x.int) 16.42710 16.91048 21.93023 17.42709 19.38547 54.36562    50  a 
# int2dbl(x.int) 35.94064 36.61367 47.15685 37.40329 63.61169 78.70038    50   b
# dbl2dbl(x.dbl) 33.51193 34.18427 45.30098 35.33685 63.45788 75.46987    50   b
总结(!)前面的整个注释,将
0
替换为
0L
将节省一些时间

最后,为了以更公平的方式复制基准,我们可以使用:

library(dplyr)
library(tidyr)
library(microbenchmark) 
set.seed(24)
df1 = as.data.frame(matrix(sample(c(NA, 1:5), 1e6 * 12, TRUE), 
                            dimnames = list(NULL, paste0("var", 1:12)), ncol = 12))
包装函数:

stopifnot(ncol(df1) == 12)  #some of the alternatives are hardcoded to 12 columns
mut_all_ifelse = function(x, val) x %>% mutate_all(funs(ifelse(is.na(.), val, .)))
mut_at_ifelse = function(x, val) x %>% mutate_at(funs(ifelse(is.na(.), val, .)), .cols = c(1:12))
baseAssign = function(x, val) { x[is.na(x)] <- val; x }
baseFor = function(x, val) { for(j in 1:ncol(x)) x[[j]][is.na(x[[j]])] = val; x }
base_replace = function(x, val) x %>% replace(., is.na(.), val)
mut_all_replace = function(x, val) x %>% mutate_all(funs(replace(., is.na(.), val)))
mut_at_replace = function(x, val) x %>% mutate_at(funs(replace(., is.na(.), val)), .cols = c(1:12))
myreplace_na = function(x, val) x %>% replace_na(list(var1 = val, var2 = val, var3 = val, var4 = val, var5 = val, var6 = val, var7 = val, var8 = val, var9 = val, var10 = val, var11 = val, var12 = val))
强制进行“双重”测试:

不强制进行“双重”测试:

还有一种简单的可视化方法:

boxplot(benchnum, ylim = range(min(summary(benchint)$min, summary(benchnum)$min),
                               max(summary(benchint)$max, summary(benchnum)$max)))
boxplot(benchint, add = TRUE, border = "red", axes = FALSE) 
legend("topright", c("coerce", "not coerce"), fill = c("black", "red"))                       


请注意,
df1
在所有这些之后是不变的(
str(df1)
)。

尝试用
{}
--
{df1[is.na(df1)]@alexis_laz:的确如此!这回答了大多数问题,帮助我开始了解可变项在R中的工作方式。您是否愿意将其放入一个答案中,以便我选择它?此外,您是否可以添加一个解释,说明为什么使用(甚至不使用)for循环简化子集的工作速度比其他选项快得多?我添加了一个更为扩展的答案。
for
循环是最快的选择之一,因为它所做的是替换向量中的一个值所需的最小值。
for
循环中发生的所有子集都只与原语相关函数,而不是
[
[谢谢!这是一组非常有趣的介绍,介绍了局部和函数包装,尽可能分解为整数的明显好处,以及更简单的循环如何真正提高速度。内容丰富,编写精良!欢迎您,很高兴您发现它很有用。顺便说一句,请注意实际上,“循环”只包含“矢量化”操作。“data.frame”是一个向量列表(字面上和作为R对象)。上面的循环本质上等同于
df1[[1]][is.na(df1[[1]]]]]=0L;df1[[2]][is.na(df1[[2]]]]=0L;等。
但在
for
循环中被包装为方便且合理的代码。无论是用户代码、R函数还是内部,都必须在“data.frame”列向量中进行迭代选择。仅供参考,您的学生(OP)试图在您在此所做的基础上进行构建,但似乎仍然感到困惑
x.int = 1:1e7; x.dbl = as.numeric(x.int)
microbenchmark(int2int(x.int), int2dbl(x.int), dbl2dbl(x.dbl), times = 50)
#Unit: milliseconds
#           expr      min       lq     mean   median       uq      max neval cld
# int2int(x.int) 16.42710 16.91048 21.93023 17.42709 19.38547 54.36562    50  a 
# int2dbl(x.int) 35.94064 36.61367 47.15685 37.40329 63.61169 78.70038    50   b
# dbl2dbl(x.dbl) 33.51193 34.18427 45.30098 35.33685 63.45788 75.46987    50   b
library(dplyr)
library(tidyr)
library(microbenchmark) 
set.seed(24)
df1 = as.data.frame(matrix(sample(c(NA, 1:5), 1e6 * 12, TRUE), 
                            dimnames = list(NULL, paste0("var", 1:12)), ncol = 12))
stopifnot(ncol(df1) == 12)  #some of the alternatives are hardcoded to 12 columns
mut_all_ifelse = function(x, val) x %>% mutate_all(funs(ifelse(is.na(.), val, .)))
mut_at_ifelse = function(x, val) x %>% mutate_at(funs(ifelse(is.na(.), val, .)), .cols = c(1:12))
baseAssign = function(x, val) { x[is.na(x)] <- val; x }
baseFor = function(x, val) { for(j in 1:ncol(x)) x[[j]][is.na(x[[j]])] = val; x }
base_replace = function(x, val) x %>% replace(., is.na(.), val)
mut_all_replace = function(x, val) x %>% mutate_all(funs(replace(., is.na(.), val)))
mut_at_replace = function(x, val) x %>% mutate_at(funs(replace(., is.na(.), val)), .cols = c(1:12))
myreplace_na = function(x, val) x %>% replace_na(list(var1 = val, var2 = val, var3 = val, var4 = val, var5 = val, var6 = val, var7 = val, var8 = val, var9 = val, var10 = val, var11 = val, var12 = val))
identical(mut_all_ifelse(df1, 0), mut_at_ifelse(df1, 0))
#[1] TRUE
identical(mut_at_ifelse(df1, 0), baseAssign(df1, 0))
#[1] TRUE
identical(baseAssign(df1, 0), baseFor(df1, 0))
#[1] TRUE
identical(baseFor(df1, 0), base_replace(df1, 0))
#[1] TRUE
identical(base_replace(df1, 0), mut_all_replace(df1, 0))
#[1] TRUE
identical(mut_all_replace(df1, 0), mut_at_replace(df1, 0))
#[1] TRUE
identical(mut_at_replace(df1, 0), myreplace_na(df1, 0))
#[1] TRUE
benchnum = microbenchmark(mut_all_ifelse(df1, 0), 
                          mut_at_ifelse(df1, 0), 
                          baseAssign(df1, 0), 
                          baseFor(df1, 0),
                          base_replace(df1, 0), 
                          mut_all_replace(df1, 0),
                          mut_at_replace(df1, 0), 
                          myreplace_na(df1, 0),
                          times = 10)
benchnum
#Unit: milliseconds
#                    expr       min        lq      mean    median        uq       max neval cld
#  mut_all_ifelse(df1, 0) 1368.5091 1441.9939 1497.5236 1509.2233 1550.1416 1629.6959    10   c
#   mut_at_ifelse(df1, 0) 1366.1674 1389.2256 1458.1723 1464.5962 1503.4337 1553.7110    10   c
#      baseAssign(df1, 0)  532.4975  548.9444  586.8198  564.3940  655.8083  667.8634    10  b 
#         baseFor(df1, 0)  169.6048  175.9395  206.7038  189.5428  197.6472  308.6965    10 a  
#    base_replace(df1, 0)  518.7733  547.8381  597.8842  601.1544  643.4970  666.6872    10  b 
# mut_all_replace(df1, 0)  169.1970  183.5514  227.1978  194.0903  291.6625  346.4649    10 a  
#  mut_at_replace(df1, 0)  176.7904  186.4471  227.3599  202.9000  303.4643  309.2279    10 a  
#    myreplace_na(df1, 0)  172.4926  177.8518  199.1469  186.3645  192.1728  297.0419    10 a
benchint = microbenchmark(mut_all_ifelse(df1, 0L), 
                          mut_at_ifelse(df1, 0L), 
                          baseAssign(df1, 0L), 
                          baseFor(df1, 0L),
                          base_replace(df1, 0L), 
                          mut_all_replace(df1, 0L),
                          mut_at_replace(df1, 0L),
                          myreplace_na(df1, 0L),
                          times = 10)
benchint
#Unit: milliseconds
#                     expr        min        lq      mean    median        uq       max neval cld
#  mut_all_ifelse(df1, 0L) 1291.17494 1313.1910 1377.9265 1353.2812 1417.4389 1554.6110    10   c
#   mut_at_ifelse(df1, 0L) 1295.34053 1315.0308 1372.0728 1353.0445 1431.3687 1478.8613    10   c
#      baseAssign(df1, 0L)  451.13038  461.9731  477.3161  471.0833  484.9318  528.4976    10  b 
#         baseFor(df1, 0L)   98.15092  102.4996  115.7392  107.9778  136.2227  139.7473    10 a  
#    base_replace(df1, 0L)  428.54747  451.3924  471.5011  470.0568  497.7088  516.1852    10  b 
# mut_all_replace(df1, 0L)  101.66505  102.2316  137.8128  130.5731  161.2096  243.7495    10 a  
#  mut_at_replace(df1, 0L)  103.79796  107.2533  119.1180  112.1164  127.7959  166.9113    10 a  
#    myreplace_na(df1, 0L)  100.03431  101.6999  120.4402  121.5248  137.1710  141.3913    10 a
boxplot(benchnum, ylim = range(min(summary(benchint)$min, summary(benchnum)$min),
                               max(summary(benchint)$max, summary(benchnum)$max)))
boxplot(benchint, add = TRUE, border = "red", axes = FALSE) 
legend("topright", c("coerce", "not coerce"), fill = c("black", "red"))