Использование стандартной оценки и do_ для запуска моделирования по сетке параметров без do.call

Цели

Я хочу использовать dplyr для запуска моделирования на сетках параметров. В частности, мне нужна функция, которую я могу использовать в другой программе, которая

  • передается data.frame
  • для каждой строки вычисляется некоторое моделирование с использованием каждого столбца в качестве аргумента
  • также передаются некоторые дополнительные данные (например, начальные условия)

Здесь мой подход

require(dplyr)
run <- function(data, fun, fixed_parameters, ...) {
   ## ....
   ## argument checking
   ##

   fixed_parameters <- as.environment(fixed_parameters)
   grouped_out <- do_(rowwise(data), ~ do.call(fun, c(., fixed_parameters, ...)))
   ungroup(grouped_out)
 }

Это работает. Например, для

growth <- function(n, r, K, b) {
  # some dynamical simulation
  # this is an obviously-inefficient way to do this ;)
  n  + r - exp(n) / K - b - rnorm(1, 0, 0.1)
}
growth_runner <- function(r, K, b, ic, ...) {
  # a wrapper to run the simulation with some fixed values
  n0 = ic$N0
  T = ic$T
  reps = ic$reps
  data.frame(n_final = replicate(reps, {for(t in 1:T) {
                                          n0 <- growth(n0, r, K, b)
                                        };
                                        n0})
  )
}

Я могу определить и запустить,

   data <- expand.grid(b = seq(0.01, 0.5, length.out=10),
                       K = exp(seq(0.1, 5, length.out=10)),
                       r = seq(0.5, 3.5, length.out=10))
   initial_data = list(N0=0.9, T=5, reps=20)
   output <- run(data, growth_runner, initial_data)

Вопрос

Хотя это, похоже, работает, мне интересно, есть ли способ сделать это без do.call. (Отчасти из-за проблем с do.call.)

Мне действительно интересен способ заменить строку grouped_out <- do_(rowwise(data), ~ do.call(fun, c(., fixed_parameters, ...))) тем, что делает то же самое, но без do.call. Изменить: Также будет работать подход, который каким-то образом позволяет избежать штрафов за производительность при использовании do.call, описанных в приведенной выше ссылке.

Примечания и ссылки

Ответ 1

Мне было немного сложно следовать вашему коду, но я думаю, что это эквивалентно.

Сначала я определяю функцию, которая вас интересует:

growth_t <- function(n0, r, K, b, T) {
  n <- n0

  for (t in 1:T) {
    n <- n + r - exp(n) / K - b - rnorm(1, 0, 0.1)
  }
  n
}

Затем я определяю данные, которые вы хотите изменить, включая переменную "dummy" для повторений:

data <- expand.grid(
  b = seq(0.01, 0.5, length.out = 5),
  K = exp(seq(0.1, 5, length.out = 5)),
  r = seq(0.5, 3.5, length.out = 5),
  rep = 1:20
)

Затем я могу передать его в purrr::pmap_d(). pmap_d() выполняет "параллельное" отображение, т.е. принимает в качестве входных данных список (или фрейм данных) и вызывает функцию, изменяющую все именованные аргументы для каждой итерации. Фиксированные параметры предоставляются после имени функции.

library(purrr)
data$output <- pmap_dbl(data[1:3], growth_t, n0 = 0.9, T = 5)

Это действительно не похоже на проблему dplyr, потому что это не касается манипуляции с данными.

Ответ 2

В приведенном ниже примере используется do.call и выводится вывод таким же образом, что и OP.

Сначала замените параметры функции вектором, который вы пройдете, - это то, что вы пройдете с помощью apply.

growth_runner <- function(data.in, ic, ...) {
  # a wrapper to run the simulation with some fixed values
  n0 = ic$N0
  T = ic$T
  reps = ic$reps
  data.frame(n_final = replicate(reps, {for(t in 1:T) {
    n0 <- growth(n0, data.in[3], data.in[2], data.in[1])
  };
    n0})
  )
}

Задайте сетку, которую вы хотите найти, так же, как и раньше.

data <- expand.grid(b = seq(0.01, 0.5, length.out=10),
                    K = exp(seq(0.1, 5, length.out=10)),
                    r = seq(0.5, 3.5, length.out=10))
initial_data = list(N0=0.9, T=5, reps=20)

Используйте для перехода через вашу сетку, затем добавьте результаты

output.mid = apply(data, 1, ic=initial_data, FUN=growth_runner)
output <- data.frame('n_final'=unlist(output.mid))

И у вас есть выход без каких-либо вызовов do.call или любой внешней библиотеки.

> dim(output)
[1] 20000     1
> head(output)
     n_final
1 -0.6375070
2 -0.7617193
3 -0.3266347
4 -0.7921655
5 -0.5874983
6 -0.4083613

Ответ 3

Вы можете заменить строку do.call следующим (спасибо @shorpy для указания purrr:invoke_rows()):

  grouped_out <- purrr::invoke_rows(fun, dplyr::rowwise(data), fixed_parameters)

без каких-либо изменений, это даст кадр данных с столбцом data.frames, например

Source: local data frame [1,000 x 4]
            b        K     r                .out
        (dbl)    (dbl) (dbl)               (chr)
1  0.01000000 1.105171   0.5 <data.frame [20,1]>
2  0.06444444 1.105171   0.5 <data.frame [20,1]>
3  0.11888889 1.105171   0.5 <data.frame [20,1]>

Чтобы восстановить что-то ближе к исходному поведению, замените конечную строку run на

dplyr::ungroup(tidyr::unnest(grouped_out, .out))

который дает

Source: local data frame [20,000 x 4]

       b        K     r    n_final
   (dbl)    (dbl) (dbl)      (dbl)
1   0.01 1.105171   0.5 -0.6745470
2   0.01 1.105171   0.5 -0.7500365
3   0.01 1.105171   0.5 -0.6568312

Никаких других изменений в коде не требуется:)