4 *apply
funkcije
Upoznacemo se u ovom poglavlju sa *apply
familijom funkcija, i to: apply
, sapply
i lapply
funkcijama, koje cine osnovu funkcionalnog programiranja u R-u. U pitanju su funkcije koje sluze za preslikavanje elemenata vektora/matrica/listi u nove vektore/matrice/liste primenom jedne iste funkcije na svaki element.
Pre svega, napomenuo bih da lep tutorijal sa lepim slikama koje ilustruju ove funkcije ima na https://www.datacamp.com/community/tutorials/r-tutorial-apply-family
4.1 Funkcija sapply
Najjednostavnija od apply
funkcija, funkcija sapply
se poziva ovako:
sapply(v, f)
, gde je v
vektor/lista/matrica, a f
funkcija koju treba primeniti na svaki element od v
. Dobijamo preslikavanje:
\[v = (v_1, v_2, \dots, v_n)\stackrel{sapply}{\rightarrow} (f(v_1), f(v_2), \dots, f(v_n)).\]
Dakle, sapply
primenjuje datu funkciju na svaki element datog vektora/liste/matrice i vraca rezultujuci vektor. Treba napomenuti da uvek pokusava da kao rezultat vrati vektor, a ukoliko to nije moguce, vraca listu.
4.1.1 Par kratkih primera
Prost primer za sapply
je kvadriranje svakog clana vektora
## [1] 1 4 9 16 25
Ovaj kod je ekvivalentan sa x^2
.
Funkcija sapply
ne mora da prima vektor kao argument vec moze primiti i listu. Kao izlaznu vrednosti opet ce dati vektor. Evo primera ako zelimo da za neku listu matrica zelimo da izracunamo sumu svih vrednosti u matrici.
matrix_list <- list(diag(5), diag(7), matrix(1:10, nrow=2))
# na svaku matricu iz matrix_list primenimo funkciju sum
matrix_sums <- sapply(matrix_list, sum)
matrix_sums
## [1] 5 7 55
Ovo je naravno isto sto i
## [1] 5 7 55
Ukoliko se primenjuje visedimenziona funkcija, sapply
moze vratiti i matricu. Na primer:
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 1 4 9 16 25
Svaka kolona je jedan rezultat primenjene funkcije. Vidimo da je prva vrsta k
, a druga vrsta k^2
.
4.1.2 Lepsi generator slucjanih velicina
Kao bolji primer koriscenja sapply
vraticemo se na nas generator metodom inverzne transformacije.
generator <- function(n, cdf, support) {
# inicijalizujemo uzorak kao prazan vektor od n elemenata
sample <- numeric(n)
# generisemo n brojeva iz uniformne raspodele
u <- runif(n)
# za svaki od brojeva u_i primenimo inverz funkcije raspodele
for (i in 1:n) {
sample[i] <- inv(cdf, u[i], support)
}
# vracamo uzorak
return(sample)
}
Vidimo da je ceo kod u generatoru zasnovan na tome da generisemo \(n\) brojeva iz uniformne raspodele, pa na svaki od njih primenimo funkciju function(u) inv(cdf, u, support)
(u[i]
zamenjeno sa u
). Ovo je bas ono sto ocekujemo od sapply
funkcije! Stoga promenimo kod generatora da to iskoristi.
generator <- function(n, cdf, support) {
# generisemo n brojeva iz uniformne raspodele
u <- runif(n)
# na svaki element iz u primenimo inverznu transformaciju
sample <- sapply(u, function(u) inv(cdf, u, support))
return(sample)
}
To je ceo kod! Sveli smo kod prakticno na 2 reda, jer smo izbacili tehnicke stvari poput inicijalizacije vektora koji vracamo, prolazenja kroz petlju i popunjavanja rezultujuceg vektora clan po clan. Ostao je kod koji sadrzi samo sustinu onoga sto hocemo da uradimo:
- uzmemo uzorak iz uniformne raspodele (
u <- runif(n)
) - Svaki element uzorka transformisemo inverznom transformacijom (
sapply(u, function(u) inv(cdf, u, support))
)
Nista vise od toga nije trazeno u matematickom zapisu metoda, pa nema potrebe ni da komplikujemo kod.
Upravo to je (pored brzine i nekih tehnickih stvari poput bolje otpornosti na greske od petlji) glavna prednost *apply
funkcija naspram petlji – kod cine mnogo citljivijim (nakon sto je korisnik upoznat sa ovim funkcijama, naravno).
4.2 Funkcija lapply
Funkcija lapply
se koristi na isti nacin kao i sapply
, s tim sto kao rezultat vraca listu, a ne vektor. Dakle, ukoliko ne ocekujemo rezultat da bude jednog te istog tipa i dimenzije, vec zelimo da izlaz bude lista, onda koristimo lapply
.
Na primer, ako zelimo za neki vektor brojeva napravimo listu dijagonalnih matrica odgovarajuce dimenzije, to mozemo da uradimo sa
## [[1]]
## [,1]
## [1,] 1
##
## [[2]]
## [,1] [,2] [,3]
## [1,] 1 0 0
## [2,] 0 1 0
## [3,] 0 0 1
##
## [[3]]
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 0 0 0 0
## [2,] 0 1 0 0 0
## [3,] 0 0 1 0 0
## [4,] 0 0 0 1 0
## [5,] 0 0 0 0 1
Dobili smo listu dijagonalnih matrica dimenzija 1, 3 i 5.
Da smo koristili petlje, ovaj kod bi bio dosta nezgrapniji:
## [[1]]
## [,1]
## [1,] 1
##
## [[2]]
## [,1] [,2] [,3]
## [1,] 1 0 0
## [2,] 0 1 0
## [3,] 0 0 1
##
## [[3]]
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 0 0 0 0
## [2,] 0 1 0 0 0
## [3,] 0 0 1 0 0
## [4,] 0 0 0 1 0
## [5,] 0 0 0 0 1
Moramo da inicijalizujemo listu, da vodimo racuna o brojacu i da element po element dopunjujemo rezultujucu listu.
4.3 Funkcija apply
Funkcij apply
je funkcija koja se koristi kada imamo visedimenzioni ulaz, poput matrice ili dataframe-a, a zelimo da primenimo neku funkciju po kolonama ili po vrstama (ili nekoj trecoj dimenziji ukoliko postoji).
Funkcija se poziva kao apply(mat, dim, f)
, gde je m
matrica/dataframe, dim
je dimenzija po kojoj primenjujemo funkciju (1 je za vrste, 2 je za kolone), a f
je funkcija koju da primenimo na svaku vrstu/kolonu.
Primenom apply
dobijamo preslikavanje (po vrstama - dim = 1
)
\[
\begin{pmatrix}1 & 3 & 5\\ 2 & 4 & 6\end{pmatrix} \stackrel{apply(\cdot, 1, f)}{\longrightarrow} \begin{pmatrix}f(1, 3, 5)\\ f(2, 4, 6)\end{pmatrix}
\]
ili po kolonama (dim = 2
)
\[
\begin{pmatrix}1 & 3 & 5\\ 2 & 4 & 6\end{pmatrix} \stackrel{apply(\cdot, 2, f)}{\longrightarrow} \left(f\binom12, f\binom34, f\binom56\right)
\]
Na primer, moze se odrediti zbir elemenata u svakoj koloni neke matrice.
## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12
## [1] 6 15 24 33
Ovo je ekvivalentno pozivu
## [1] 6 15 24 33
Ako zelimo prosek po vrstama, mozemo pozvati
## [1] 5.5 6.5 7.5
Sto je isto kao i
## [1] 5.5 6.5 7.5
Rezultat ne mora biti vektor, mozemo vratiti rezultat poziva summary
na svaku kolonu dataframe-a i dobijamo matricu.
## speed dist
## 1 4 2
## 2 4 10
## 3 7 4
## 4 7 22
## 5 8 16
## 6 9 10
## 7 10 18
## 8 10 26
## 9 10 34
## 10 11 17
## 11 11 28
## 12 12 14
## 13 12 20
## 14 12 24
## 15 12 28
## 16 13 26
## 17 13 34
## 18 13 34
## 19 13 46
## 20 14 26
## 21 14 36
## 22 14 60
## 23 14 80
## 24 15 20
## 25 15 26
## 26 15 54
## 27 16 32
## 28 16 40
## 29 17 32
## 30 17 40
## 31 17 50
## 32 18 42
## 33 18 56
## 34 18 76
## 35 18 84
## 36 19 36
## 37 19 46
## 38 19 68
## 39 20 32
## 40 20 48
## 41 20 52
## 42 20 56
## 43 20 64
## 44 22 66
## 45 23 54
## 46 24 70
## 47 24 92
## 48 24 93
## 49 24 120
## 50 25 85
## speed dist
## Min. 4.0 2.00
## 1st Qu. 12.0 26.00
## Median 15.0 36.00
## Mean 15.4 42.98
## 3rd Qu. 19.0 56.00
## Max. 25.0 120.00
Rezultat je matrica u kojoj svaka kolona sadrzi rezultat poziva summary
na odgovarajucu kolonu iz skupa podataka cars
(kolone su speed
i dist
)
4.3.1 Standardizacija kolona dataframe-a
Cesto je neophodno standradizovati kolone dataframe-a, primenom transformacije \(\frac{X-m}{\sigma}\), da bismo dobili promenljivu sa ocekivanjem \(0\) i disperzijom \(1\). To mozemo lako odraditi funkcijom apply
.
Na primer, standardizovacemo kolone iz cars
skupa podataka.
## speed dist
## [1,] -2.15596948 -1.59025960
## [2,] -2.15596948 -1.27981361
## [3,] -1.58860909 -1.51264810
## [4,] -1.58860909 -0.81414462
## [5,] -1.39948896 -1.04697911
## [6,] -1.21036883 -1.27981361
## [7,] -1.02124870 -0.96936762
## [8,] -1.02124870 -0.65892162
## [9,] -1.02124870 -0.34847563
## [10,] -0.83212857 -1.00817336
## [11,] -0.83212857 -0.58131012
## [12,] -0.64300844 -1.12459061
## [13,] -0.64300844 -0.89175612
## [14,] -0.64300844 -0.73653312
## [15,] -0.64300844 -0.58131012
## [16,] -0.45388831 -0.65892162
## [17,] -0.45388831 -0.34847563
## [18,] -0.45388831 -0.34847563
## [19,] -0.45388831 0.11719336
## [20,] -0.26476818 -0.65892162
## [21,] -0.26476818 -0.27086413
## [22,] -0.26476818 0.66047385
## [23,] -0.26476818 1.43658884
## [24,] -0.07564805 -0.89175612
## [25,] -0.07564805 -0.65892162
## [26,] -0.07564805 0.42763936
## [27,] 0.11347208 -0.42608713
## [28,] 0.11347208 -0.11564113
## [29,] 0.30259221 -0.42608713
## [30,] 0.30259221 -0.11564113
## [31,] 0.30259221 0.27241636
## [32,] 0.49171234 -0.03802963
## [33,] 0.49171234 0.50525085
## [34,] 0.49171234 1.28136584
## [35,] 0.49171234 1.59181183
## [36,] 0.68083247 -0.27086413
## [37,] 0.68083247 0.11719336
## [38,] 0.68083247 0.97091984
## [39,] 0.86995260 -0.42608713
## [40,] 0.86995260 0.19480486
## [41,] 0.86995260 0.35002786
## [42,] 0.86995260 0.50525085
## [43,] 0.86995260 0.81569685
## [44,] 1.24819285 0.89330835
## [45,] 1.43731298 0.42763936
## [46,] 1.62643311 1.04853134
## [47,] 1.62643311 1.90225783
## [48,] 1.62643311 1.94106357
## [49,] 1.62643311 2.98881880
## [50,] 1.81555324 1.63061758
Izracunajmo prosek i disperziju originalnih i skaliranih podataka (zaokruzeno na 2 decimale):
## speed dist
## [1,] 15.40 42.98
## [2,] 27.96 664.06
## speed dist
## [1,] 0 0
## [2,] 1 1
Dakle, ocekivano, skalirani podaci su takvi da su prosek i standardna devijacija svake od kolona, redom, 0 i 1.