Ускорить вычисление Строковой медианы каждого 3-кортежа столбцов

если у меня есть фрейм данных, как например:

df = data.frame(matrix(rnorm(100), 5000, 100))

Я могу использовать следующую функцию, чтобы получить каждую комбинацию трехчленных медианов по строке:

median_df = t(apply(df, 1, combn, 3, median))

проблема в том, что эта функция займет несколько часов. Виновником является median (), который занимает примерно в десять раз больше времени, чем max () или min ().

как я могу ускорить эту функцию, возможно, написав более быструю версию median() или работая с исходными данными по-другому?

обновление:

если я запускаю приведенный выше код, но только для df [, 1: 10] как таковой:

median_df = t(apply(df[,1:10], 1, combn, 3, median))

занимает 29 секунд

fastMedian_df = t(apply(df[,1:10], 1, combn, 3, fastMedian))

из пакета ccaPP занимает 6,5 секунды

max_df = t(apply(df[,1:10], 1, combn, 3, max))

занимает 2,5 секунды

таким образом, мы видим значительное улучшение с fastMedian(). Мы все еще можем сделать лучше?

1 ответов


одним из подходов к ускорению было бы отметить, что медиана трех чисел-это их сумма минус их Макс минус их мин. Это означает, что мы можем векторизовать наши вычисления медианы, обрабатывая каждую тройку столбцов один раз (выполняя медиану для всех строк в том же вычислении) вместо обработки ее один раз для каждой строки.

set.seed(144)
# Fully random matrix
df = matrix(rnorm(50000), 5000, 10)
original <- function(df) t(apply(df, 1, combn, 3, median))
josilber <- function(df) {
  combos <- combn(seq_len(ncol(df)), 3)
  apply(combos, 2, function(x) rowSums(df[,x]) - pmin(df[,x[1]], df[,x[2]], df[,x[3]]) - pmax(df[,x[1]], df[,x[2]], df[,x[3]]))
}
system.time(res.josilber <- josilber(df))
#    user  system elapsed 
#   0.117   0.009   0.149 
system.time(res.original <- original(df))
#    user  system elapsed 
#  15.107   1.864  16.960 
all.equal(res.josilber, res.original)
# [1] TRUE

векторизация дает ускорение 110x, когда есть 10 столбцов и 5000 строк. К сожалению, у меня нет машины с достаточно память для хранения 808.5 миллионов чисел в выходных данных для вашего полного примера.

вы можете ускорить это, реализовав функцию Rcpp, которая принимает в качестве входного векторное представление Матрицы (он же вектор, полученный при чтении матрицы вниз по столбцам) вместе с количеством строк и возвращает медиану каждого столбца. Функция сильно зависит от std::nth_element функция, которая асимптотически линейна по числу элементов, из которых вы берете медиану. (Отмечать что я не усредняю средние два значения, когда я беру медиану вектора четной длины; я вместо этого беру нижний из двух).

library(Rcpp)
cppFunction(
"NumericVector vectorizedMedian(NumericVector x, int chunkSize) {
 const int n = x.size() / chunkSize;
 std::vector<double> input = Rcpp::as<std::vector<double> >(x);
  NumericVector res(n);
  for (int i=0; i < n; ++i) {
    std::nth_element(input.begin()+i*chunkSize, input.begin()+i*chunkSize+chunkSize/2,
                     input.begin()+(i+1)*chunkSize);
    res[i] = input[i*chunkSize+chunkSize/2];
  }
  return res;
}")

теперь мы просто вызываем эту функцию вместо использования rowSums, pmin и pmax:

josilber.rcpp <- function(df) {
  combos <- combn(seq_len(ncol(df)), 3)
  apply(combos, 2, function(x) vectorizedMedian(as.vector(t(df[,x])), 3))
}
system.time(josilber.rcpp(df))
#    user  system elapsed 
#   0.049   0.008   0.081 
all.equal(josilber(df), josilber.rcpp(df))
# [1] TRUE

в общем, таким образом мы получаем 210x ускорение; 110x ускорение от перехода от не-векторизация применения median для приложения векторизован и оставшихся 2х ускорение-это переход от сочетания rowSums, pmin и pmax для вычисления медианы векторизованным способом к подходу на основе Rcpp.