R - Самый быстрый способ получения подмножеств l oop и получения результатов с использованием таблиц данных (вычисление месячных показателей) - PullRequest
2 голосов
/ 30 марта 2020

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

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

  date     event  id return
2000-07-06     2  1   0.1
2000-07-07     1  1   0.2
2000-07-09     0  1   0.6
2000-07-10     0  1   0.4
2000-07-15     2  1   0.7
2000-07-16     1  1   0.3
2000-07-20     0  1   0.1
2000-07-21     1  1   0.2
2000-07-06     1  2   0.3
2000-07-07     2  2   0.4
2000-07-15     0  2   0.6
2000-07-16     0  2   0.8
2000-07-17     2  2   0.9
2000-07-18     1  2   0.1

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


for (j in 1:length(list.of.ids)) {
  for (i in 1:(number.of.months) {
    temp <- subset(data, data$date < FirstDayMonth[i+1] & data$date >= FirstDayMonth[i] & data$id == list.of.ids[j])
    total[i,j+1] <- sum(temp$return, na.rm = TRUE)
  }
}

Примечание: total [,] - это матрица со столбцом времени и одним столбцом для каждого идентификатора, а количество строк равняется каждому месяцу в наборе данных. Я надеюсь, что у меня будет матрица, в которой хранятся все мои месячные показатели для идентификаторов и месяцев. Этот l oop позволяет мне рассчитать месячную сумму возвратов по идентификатору и затем сохранить ее в этой матрице.

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

Есть ли какие-либо улучшения в коде, которые позволят мне получить желаемый результат быстрее?

Ответы [ 3 ]

2 голосов
/ 30 марта 2020

Улучшения, которые должны привести к ускорению:

for (j in 1:length(list.of.ids)) {
  id1 <- data$id == list.of.ids[j]
  # outside 2nd loop so no redundant operations wont be made
  for (i in 1:(number.of.months)) {
    id2 <- data$date < FirstDayMonth[i+1] & data$date >= FirstDayMonth[i]
    total[i, j+1] <- sum(data$return[id1 & id2], na.rm = TRUE)
  }
}

(вероятно, большие улучшения, так как нам не нужно каждый раз создавать новый объект data.frame, мы просто получаем индексы, для элементов которых нам нужно вычислить сумма)

Но я бы использовал data.table:

require(data.table)
data <- as.data.table(data)
data[, ym := format(date, '%Y-%m')]
res <- data[, sum(return, na.rm = T), keyby = .(ym, id)]
res
#         ym id  V1
# 1: 2000-07  1 2.6
# 2: 2000-07  2 3.1

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

m <- matrix(res$V1, nrow = length(unique(res$ym)))
m
#      [,1] [,2]
# [1,]  2.6  3.1

Обновление:

Более быстрая версия:

setDT(data) # converts data to data.table
x <- data[, .(date = unique(date))][, .(date, ym = format(date, '%Y-%m'))]
data[x, ym := i.ym, on = 'date']
res <- data[, sum(return, na.rm = T), keyby = .(ym, id)]
res

(format(date, '%Y-%m') медленный, поэтому мы берем только уникальные даты и вычисляем для них ym, затем объединяем их с данными. Это должно быть довольно быстро, если вы имеют много повторяющихся дат.)

Обновление 2:

Преобразование в матрицу можно получить с помощью:

resdt <- dcast(res, ym ~ id, value.var = 'V1') # change data structure
resdt[1:2, 1:3]
#         ym        1        2
# 1: 2000-01 6.824182 2.535805
# 2: 2000-02 3.825659 6.769578
resdt[, ym := NULL] # delets ym
setcolorder(resdt, neworder = list.of.ids) # reorder columns
m <- as.matrix(resdt)
m[1:2, 1:2]
#             1        2         3
# [1,] 6.824182 2.535805 -1.193692
# [2,] 3.825659 6.769578 -1.117223
1 голос
/ 30 марта 2020

Это должно быть значительно быстрее:

for(i in 1:length(number.of.months)) {
  inds <- dat$date < FirstDayMonth[i+1] & dat$date >= FirstDayMonth[i]
  total[i,] <- rowsum(dat$result[inds], dat$id[inds], na.rm=TRUE)
}
1 голос
/ 30 марта 2020

Использование aggregate. Переменная год-месяц ym, которую мы можем создать с помощью substr от первого до седьмого символа столбца даты.

m <- with(dat, aggregate(list(return=return),
                         by=list(ym=substr(date, 1, 7), id=id), sum))
m
#        ym id return
# 1 2000-07  1    2.6
# 2 2000-07  2    3.1

Или tapply.

m <- with(dat, tapply(return, list(ym=substr(date, 1, 7), id=id), sum))
m
#          id
# ym          1   2
#   2000-07 2.6 3.1

Данные

dat <- structure(list(date = c("2000-07-06", "2000-07-07", "2000-07-09", 
"2000-07-10", "2000-07-15", "2000-07-16", "2000-07-20", "2000-07-21", 
"2000-07-06", "2000-07-07", "2000-07-15", "2000-07-16", "2000-07-17", 
"2000-07-18"), event = c(2L, 1L, 0L, 0L, 2L, 1L, 0L, 1L, 1L, 
2L, 0L, 0L, 2L, 1L), id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 
2L, 2L, 2L, 2L, 2L), return = c(0.1, 0.2, 0.6, 0.4, 0.7, 0.3, 
0.1, 0.2, 0.3, 0.4, 0.6, 0.8, 0.9, 0.1)), row.names = c(NA, -14L
), class = "data.frame")
...