У меня есть большая проблема производительности в R. Я записал функцию, которая выполняет итерации по a data.frame
объект. Это просто добавляет новый столбец к a data.frame
и накапливает что-то. (простая операция). data.frame
имеет примерно 850K строки. Мой ПК все еще работает (о 10-м теперь), и я понятия не имею о времени выполнения.
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
temp[i,10] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
temp[i,10] <- temp[i,9] + temp[i-1,10]
} else {
temp[i,10] <- temp[i,9]
}
} else {
temp[i,10] <- temp[i,9]
}
}
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
Какие-либо идеи, как ускорить эту операцию?
В R часто можно ускорить обработку цикла, используя функции семейства apply
(в вашем случае это, вероятно, replicate
). Посмотрите на пакет plyr
, который предоставляет индикаторы выполнения.
Другой вариант - вообще отказаться от циклов и заменить их векторной арифметикой. Я не уверен, что именно вы делаете, но, вероятно, вы можете применить вашу функцию ко всем строкам сразу:
temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]
Это будет намного быстрее, а затем вы можете отфильтровать строки с помощью вашего условия:
cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]
Векторизованная арифметика требует больше времени и обдумывания проблемы, но тогда вы можете иногда сэкономить несколько порядков времени выполнения.
Это можно сделать намного быстрее, пропуская циклы с помощью индексов или вложенных операторов ifelse()
.
idx <- 1:nrow(temp)
temp[,10] <- idx
idx1 <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
temp[idx1,10] <- temp[idx1,9] + temp[which(idx1)-1,10]
temp[!idx1,10] <- temp[!idx1,9]
temp[1,10] <- temp[1,9]
names(temp)[names(temp) == "V10"] <- "Kumm."
Самая большая проблема и корень неэффективности - это индексирование data.frame, я имею в виду все эти строки, где вы используете temp [,]
.
Постарайтесь по возможности избегать этого. Я взял вашу функцию, изменил индексирование и здесь version_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Как видите, я создал вектор res
, который собирает результаты. В конце я добавляю его в data.frame
, и мне не нужно возиться с именами.
Так насколько это лучше?
Я запускаю каждую функцию для data.frame
с помощью nrow
от 1000 до 10 000 на 1000 и измеряю время с помощью system.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
Результат:
Вы можете видеть, что ваша версия экспоненциально зависит от nrow (X)
. Модифицированная версия имеет линейную зависимость, и простая модель lm
предсказывает, что для вычисления 850 000 строк требуется 6 минут 10 секунд.
Как заявляют Шейн и Калимо в своих ответах, векторизация является ключом к повышению производительности. Из своего кода вы можете выйти за пределы цикла:
temp [i, 9]
) Это приводит к этому коду
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Сравнить результат для этой функции на этот раз для nrow
от 10 000 до 100 000 на 10 000.
Другой настройкой является изменение индексации цикла temp [i, 9]
на res [i]
(которые в точности совпадают в i- итерация цикла).
Это снова разница между индексированием вектора и индексированием data.frame
.
Второе: когда вы смотрите на цикл, вы можете видеть, что нет необходимости перебирать все i
, а только те, которые соответствуют условию.
Итак, начнем
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Производительность, которую вы получаете, сильно зависит от структуры данных. Точно - по процентам от ИСТИННЫХ
значений в условии.
Для моих смоделированных данных требуется время вычисления для 850 000 строк меньше одной секунды.
Если вы хотите, чтобы вы пошли дальше, я вижу, по крайней мере, две вещи, которые можно сделать:
C
для выполнения условного накопления , если вы знаете, что в ваших данных max последовательность невелика, вы можете изменить цикл на векторизованный while, что-то вроде
while (any (cond)) {
indx <- c (ЛОЖЬ, cond [-1] &! cond [-n])
res [indx] <- res [indx] + res [which (indx) -1]
cond [indx] <- FALSE
}
Код, используемый для моделирования и рисунков, доступен на GitHub .