O maior problema e raiz da ineficácia é a indexação de data.frame, quero dizer todas essas linhas em que você usa temp[,]
.
Tente evitar isso o máximo possível. Peguei sua função, mudei de indexação e aqui 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)
}
Como você pode ver, crio um vetor res
que reúne resultados. No final, eu o adiciono data.frame
e não preciso mexer em nomes. Então, como é melhor?
Eu corro cada função data.frame
com nrow
de 1.000 a 10.000 por 1.000 e meço o tempo comsystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
O resultado é
Você pode ver que sua versão depende exponencialmente de nrow(X)
. A versão modificada possui relação linear e o lm
modelo simples prevê que, para 850.000 linhas, a computação leva 6 minutos e 10 segundos.
Poder da vetorização
Como Shane e Calimo declaram em suas respostas, a vetorização é a chave para um melhor desempenho. No seu código, você pode sair do loop:
- condicionamento
- inicialização dos resultados (que são
temp[i,9]
)
Isso leva a esse código
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)
}
Compare o resultado dessas funções, desta vez nrow
de 10.000 a 100.000 por 10.000.
Ajustando o sintonizado
Outro ajuste é a alteração de uma indexação de loop temp[i,9]
para res[i]
(que são exatamente iguais na i-ésima iteração de loop). Novamente, é uma diferença entre indexar um vetor e indexar a data.frame
.
Segunda coisa: quando você olha no loop, pode ver que não há necessidade de repetir tudo i
, mas apenas os que se encaixam na condição.
Aqui vamos nos
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)
}
O desempenho que você obtém altamente depende de uma estrutura de dados. Precisamente - em porcentagem de TRUE
valores na condição. Para meus dados simulados, leva tempo de computação para 850.000 linhas abaixo de um segundo.
Eu quero que você possa ir mais longe, vejo pelo menos duas coisas que podem ser feitas:
- escreva um
C
código para fazer cumsum condicional
se você sabe que na sequência máxima de dados não é grande, pode alterar o loop para vetorizado enquanto, algo como
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
O código usado para simulações e figuras está disponível no GitHub .
res = c(1,2,3,4)
econd
é tudoTRUE
, então resultado final deve ser:1
,3
(causa1+2
),6
(segunda causa é agora3
, e em terceiro lugar é3
também),10
(6+4
). Fazendo somatório simples que você tem1
,3
,5
,7
.Estratégias gerais para acelerar o código R
Primeiro, descubra onde realmente está a parte lenta. Não é necessário otimizar o código que não está sendo executado lentamente. Para pequenas quantidades de código, simplesmente pensar nele pode funcionar. Se isso falhar, o RProf e ferramentas de perfil semelhantes podem ser úteis.
Depois de descobrir o gargalo, pense em algoritmos mais eficientes para fazer o que deseja. Os cálculos devem ser executados apenas uma vez, se possível, portanto:
O uso de funções mais eficientes pode produzir ganhos de velocidade moderados ou grandes. Por exemplo,
paste0
produz um pequeno ganho de eficiência, mas.colSums()
seus familiares produzem ganhos um pouco mais pronunciados.mean
é particularmente lento .Então você pode evitar alguns problemas particularmente comuns :
cbind
irá atrasá-lo muito rapidamente.Tente uma melhor vetorização , o que geralmente pode ajudar, mas nem sempre. Nesse sentido, comandos inerentemente vetorizados como
ifelse
,diff
e similares fornecerão mais melhorias do que aapply
família de comandos (que fornecem pouco ou nenhum aumento de velocidade em um loop bem escrito).Você também pode tentar fornecer mais informações para funções R . Por exemplo, use em
vapply
vez desapply
e especifiquecolClasses
ao ler dados baseados em texto . Os ganhos de velocidade serão variáveis, dependendo de quanto você adivinhar.Em seguida, considere os pacotes otimizados : O
data.table
pacote pode produzir enormes ganhos de velocidade sempre que possível, na manipulação de dados e na leitura de grandes quantidades de dados (fread
).Em seguida, tente obter ganhos de velocidade por meios mais eficientes de chamar R :
Ra
ejit
em conjunto para compilação just-in-time (Dirk tem um exemplo nesta apresentação ).E, por último, se todas as opções acima ainda não o levarem tão rápido quanto necessário, você pode precisar mudar para um idioma mais rápido para o snippet de código lento . A combinação de
Rcpp
einline
aqui facilita muito a substituição da parte mais lenta do algoritmo pelo código C ++. Aqui, por exemplo, é a minha primeira tentativa de fazê-lo , e surpreende até as soluções R altamente otimizadas.Se você ainda tiver problemas depois de tudo isso, precisará de mais poder de computação. Examine a paralelização ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) ou mesmo as soluções baseadas em GPU (
gpu-tools
).Links para outras orientações
fonte
Se você estiver usando
for
loops, provavelmente está codificando R como se fosse C ou Java ou qualquer outra coisa. O código R adequadamente vetorizado é extremamente rápido.Tomemos, por exemplo, esses dois bits simples de código para gerar uma lista de 10.000 números inteiros em sequência:
O primeiro exemplo de código é como alguém codificaria um loop usando um paradigma de codificação tradicional. Demora 28 segundos para concluir
Você pode obter uma melhoria quase 100 vezes com a ação simples de pré-alocar memória:
Mas, usando a operação do vetor R base, usando o operador dois pontos,
:
essa operação é praticamente instantânea:fonte
a[i]
não muda. Massystem.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)})
tem um resultado semelhante.rep(1, 1e5)
- os tempos são idênticos.Isso pode ser feito muito mais rapidamente, ignorando os loops usando índices ou
ifelse()
instruções aninhadas .fonte
i
valor -th depende dei-1
-th, para que não possam ser definidos da maneira que você faz (usandowhich()-1
).Não gosto de reescrever código ... Também é claro que ifelse e lapply são melhores opções, mas às vezes é difícil ajustá-lo.
Freqüentemente eu uso data.frames como se usasse listas como
df$var[i]
Aqui está um exemplo inventado:
data.frame versão:
versão da lista:
17 vezes mais rápido para usar uma lista de vetores que um data.frame.
Quaisquer comentários sobre por que internamente os data.frames são tão lentos nesse sentido? Alguém poderia pensar que eles operam como listas ...
Para um código ainda mais rápido, faça isso em
class(d)='list'
vez ded=as.list(d)
eclass(d)='data.frame'
fonte
[<-.data.frame
, que é chamada de alguma forma quando você fazd$foo[i] = mark
e pode acabar fazendo uma nova cópia do vetor de possivelmente todo o data.frame em cada<-
modificação. Seria uma pergunta interessante sobre SO.df$var[i]
notação passa pela mesma[<-.data.frame
função? Eu notei que é bastante longo. Caso contrário, que função ele usa?d$foo[i]=mark
é traduzido aproximadamented <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, mas com algum uso de variáveis temporárias.Como Ari mencionou no final de sua resposta, os pacotes
Rcpp
einline
tornam incrivelmente fácil acelerar as coisas. Como exemplo, tente esteinline
código (aviso: não testado):Existe um procedimento semelhante para
#include
ing coisas, onde você apenas passa um parâmetropara cxxfunção, como
include=inc
. O que é realmente legal nisso é que ele faz todos os links e compilações para você, portanto a criação de protótipos é muito rápida.Isenção de responsabilidade: não tenho certeza absoluta de que a classe de tmp deva ser numérica e não matricial numérica ou qualquer outra coisa. Mas tenho quase certeza.
Edit: se você ainda precisar de mais velocidade depois disso, o OpenMP é um recurso de paralelização adequado
C++
. Eu não tentei usá-loinline
, mas deve funcionar. A idéia seria, no caso den
núcleos, fazer com que a iteração de loopk
fosse realizadak % n
. A introdução adequada é encontrado em Matloff é A Arte de R programação , disponível aqui , no capítulo 16, Recorrer a C .fonte
As respostas aqui são ótimas. Um aspecto menor não abordado é que a pergunta declara " Meu PC ainda está funcionando (cerca de 10 horas agora) e não tenho idéia do tempo de execução ". Eu sempre coloco o código a seguir em loops durante o desenvolvimento para ter uma ideia de como as mudanças parecem afetar a velocidade e também para monitorar quanto tempo levará para ser concluído.
Funciona com lapply também.
Se a função no loop for bastante rápida, mas o número de loops for grande, considere apenas imprimir de vez em quando, pois a impressão no próprio console tem uma sobrecarga. por exemplo
fonte
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
já que geralmente estou repetindo coisas nomeadas (com nomesnm
).No R, é possível acelerar o processamento do loop usando as
apply
funções da família (no seu caso, provavelmente seriareplicate
). Dê uma olhada noplyr
pacote que fornece barras de progresso.Outra opção é evitar completamente os loops e substituí-los por aritmética vetorizada. Não sei exatamente o que você está fazendo, mas você provavelmente pode aplicar sua função a todas as linhas de uma vez:
Isso será muito mais rápido e, em seguida, você poderá filtrar as linhas com sua condição:
A aritmética vetorizada requer mais tempo e reflexão sobre o problema, mas às vezes você pode salvar várias ordens de magnitude no tempo de execução.
fonte
Processar com
data.table
é uma opção viável:Se você ignorar os possíveis ganhos com a filtragem de condições, é muito rápido. Obviamente, se você pode fazer o cálculo no subconjunto de dados, isso ajuda.
fonte