Альтернативы для цикла и подмножества набора данных.(..используя функции высшего порядка или альтернативные структуры данных) - PullRequest
0 голосов
/ 05 июня 2018

Я выполняю моделирование дискретных событий в R. «Сердце» моего алгоритма выполняет следующее (псевдокод):

1) Итерация по events

a) Изменить event[i] в зависимости от resources

b) Изменить resources в зависимости от результата шага a)

В следующем воспроизводимом примере рассматриваются основные аспекты:

Генерация некоторых данных:

set.seed(4)
n <- 3
nr_resources <- 2

events <- data.frame(
  t = as.integer(trunc(cumsum(rexp(n)))),
  resource = NA,
  worktime = as.integer(trunc(runif(n)*10))
)

resources <- data.frame(
  id = 1:nr_resources,
  t_free = 0L
)
events
resources

# > events
# t resource worktime
# 0       NA        2
# 4       NA        8
# 5       NA        2
# > resources
# id t_free
#  1      0
#  2      0

Теперь мы можем смоделировать диспетчеризацию ресурсов:

for (i in 1:n) {
  events$resource[i] <- resources$id[resources$t_free <= events$t[i]][1]
  resources$t_free[events$resource[i]] <- events$t[i] + events$worktime[i]
}

events
resources

# > events
# t resource worktime
# 0        1        2
# 4        1        8
# 5        2        2
# > resources
# id t_free
#  1     12
#  2      7

Этот подход работает хорошо, но есть ряд недостатков, которые я хотел быустранить.Поскольку events и resources разделены на два набора данных, существует довольно много подмножеств (поиск и замена), происходящих в двух наборах данных.Это не совсем читабельно.И в реальном приложении это даже становится узким местом производительности.(... конечно, реальный пример немного сложнее ..)

Поэтому я спрашиваю себя, есть ли лучшие способы выполнить эту задачу в R.

Я думал о замене цикла for общей функцией высшего порядка, но не дал никаких результатов.

  • Типичный подход R lapply не работает, потому что lapplyне построен для этого итеративных изменений во входных данных.(Насколько я вижу ..)
  • Моя задача немного похожа на шаблон Reduce.Поскольку Reduce(sum, 1:3, accumulate = TRUE) использует промежуточные результаты, а также сохраняет их, я подумал, что могу использовать функцию Reduce, но не достиг никаких результатов.

Я также думал о реструктуризации своих данных, но безуспешно досейчас.

Что я пытался подробно

На алгоритмической стороне:

Неудачный подход с lapply:

l <- list(events = events, resources = resources)
l <- lapply(l, function(x) {
  l$events$resource <- l$resources$id[l$resources$t_free <= l$events$t][1]
  l$resources$t_free[l$events$resource] <- l$events$t + l$events$worktime
  return(l)
})

l$events
l$resources

В результате получается:

# $events
# t resource worktime
# 1 0        1        2
# 2 4        1        8
# 3 5        1        2
# 
# $resources
# id t_free
# 1  1      7
# 2  2      0

Промежуточные изменения ресурсов теряются и, следовательно, всегда резервируется ресурс 1.


Неудачный подход с Reduce:

l <- list(events = events, resources = resources)
l <- Reduce(function(l) {
  l$events$resource <- l$resources$id[l$resources$t_free <= l$events$t][1]
  l$resources$t_free[l$events$resource] <- l$events$t + l$events$worktime
  return(l)}, l, accumulate = TRUE)

Сбой с

Ошибка в f (init, x [[i]]): неиспользованный аргумент (x [[i]])


На стороне data :

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

data <- merge(events, resources)
data <- data[order(data$t), ]
data

# t resource worktime id t_free
# 0       NA        2  1      0
# 0       NA        2  2      0
# 4       NA        8  1      0
# 4       NA        8  2      0
# 5       NA        2  1      0
# 5       NA        2  2      0

for (i in seq_along(data)) {
  if ( is.na(data$resource[i])) {
    data$resource[data$t == data$t[i]] <- data$id[data$t_free <= data$t[i]][1]
    data$t_free[data$id == data$resource[i]] <- data$t[i] + data$worktime[i]
  }
}

data
# t resource worktime id t_free
# 0        1        2  1     12
# 0        1        2  2      7
# 4        1        8  1     12
# 4        1        8  2      7
# 5        2        2  1     12
# 5        2        2  2      7

events <- unique(data[,1:3])
events
# t resource worktime
# 0        1        2
# 4        1        8
# 5        2        2

resources <- unique(data[,4:5])
resources
# id t_free
#  1     12
#  2      7

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


Так что мойвопрос:

Существуют ли альтернативы на стороне с алгоритмической или на стороне data , которые улучшают мои фактические решения?

1 Ответ

0 голосов
/ 05 июня 2018

Честно говоря, я предпочитаю ваш первый цикл for, вам следует подумать об использовании чего-то вроде Rcpp::sourceCpp и перенести вашу логику на C ++.Я думаю, что это должно быть читаемым и быстрее.Если вы должны сделать это в R, вот возможность:

t_free <- Reduce(x = 1L:n,
                 init = rep(0L, nr_resources),
                 accumulate = TRUE,
                 f = function(t_free, i) {
                   # which.max will return the location of the first TRUE
                   id <- which.max(t_free <= events$t[i])
                   # R makes a local copy of t_free here
                   t_free[id] <- events$t[i] + events$worktime[i]
                   # return the chosen resource for this "iteration"
                   attr(t_free, "resource") <- id
                   # return the modified copy
                   t_free
                 })

# events$resource column by extracting the resource attribute, igonring init
events$resource <- sapply(t_free[-1L], attr, "resource")
# your resources$t_free column in the last element
resources <- data.frame(id = 1L:nr_resources,
                        t_free = t_free[[n + 1L]])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...