Эффективно примените sample () в R

Мне нужно выбрать переменную результата с учетом матрицы с построчными вероятностями результата.

set.seed(1010) #reproducibility

#create a matrix of probabilities
#three possible outcomes, 10.000 cases
probabilities <- matrix(runif (10000*3),nrow=10000,ncol=3)
probabilities <- probabilities / Matrix::rowSums(probabilities)

Самый быстрый способ, который я мог придумать, - это комбинация apply () и sample ().

#row-wise sampling using these probabilities
classification <- apply(probabilities, 1, function(x) sample(1:3, 1, prob = x))

Однако в том, что я делаю, это вычислительное узкое место. У вас есть идея, как ускорить этот код / ​​как сэмплировать более эффективно?

Спасибо!

Решение, использующее cran.r-project.org/web/packages/Rcpp/index.html, вероятно, лучший вариант, который у вас есть.

RLave 09.11.2018 12:02

Другая полезная информация может быть здесь: gallery.rcpp.org/articles/…

RLave 09.11.2018 13:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
244
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если вы хотите поместить probabilities в list, purrr::map или lapply кажутся немного быстрее:

probabilities <- matrix(runif (10000*3),nrow=10000,ncol=3)
probabilities <- probabilities / Matrix::rowSums(probabilities)
probabilities_list <- split(probabilities, seq(nrow(probabilities)))

library(purrr)
set.seed(1010)
classification_list <- map(probabilities_list, function(x) sample(1:3, 1, prob = x))

set.seed(1010)
classification_list <- lapply(probabilities_list, function(x) sample(1:3, 1, prob = x))

Бенчмаркинг:

microbenchmark::microbenchmark(
  apply = {classification = apply(probabilities, 1, function(x) sample(1:3, 1, prob = x))},
  map = {classification = map(probabilities_list, function(x) sample(1:3, 1, prob = x))},
  lapply = {classification = lapply(probabilities_list, function(x) sample(1:3, 1, prob = x))},
  times = 100
)
# Unit: milliseconds
#  expr      min       lq     mean   median       uq      max neval
# apply 39.92883 42.59249 48.39247 45.03080 47.86648 94.39828   100
#   map 35.54077 37.13866 42.19719 39.95046 41.56323 66.05167   100
#lapply 34.54861 36.48664 42.69512 39.20139 52.31494 59.29200   100

С чехлами 100.000

# Unit: milliseconds
# expr      min       lq     mean   median       uq      max neval
# apply 457.5310 520.4926 572.5974 552.1674 611.5640 957.3997   100
#   map 391.4751 457.7326 488.3286 482.1459 512.2054 899.1380   100
#lapply 386.2698 443.6732 491.9957 475.4160 507.3677 868.6725   100
Ответ принят как подходящий

Комментарий RLave о том, что Rcpp может быть подходящим вариантом (вам также понадобится RcppArmadillo для sample()); Для создания такой функции я использовал следующий код C++:

// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadilloExtensions/sample.h>

using namespace Rcpp;

// [[Rcpp::export]]
IntegerVector sample_matrix(NumericMatrix x, IntegerVector choice_set) {
    int n = x.nrow();
    IntegerVector result(n);
    for ( int i = 0; i < n; ++i ) {
        result[i] = RcppArmadillo::sample(choice_set, 1, false, x(i, _))[0];
    }
    return result;
}

Затем я сделал эту функцию доступной в моем сеансе R через

Rcpp::sourceCpp("sample_matrix.cpp")

Теперь мы можем протестировать его на R в сравнении с вашим первоначальным подходом, а также с другими предложениями по использованию purrr::map() и lapply():

set.seed(1010) #reproducibility

#create a matrix of probabilities
#three possible outcomes, 10.000 cases
probabilities <- matrix(runif (10000*3),nrow=10000,ncol=3)
probabilities <- probabilities / Matrix::rowSums(probabilities)
probabilities_list <- split(probabilities, seq(nrow(probabilities)))

library(purrr)
library(microbenchmark)

