В вашей функции Mode
, поскольку вы в основном вызываете функции-оболочки, вы не увидите такого большого улучшения по сравнению с базовой R
. На самом деле, просто написав верный базовый перевод R, мы имеем:
baseMode <- function(x, narm = FALSE) {
if (narm) x <- x[!is.na(x)]
ux <- unique(x)
ux[which.max(table(match(x, ux)))]
}
И бенчмаркинг, у нас есть:
set.seed(1234)
s <- sample(1e5, replace = TRUE)
library(microbenchmark)
microbenchmark(Mode(s), baseMode(s), times = 10, unit = "relative")
Unit: relative
expr min lq mean median uq max neval
Mode(s) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 10
baseMode(s) 1.490765 1.645367 1.571132 1.616061 1.637181 1.448306 10
Как правило, когда мы предпринимаем усилия для написания собственного скомпилированного кода, мы ожидаем большего. Простое завершение этих и без того эффективных скомпилированных функций в Rcpp
не сможет волшебным образом принести вам ожидаемые результаты. На самом деле, на больших примерах базовое решение быстрее. Обратите внимание:
set.seed(1234)
sBig <- sample(1e6, replace = TRUE)
system.time(Mode(sBig))
user system elapsed
1.410 0.036 1.450
system.time(baseMode(sBig))
user system elapsed
0.915 0.025 0.943
Чтобы ответить на ваш вопрос о написании функции более быстрого режима, мы можем использовать std::unordered_map
, что очень похоже на table
под капотом (т.е. они оба являются хеш-таблицами в своей основе). Кроме того, поскольку вы возвращаете одно целое число, мы можем с уверенностью предположить, что мы можем заменить NumericVector
на IntegerVector
, а также что вам не нужно возвращать каждое значение , которое встречается чаще всего.
Алгоритм, приведенный ниже, можно изменить, чтобы он возвращал истинный режим , но я оставлю это в качестве упражнения (подсказка: вам понадобится std::vector
вместе с выполнением какого-либо действия, когда it->second == myMax
) , Нотабене вам также нужно будет добавить // [[Rcpp::plugins(cpp11)]]
вверху вашего файла cpp для std::unordered_map
и auto
.
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::plugins(cpp11)]]
#include <unordered_map>
// [[Rcpp::export]]
int fastIntMode(IntegerVector x, bool narm = false) {
if (narm) x = x[!is_na(x)];
int myMax = 1;
int myMode = 0;
std::unordered_map<int, int> modeMap;
modeMap.reserve(x.size());
for (std::size_t i = 0, len = x.size(); i < len; ++i) {
auto it = modeMap.find(x[i]);
if (it != modeMap.end()) {
++(it->second);
if (it->second > myMax) {
myMax = it->second;
myMode = x[i];
}
} else {
modeMap.insert({x[i], 1});
}
}
return myMode;
}
И отметки:
microbenchmark(Mode(s), baseMode(s), fastIntMode(s), times = 15, unit = "relative")
Unit: relative
expr min lq mean median uq max neval
Mode(s) 6.428343 6.268131 6.622914 6.134388 6.881746 7.78522 15
baseMode(s) 9.757491 9.404101 9.454857 9.169315 9.018938 10.16640 15
fastIntMode(s) 1.000000 1.000000 1.000000 1.000000 1.000000 1.00000 15
Теперь мы говорим ... примерно в 6 раз быстрее, чем оригинал, и в 9 раз быстрее, чем база. Все они возвращают одно и то же значение:
fastIntMode(s)
##[1] 85433
baseMode(s)
##[1] 85433
Mode(s)
##[1] 85433
А для нашего более крупного примера:
## base R returned in 0.943s
system.time(fastIntMode(s))
user system elapsed
0.217 0.006 0.224