Самая большая проблема и корень неэффективности заключается в индексации 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 до 10000 на 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-го цикла). Это опять разница между индексированием вектора и индексированием a 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)
}
Производительность, которую вы получаете, сильно зависит от структуры данных. Точно - на процент TRUE
значений в состоянии. Для моих смоделированных данных требуется время вычисления на 850 000 строк ниже одной секунды.
Если вы хотите, вы можете пойти дальше, я вижу, по крайней мере, две вещи, которые можно сделать:
- Напиши
C
код, чтобы сделать условное cumsum
если вы знаете, что в ваших данных максимальная последовательность не велика, вы можете изменить цикл на векторизованное время, что-то вроде
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
Код, используемый для моделирования и рисунков, доступен на GitHub .
res = c(1,2,3,4)
иcond
это всеTRUE
, то конечный результат должен быть:1
,3
(причина1+2
),6
(причина второй теперь3
, и третий3
и),10
(6+4
). Делая простое суммирование вы получили1
,3
,5
,7
.Общие стратегии для ускорения кода R
Во-первых, выяснить, где медленная часть на самом деле. Нет необходимости оптимизировать код, который не работает медленно. Для небольшого количества кода, просто продумывая его, может работать. Если это не помогло, RProf и подобные инструменты профилирования могут быть полезны.
Как только вы обнаружите узкое место, подумайте о более эффективных алгоритмах для выполнения того, что вы хотите. Расчеты должны выполняться только один раз, если это возможно, поэтому:
Использование более эффективных функций может привести к умеренному или большому увеличению скорости. Например,
paste0
дает небольшой прирост эффективности, но.colSums()
и его родственники дают несколько более выраженный прирост.mean
является особенно медленным .Тогда вы можете избежать некоторых наиболее распространенных проблем :
cbind
замедлит вас очень быстро.Попробуйте улучшить векторизацию , которая часто, но не всегда, помогает. В связи с этим, по своей сути векторизованных команды типа
ifelse
,diff
и тому подобное обеспечит больше , чем улучшениеapply
семейство команд (которые обеспечивают практически полное повышение скорости по сравнению с хорошо написанным циклом).Вы также можете попытаться предоставить дополнительную информацию для функций R . Например, используйте
vapply
вместоsapply
, и укажитеcolClasses
при чтении в текстовых данных . Прирост скорости будет изменяться в зависимости от того, сколько угадывания вы устраняете.Далее рассмотрим оптимизированные пакеты .
data.table
Пакет может значительно увеличить скорость, где его использование возможно, при манипулировании данными и при чтении больших объемов данных (fread
).Затем попробуйте увеличить скорость за счет более эффективных способов вызова R :
Ra
иjit
пакеты концерта для точно в момент компиляции (Dirk есть пример в данной презентации ).И, наконец, если все вышеперечисленное все еще не дает вам такой скорости, как вам нужно, вам может потребоваться перейти на более быстрый язык для медленного фрагмента кода . Комбинация
Rcpp
иinline
здесь делает замену только самой медленной части алгоритма кодом C ++ особенно легкой. Вот, например, моя первая попытка сделать это , и она поражает даже высоко оптимизированные R-решения.Если после всего этого у вас все еще есть проблемы, вам просто нужно больше вычислительной мощности. Посмотрите на распараллеливание ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) или даже решения на основе GPU (
gpu-tools
).Ссылки на другие руководства
источник
Если вы используете
for
циклы, вы, скорее всего, кодируете R, как если бы это был C, Java или что-то еще. Правильно векторизованный R-код очень быстр.Возьмем, к примеру, эти два простых фрагмента кода для генерации списка из 10 000 целых чисел в последовательности:
Первый пример кода - это то, как можно было бы закодировать цикл, используя традиционную парадигму кодирования. Это займет 28 секунд, чтобы завершить
Вы можете получить улучшение почти в 100 раз простым действием предварительного выделения памяти:
Но используя базовую векторную операцию R с использованием оператора двоеточия,
:
эта операция практически мгновенная:источник
a[i]
не меняется. Ноsystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
имеет аналогичный результат.rep(1, 1e5)
- время идентично.Это можно сделать намного быстрее, пропустив циклы с помощью индексов или вложенных
ifelse()
операторов.источник
i
-th значение зависит отi-1
-th, поэтому они не могут быть установлены так, как вы это делаете (используяwhich()-1
).Мне не нравится переписывать код ... Также, конечно, ifelse и lapply - лучшие варианты, но иногда это трудно сделать.
Часто я использую data.frames, как можно использовать списки, такие как
df$var[i]
Вот вымышленный пример:
версия data.frame:
версия списка:
В 17 раз быстрее использовать список векторов, чем в data.frame.
Любые комментарии о том, почему внутренне data.frames так медленно в этом отношении? Казалось бы, они работают как списки ...
Для еще более быстрого кода сделайте это
class(d)='list'
вместоd=as.list(d)
иclass(d)='data.frame'
источник
[<-.data.frame
, которые как-то вызываются, когда вы это делаете,d$foo[i] = mark
и могут в конечном итоге создать новую копию вектора, возможно, всего data.frame в каждой<-
модификации. Это сделало бы интересный вопрос о SO.df$var[i]
Нотация проходит через ту же[<-.data.frame
функцию? Я заметил, что это действительно довольно долго. Если нет, то какую функцию он использует?d$foo[i]=mark
переводится примерноd <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, но с некоторым использованием временных переменных.Как Ари уже упоминался в конце своего ответа,
Rcpp
иinline
пакеты делают его невероятно легко сделать вещи быстро. Как пример, попробуйте этотinline
код (предупреждение: не проверено):Есть аналогичная процедура для
#include
вещей, где вы просто передаете параметрк функции, как
include=inc
. Что действительно круто в этом, так это то, что он выполняет всю компоновку и компиляцию за вас, поэтому прототипирование действительно быстрое.Отказ от ответственности: я не совсем уверен, что класс tmp должен быть числовым, а не числовой матрицей или чем-то еще. Но я в основном уверен.
Изменить: если вам все еще нужно больше скорости после этого, OpenMP является средством параллелизации хорошо для
C++
. Я не пробовал использовать егоinline
, но он должен работать. Идея была бы, в случаеn
ядер, есть цикл итерацияk
быть осуществлена путемk % n
. Подходящее введение можно найти в Matloff's Art of R Programming , доступной здесь , в главе 16, « Использование C» .источник
Ответы здесь великолепны. Один незначительный аспект, который не был затронут, заключается в том, что вопрос гласит: « Мой ПК все еще работает (около 10 часов), и я понятия не имею о времени выполнения ». Я всегда вставляю следующий код в циклы при разработке, чтобы понять, как изменения влияют на скорость, а также для отслеживания того, сколько времени потребуется для завершения.
Работает с lapply также.
Если функция в цикле довольно быстрая, но число циклов велико, рассмотрите возможность периодической печати, так как печать на самой консоли имеет накладные расходы. например
источник
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
так как я обычно перебираю именованные вещи (с именами вnm
).В R вы часто можете ускорить циклическую обработку, используя
apply
семейные функции (в вашем случае, вероятно, так и будетreplicate
). Посмотрите наplyr
пакет, который предоставляет индикаторы выполнения.Другой вариант - вообще избежать циклов и заменить их векторной арифметикой. Я не совсем уверен, что вы делаете, но вы, вероятно, можете применить свою функцию ко всем строкам одновременно:
Это будет намного быстрее, и тогда вы сможете отфильтровать строки с вашим условием:
Векторизованная арифметика требует больше времени и размышлений о проблеме, но иногда вы можете сэкономить несколько порядков времени выполнения.
источник
Обработка с помощью
data.table
является жизнеспособным вариантом:Если вы игнорируете возможные выгоды от фильтрации условий, это очень быстро. Очевидно, что если вы можете сделать расчет на подмножестве данных, это помогает.
источник