Как сделать быстрый расчет процентилей в C++/Rcpp

у меня есть большой вектор, содержащий кучу двойных элементов. Учитывая массив вектора процентиля, например percentile_vec = c(0.90, 0.91, 0.92, 0.93, 0.94, 0.95). В настоящее время я использую Rcpp sort функция для сортировки большого вектора, а затем найти соответствующее значение процентиля. Вот основные коды:

// [[Rcpp::export]]
NumericVector sort_rcpp(Rcpp::NumericVector& x)
{
  std::vector<double> tmp = Rcpp::as<std::vector<double>> (x);    // or NumericVector tmp = clone(x);
  std::sort(tmp.begin(), tmp.end());
  return wrap(tmp);
}

// [[Rcpp::export]]
NumericVector percentile_rcpp(Rcpp::NumericVector& x, Rcpp::NumericVector& percentile)
{
  NumericVector tmp_sort = sort_rcpp(x);
  int size_per = percentile.size();
  NumericVector percentile_vec = no_init(size_per);
  for (int ii = 0; ii < size_per; ii++)
  {
    double size_per = tmp_sort.size() * percentile[ii];
    double size_per_round;
    if (size_per < 1.0)
    {
      size_per_round = 1.0;
    }
    else
    {
      size_per_round = std::round(size_per);
    }
    percentile_vec[ii] = tmp_sort[size_per_round-1];  // For extreme case such as size_per_round == tmp_sort.size() to avoid overflow
  }
  return percentile_vec;
}

Я также пытаюсь вызвать функцию R quantile(x, c(.90, .91, .92, .93, .94, .95)) в Rcpp с помощью:

sub_percentile <- function (x)
{
  return (quantile(x, c(.90, .91, .92, .93, .94, .95)));
}  

source('C:/Users/~Call_R_function.R')

тест лежит на x=runif(1E6) перечислены ниже:

microbenchmark(sub_percentile(x)->aa, percentile_rcpp(x, c(.90, .91, .92, .93, .94, .95))->bb)
#Unit: milliseconds
              expr      min       lq     mean   median       uq       max   neval
  sub_percentile(x) 99.00029 99.24160 99.35339 99.32162 99.41869 100.57160   100
 percentile_rcpp(~) 87.13393 87.30904 87.44847 87.40826 87.51547  88.41893   100

Я ожидаю быстрой скорости процентиль расчет, но я предполагаю,std::sort(tmp.begin(), tmp.end()) замедляет скорость. Есть ли лучший способ получить быстрый результат с помощью C++, RCpp/RcppAramdillo? Спасибо.

2 ответов


ветвление в цикле может быть оптимизировано. Используйте вызовы std::min/max с помощью ints.

Я бы решил процентный расчет индексов массива таким образом:

uint PerCentIndex( double pc, uint size )
{
    return 0.5 + ( double ) ( size - 1 ) * pc;
}

только эта строка в середине цикла выше:

percentile_vec[ii] 
 = tmp_sort[ PerCentIndex( percentile[ii], tmp_sort.size() ) ];

в зависимости от того, сколько процентилей вам нужно вычислить и насколько велики ваши векторы, вы можете сделать гораздо лучше (только O(N)), чем сортировка всего вектора (в лучшем случае O(N*log(N))).

мне пришлось вычислить 1 процентиль векторов (>=160K) элементов, поэтому я сделал следующее:

void prctile_stl(double* in, const dim_t &len, const double &percent, std::vector<double> &range) {
// Calculates "percent" percentile.
// Linear interpolation inspired by prctile.m from MATLAB.

double r = (percent / 100.) * len;

double lower = 0;
double upper = 0;
double* min_ptr = NULL;
dim_t k = 0;

if(r >= len / 2.) {     // Second half is smaller
    dim_t idx_lo = max(r - 1, (double) 0.);
    nth_element(in, in + idx_lo, in + len);             // Complexity O(N)
    lower = in[idx_lo];
    if(idx_lo < len - 1) {
        min_ptr = min_element(&(in[idx_lo + 1]), in + len);
        upper = *min_ptr;
        }
    else
        upper = lower;
    }
else {                  // First half is smaller
    double* max_ptr;
    dim_t idx_up = ceil(max(r - 1, (double) 0.));
    nth_element(in, in + idx_up, in + len);             // Complexity O(N)
    upper = in[idx_up];
    if(idx_up > 0) {
        max_ptr = max_element(in, in + idx_up);
        lower = *max_ptr;
        }
    else
        lower = upper;
    }

// Linear interpolation
k = r + 0.5;        // Implicit floor
r = r - k;
range[1] = (0.5 - r) * lower + (0.5 + r) * upper;

min_ptr = min_element(in, in + len);
range[0] = *min_ptr;
}

Другой альтернативой является алгоритм IQAgent из численных приемников 3rd. Эд. Первоначально он предназначался для потоков данных, но вы можете обмануть его, разделив свой большой datavector в меньшие куски (например, элементы 10K) и вычислить процентили для каждого из блоков (где используется сортировка на кусках 10K). Если вы обрабатываете блоки по одному, каждый последующий блок будет немного изменять значения процентилей, пока вы не получите довольно хорошее приближение в конце. Алгоритм дал хорошие результаты (до 3-го или 4-го десятичного знака), но был еще медленнее, чем реализация n-го элемента.