microbenchmark(
    apply = apply(probabilities, 1, function(x) sample(1:3, 1, prob = x)),
    map = map(probabilities_list, function(x) sample(1:3, 1, prob = x)),
    lapply = lapply(probabilities_list, function(x) sample(1:3, 1, prob = x)),
    rcpp = sample_matrix(probabilities, 1:3),
    times = 100
)

Unit: milliseconds
   expr       min        lq      mean    median        uq       max neval
  apply 307.44702 321.30051 339.85403 342.36421 350.86090 434.56007   100
    map 254.69721 265.10187 282.85592 286.21680 295.48886 363.95898   100
 lapply 249.68224 259.70178 280.63066 279.87273 287.10062 691.21359   100
   rcpp  12.16787  12.55429  13.47837  13.81601  14.25198  16.84859   100
 cld
   c
  b 
  b 
 a  

Значительная экономия времени.

Это выглядит очень многообещающе, спасибо большое. Можно ли переписать команду Rcpp, чтобы она была динамической по количеству состояний? Т.е. без необходимости переписывать как IntegerVector :: create (1, 2, 3, 4) для 4 состояний и так далее. У меня нет идеи C++, так что потерпите, пожалуйста.

yrx1702 09.11.2018 14:02

@ Мистер Дзен Конечно! Вскоре я отредактирую, чтобы включить эту функцию.

duckmayr 09.11.2018 14:03

@ Mr.Zen Обновлено; теперь набор выбора является аргументом функции (как в R sample()). Вы можете видеть, что прирост производительности все еще есть, но теперь он обладает необходимой гибкостью.

duckmayr 09.11.2018 14:12

Большое спасибо! Я назначу награду, когда у меня будет возможность сделать это в течение ~ 21 часа.

yrx1702 09.11.2018 14:16

Вы можете рассмотреть

  • vapply и
  • параллелизация: parallel::parApply

С вашей матрицей probabilities:

set.seed(1010) #reproducibility

#create a matrix of probabilities
#three possible outcomes, 10.000 cases
probabilities <- matrix(runif (10000*3), nrow=10000,ncol=3)
probabilities <- probabilities / Matrix::rowSums(probabilities)
classification <- apply(probabilities, 1, function(x) sample(1:3, 1, prob = x))

напыщенный

Указав класс для FUN.VALUE, вы, возможно, сможете сделать это быстро.

classification2 <- vapply(split(probabilities, 1:nrow(probabilities)),
                          function(x) sample(1:3, 1, prob = x),
                          FUN.VALUE = integer(1), USE.NAMES = FALSE)
head(classification2)
#> [1] 1 3 3 1 2 3

параллельный пакет

benchmarkme::get_cpu()
#> $vendor_id
#> [1] "GenuineIntel"
#> 
#> $model_name
#> [1] "Intel(R) Core(TM) i5-4288U CPU @ 2.60GHz"
#> 
#> $no_of_cores
#> [1] 4

В вышеуказанной среде

cl <- parallel::makeCluster(4)
doParallel::registerDoParallel(cl, cores = 4)

parApply() может делать то же, что и apply().

classification3 <- parallel::parApply(cl, probabilities, 1, function(x) sample(1:3, 1, prob = x))
head(classification3)
#> [1] 2 2 2 2 3 3

Сравнивая три, включая решение apply(),

microbenchmark::microbenchmark(
  question = { # yours
    apply(probabilities, 1, function(x) sample(1:3, 1, prob = x))
  },
  vapp = {
    vapply(split(probabilities, 1:nrow(probabilities)), function(x) sample(1:3, 1, prob = x), FUN.VALUE = integer(1), USE.NAMES = FALSE)
  },
  parr = {
    parallel::parApply(cl, probabilities, 1, function(x) sample(1:3, 1, prob = x))
  }
)
#> Unit: milliseconds
#>      expr      min       lq     mean   median       uq       max neval
#>  question 49.93853 58.39965 65.05360 62.98119 68.28044 182.03267   100
#>      vapp 44.19828 54.84294 59.47109 58.56739 62.05269 146.14792   100
#>      parr 43.33227 48.16840 53.26599 50.87995 54.17286  98.67692   100

parallel::stopCluster(cl)

Другие вопросы по теме