Ускорьте операцию цикла в R

У меня есть большая проблема производительности в 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)
}

Какие-либо идеи, как ускорить эту операцию?

182
задан Saranjith 14 July 2018 в 09:17
поделиться

3 ответа

В 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]

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

2
ответ дан 23 November 2019 в 06:01
поделиться

Это можно сделать намного быстрее, пропуская циклы с помощью индексов или вложенных операторов 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."
17
ответ дан 23 November 2019 в 06:01
поделиться

Самая большая проблема и корень неэффективности - это индексирование 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))

Результат:

performance

Вы можете видеть, что ваша версия экспоненциально зависит от 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.

performance

Настройка настроенного

Другой настройкой является изменение индексации цикла 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 строк меньше одной секунды.

performance

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

  • напишите код C для выполнения условного накопления
  • , если вы знаете, что в ваших данных max последовательность невелика, вы можете изменить цикл на векторизованный while, что-то вроде

     while (any (cond)) {
    indx <- c (ЛОЖЬ, cond [-1] &! cond [-n])
    res [indx] <- res [indx] + res [which (indx) -1]
    cond [indx] <- FALSE
    }
    

Код, используемый для моделирования и рисунков, доступен на GitHub .

422
ответ дан 23 November 2019 в 06:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: