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.

## [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.

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.

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:

  1. uzmemo uzorak iz uniformne raspodele (u <- runif(n))
  2. 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.