
Zjednodušení / zrychlení for cyklu
Pokud máme velmi objemná data, někdy se vyplatí namísto for cyklu použít funkci apply() (nebo jinou z této rodiny)
Mějme matici ma:
> ma <- matrix(rep(1:3,each=2),3,2)
> ma
[,1] [,2]
[1,] 1 2
[2,] 1 3
[3,] 2 3
Funkce apply() aplikuje námi zvolenou funkci na řádky/sloupce matice.
> apply(ma, 1, mean) # spočítá průměr v jednotlivých řádcích
[1] 1.5 2.0 2.5
> apply(ma, 2, mean) # spočítá průměr v jednotlivých sloupcích
[1] 1.333333 2.666667
Oproti tomu funkce tapply() aplikuje funkci na vektor na základě faktoru.
> vektor = ma[,2]
> skupina = c("A", "A", "B")
> tapply(vektor,skupina,FUN=function(x) mean(x))
A B
2.5 3.0
Demonstrujme si rozdíl mezi rychlostí for cyklu, funkce apply() a funkce rowsums() na příkladu výpočtu průměru v řádcích matice X. Pro výpočet času použijeme funkci system.time():
Definujeme si matici o 10 000 řádcích a 100 sloupcích s náhodně generovanými hodnotami normálního rozdělení:
> X <- matrix(rnorm(1000000),10000,100)
Použijeme for cyklus:
> system.time({mean.for = c();
for (i in c(1:nrow(X)))
{
mean.temp <- mean(X[i,])
mean.for <- c(mean.for,mean.temp)
}
})
user system elapsed
0.000 0.000 0.651
Použijeme funkci apply():
> system.time(mean.apply <- apply(X,1,mean))
user system elapsed
0.000 0.000 0.094
A nakonec funkci rowSums():
> system.time(a<-rowMeans(X))
user system elapsed
0.000 0.000 0.003
Další optimalizace for cyklu se dá dosáhnout inicializací nových objektů v plné délce ještě před spuštěním samotného cyklu.
Demonstrujeme na příkladu třech funkcí, ve kterých se v cyklu pro hodnoty i=1,..,n v každém kroku přiřadí i-tému elementu vektoru a hodnota i^2 (tedy druhá mocnina i).
V první funkci není vektor a inicializován v plné délce n , vektor a je v každém cyklu prodlužován:
> funkce1<-function(n){
a <- NULL
for (i in 1:n) {a[i]<-i^2}
return(a)
}
> system.time(funkce1(n=100000))
user system elapsed
4.728 1.461 6.301
Ve druhé funkci vektor a inicializujeme v plné délce n:
> funkce2<-function(n){
a <- numeric(n)
for (i in 1:n) {a[i]<-i^2}
return(a)
}
> system.time(funkce2(n=100000))
user system elapsed
0.105 0.000 0.103
Rozdíl v čase výpočtu je několikanásobně nižší u funkce2. To proto, že ve funkci funkce1 dochází v každé iteraeci k alokování paměti a kopírování objektu a, co podstatně snižuje rychlost výpočtu.
Nakonec třetí funkce pro tento výpočet vůbec for cyklus nepoužívá a je také nejrychlejší:
> funkce3<-function(n){
a <- c(1:n)^2
return(a)
}
> system.time(funkce3(n=100000))
user system elapsed
0.0000000000 0.0000000000 0.0009999999
Zamyslete se proto nad tím, je-li for cyklus nejlepším řešením z hlediska výkonnosti i přehlednosti Vašeho kódu.