dplyr 杂交选项现在比重新分配的 Base R 子集快约 30%。在 100M 数据点上,数据帧 mutate_all(~replace(., is.na(.), 0)) 的运行速度比基本 R d[is.na(d)] <- 0 选项快半秒。想要特别避免的是使用ifelse() 或if_else()。 (完整的 600 次试验分析运行超过 4.5 小时,主要是因为包含了这些方法。)请参阅下面的基准分析以了解完整的结果。
如果您在处理大量数据帧,data.table 是最快的选择:比标准 Base R 方法快 40%。它还可以就地修改数据,有效地让您一次处理几乎两倍的数据。
其他有用的 tidyverse 替换方法的聚类
地理位置:
-
索引
mutate_at(c(5:10), ~replace(., is.na(.), 0))
-
直接参考
mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
-
固定匹配
mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
- 或代替
contains(),试试ends_with(),starts_with()
-
模式匹配
mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))
有条件地:
(只改变一种类型,不理会其他类型。)
-
整数
mutate_if(is.integer, ~replace(., is.na(.), 0))
-
数字
mutate_if(is.numeric, ~replace(., is.na(.), 0))
-
字符串
mutate_if(is.character, ~replace(., is.na(.), 0))
完整分析 -
为 dplyr 0.8.0 更新:函数使用 purrr 格式 ~ 符号:替换已弃用的 funs() 参数。
测试的方法:
# Base R:
baseR.sbst.rssgn <- function(x) { x[is.na(x)] <- 0; x }
baseR.replace <- function(x) { replace(x, is.na(x), 0) }
baseR.for <- function(x) { for(j in 1:ncol(x))
x[[j]][is.na(x[[j]])] = 0 }
# tidyverse
## dplyr
dplyr_if_else <- function(x) { mutate_all(x, ~if_else(is.na(.), 0, .)) }
dplyr_coalesce <- function(x) { mutate_all(x, ~coalesce(., 0)) }
## tidyr
tidyr_replace_na <- function(x) { replace_na(x, as.list(setNames(rep(0, 10), as.list(c(paste0("var", 1:10)))))) }
## hybrid
hybrd.ifelse <- function(x) { mutate_all(x, ~ifelse(is.na(.), 0, .)) }
hybrd.replace_na <- function(x) { mutate_all(x, ~replace_na(., 0)) }
hybrd.replace <- function(x) { mutate_all(x, ~replace(., is.na(.), 0)) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), ~replace(., is.na(.), 0)) }
hybrd.rplc_if <- function(x) { mutate_if(x, is.numeric, ~replace(., is.na(.), 0)) }
# data.table
library(data.table)
DT.for.set.nms <- function(x) { for (j in names(x))
set(x,which(is.na(x[[j]])),j,0) }
DT.for.set.sqln <- function(x) { for (j in seq_len(ncol(x)))
set(x,which(is.na(x[[j]])),j,0) }
DT.nafill <- function(x) { nafill(df, fill=0)}
DT.setnafill <- function(x) { setnafill(df, fill=0)}
本次分析的代码:
library(microbenchmark)
# 20% NA filled dataframe of 10 Million rows and 10 columns
set.seed(42) # to recreate the exact dataframe
dfN <- as.data.frame(matrix(sample(c(NA, as.numeric(1:4)), 1e7*10, replace = TRUE),
dimnames = list(NULL, paste0("var", 1:10)),
ncol = 10))
# Running 600 trials with each replacement method
# (the functions are excecuted locally - so that the original dataframe remains unmodified in all cases)
perf_results <- microbenchmark(
hybrid.ifelse = hybrid.ifelse(copy(dfN)),
dplyr_if_else = dplyr_if_else(copy(dfN)),
hybrd.replace_na = hybrd.replace_na(copy(dfN)),
baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
baseR.replace = baseR.replace(copy(dfN)),
dplyr_coalesce = dplyr_coalesce(copy(dfN)),
tidyr_replace_na = tidyr_replace_na(copy(dfN)),
hybrd.replace = hybrd.replace(copy(dfN)),
hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
baseR.for = baseR.for(copy(dfN)),
hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
DT.for.set.nms = DT.for.set.nms(copy(dfN)),
DT.for.set.sqln = DT.for.set.sqln(copy(dfN)),
times = 600L
)
结果总结
> print(perf_results)
Unit: milliseconds
expr min lq mean median uq max neval
hybrd.ifelse 6171.0439 6339.7046 6425.221 6407.397 6496.992 7052.851 600
dplyr_if_else 3737.4954 3877.0983 3953.857 3946.024 4023.301 4539.428 600
hybrd.replace_na 1497.8653 1706.1119 1748.464 1745.282 1789.804 2127.166 600
baseR.sbst.rssgn 1480.5098 1686.1581 1730.006 1728.477 1772.951 2010.215 600
baseR.replace 1457.4016 1681.5583 1725.481 1722.069 1766.916 2089.627 600
dplyr_coalesce 1227.6150 1483.3520 1524.245 1519.454 1561.488 1996.859 600
tidyr_replace_na 1248.3292 1473.1707 1521.889 1520.108 1570.382 1995.768 600
hybrd.replace 913.1865 1197.3133 1233.336 1238.747 1276.141 1438.646 600
hybrd.rplc_at.ctn 916.9339 1192.9885 1224.733 1227.628 1268.644 1466.085 600
hybrd.rplc_at.nse 919.0270 1191.0541 1228.749 1228.635 1275.103 2882.040 600
baseR.for 869.3169 1180.8311 1216.958 1224.407 1264.737 1459.726 600
hybrd.rplc_at.idx 839.8915 1189.7465 1223.326 1228.329 1266.375 1565.794 600
DT.for.set.nms 761.6086 915.8166 1015.457 1001.772 1106.315 1363.044 600
DT.for.set.sqln 787.3535 918.8733 1017.812 1002.042 1122.474 1321.860 600
结果箱线图
ggplot(perf_results, aes(x=expr, y=time/10^9)) +
geom_boxplot() +
xlab('Expression') +
ylab('Elapsed Time (Seconds)') +
scale_y_continuous(breaks = seq(0,7,1)) +
coord_flip()
颜色编码的试验散点图(y 轴在对数刻度上)
qplot(y=time/10^9, data=perf_results, colour=expr) +
labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
coord_cartesian(ylim = c(0.75, 7.5)) +
scale_y_log10(breaks=c(0.75, 0.875, 1, 1.25, 1.5, 1.75, seq(2, 7.5)))
关于其他高绩效者的说明
当数据集变大时,Tidyr 的 replace_na 历来都排在前面。使用当前要运行的 100M 数据点集合,它的性能几乎与 Base R For 循环一样好。我很想知道不同大小的数据框会发生什么。
mutate 和 summarize _at 和 _all 函数变体的其他示例可在此处找到:https://rdrr.io/cran/dplyr/man/summarise_all.html
此外,我在这里找到了有用的演示和示例集合:https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a
归因和感谢
特别感谢:
-
Tyler Rinker 和 Akrun 用于演示微基准测试。
-
alexis_laz 帮助我理解
local() 的使用,以及(在 Frank 的耐心帮助下)无声强制在加速其中许多方法中所起的作用。
- ArthurYip 为您添加新的
coalesce() 函数并更新分析。
- Gregor 推动了
data.table 函数的计算,最终将它们包含在阵容中。
-
Base R For 循环:alexis_laz
-
data.table For 循环:Matt_Dowle
- Roman 解释了
is.numeric() 真正测试的内容。
(当然,如果你觉得这些方法有用,也请过来给他们点赞。)
关于我使用 Numerics 的注意事项:如果您确实有一个纯整数数据集,那么您的所有函数都会运行得更快。请参阅alexiz_laz's work 了解更多信息。 IRL,我不记得遇到过包含超过 10-15% 整数的数据集,所以我在全数字数据帧上运行这些测试。
使用的硬件
3.9 GHz CPU 和 24 GB RAM