Sesija 04: Funkcije i osnove funkcionalnog programiranja u R

Fidbek se upućuje na goran.milovanovic@datakolektiv.com. Ova sveščica prati kurs Uvod u R programiranje za analizu podataka 2020/21.


U Sesiji 04 bavimo se samom suštinom programskog jezika R: funkcijama i funkcionalnim programiranjem. Zahvaljujući razvojima moćnih paketa za manipulaciju podacima poput {dplyr} i {data.table}, R programer se u praksi retko sreće sa pravim funkcionalnim programiranjem - ali ono je neizostavan deo njegovog obrazovanja. Jednostavno postoje situacije u kojima će dan spasti zaista samo primena Map() ili Reduce() - funkcija koje rade sa drugim R funkcijama i omogućavaju elegantne transformacije podataka i izračunavanja izražene u tek ponekoj liniji koda. Da ne pominjemo apply() familiju funkcija bez koje nema pomena ozbiljnog rada u programskom jeziku R.

0. Funkcije u R

Videli smo već pregršt funkcija koje dolaze sa programskim jezikom R. Sada ćemo početi da pišemo naše funkcije: ništa lakše! Evo funkcije koja vraća zbir kvadrata vektora brojeva:

sum_squares <- function(x) {
  
  ss <- sum(x^2)
  return(ss)
  
}

Hajde da je primenimo na neki vektor:

a <- seq(1, 10, by = 1)
print(sum_squares(a))
## [1] 385

Uvođenje dodatnih argumenata: funkcija koja po odluci korisnika računa samo sumu kvadrata parnih brojeva u vektoru:

sum_squares_1 <- function(x, even_only = FALSE) {
  
  if (!even_only) {
   
    ss <- sum(x^2)
    return(ss)
    
  } else {
    
    w <- which(x %% 2 == 0)
    if (length(w) > 0) {
      
      x <- x[w]
      ss <- sum(x^2)
      return(ss)
       
    } else {
      
      message("No even numbers detected while even_only == TRUE")
      return(NULL)
    }
    
  }
  
}

Primena funkcije sum_squares_1():

a <- seq(1, 10, by = 1)
print(sum_squares_1(a, even_only = FALSE))
## [1] 385
a <- seq(1, 10, by = 1)
print(sum_squares_1(a, even_only = TRUE))
## [1] 220
a <- seq(1, 11, by = 2)
print(sum_squares_1(a, even_only = TRUE))
## NULL

Šta se dešava ako prosledimo karakter umesto numeričkog vektora?

a <- c("Belgrade", "New York", "Paris")
print(sum_squares_1(a, even_only = TRUE))

Ali to teško da korisniku naše funkcije nešto znači; zbog toga funkcije pišemo tako da proveravaju vrednosti i tipove svojih argumenata i obaveste korisnika jasno ako pokušava da uradi nešto što funkcija ne predviđa:

sum_squares_2 <- function(x, even_only = FALSE) {
  
  if (!is.numeric(x)) {
    
    message("In sum_squares_2(): x must be numeric.")
    return(NULL)
    
    
  }
  
  if (!even_only) {
   
    ss <- sum(x^2)
    return(ss)
    
  } else {
    
    w <- which(x %% 2 == 0)
    if (length(w) > 0) {
      
      x <- x[w]
      ss <- sum(x^2)
      return(ss)
       
    } else {
      
      message("No even numbers detected while even_only == TRUE")
      return(NULL)
    }
    
  }
  
}

Primena:

a <- c("Belgrade", "New York", "Paris")
print(sum_squares_2(a, even_only = TRUE))
## NULL

1. apply(), lapply(), i sapply()

Napisali smo funkciju sum_squares_2() koja se primenjuje na jedan vektor x. Hoćemo da je primenimo na neki N broj vektora i pokupimo sve sume kvadrata brojeva u njima. Videli smo lapply() već u akciji:

a1 <- seq(1, 20, by = 1)
a2 <- seq(21, 40, by = 1)
a3 <- seq(41, 60, by = 1)
input_list <- list(a1, a2, a3)
result_list <- lapply(input_list, sum_squares_2, even_only = TRUE)
print(result_list)
## [[1]]
## [1] 1540
## 
## [[2]]
## [1] 9940
## 
## [[3]]
## [1] 26340

I rezultat je lista, što je posledica primene lapply(); podsetimo se, ako umesto nje primenimo njenu sestru, funkciju sapply(), rezultat će biti vektor - verovatno mnogo zgodnije za izračunavanja:

a1 <- seq(1, 20, by = 1)
a2 <- seq(21, 40, by = 1)
a3 <- seq(41, 60, by = 1)
input_list <- list(a1, a2, a3)
result_list <- sapply(input_list, sum_squares_2, even_only = TRUE)
print(result_list)
## [1]  1540  9940 26340

Funkcija apply() se koristi na matricama i multidimenzionalnim nizovima u R.

my_matrix <- matrix(1:9, 
                    nrow = 3)
print(my_matrix)
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9

Sume redova u my_matrix matrici uz pomoć apply():

apply(my_matrix, 1, sum)
## [1] 12 15 18

Sume kolona u my_matrix matrici uz pomoć apply():

apply(my_matrix, 2, sum)
## [1]  6 15 24

Naravno, kao jezik specijalizovan za matematičku statistiku u kojoj linearne algebre ima do guše, R ima i već spremne funkcije za sume kolona i redova matrica:

rowSums(my_matrix)
## [1] 12 15 18
colSums(my_matrix)
## [1]  6 15 24

2. Map()

Funkcija Map() se primenjuje na funkcije dva argumenta poput funkcije map_me_fun() u sledećim redovima:

map_me_fun <- function (x, y) {
  
  return(x + y)
  
}

Definišemo sada dva vektora brojeva i prosledimo ih kao argumente R funkciji Map():

a <- c(1, 2, 3, 4, 5)
b <- c(6, 7, 8, 9, 10)

Map(map_me_fun, a, b)
## [[1]]
## [1] 7
## 
## [[2]]
## [1] 9
## 
## [[3]]
## [1] 11
## 
## [[4]]
## [1] 13
## 
## [[5]]
## [1] 15

Ne zaboravite na recycling u R! Primer:

a <- c(1, 2, 3, 4, 5)
b <- c(6, 7, 8, 9)

Map(map_me_fun, a, b)
## Warning in mapply(FUN = f, ..., SIMPLIFY = FALSE): longer argument not a multiple of length of shorter
## [[1]]
## [1] 7
## 
## [[2]]
## [1] 9
## 
## [[3]]
## [1] 11
## 
## [[4]]
## [1] 13
## 
## [[5]]
## [1] 11

3. Reduce()

Funkcija Reduce() radi sledeću stvar: primenjena na funkciju dva argumenta i vektor ili listu, prvo primenjuje datu funkciju na prva dva elementa vektora ili liste, uzima rezultat te primene i sledeći element vektora ili liste, primenjuje na ta dva, uzima rezultat i sledeći element vektora ili liste, primenjuje… i tako do kraja vektora ili liste!

nums <- c(1, 2, 3, 4)
Reduce('^', nums)
## [1] 1

Pa da. Ali:

nums <- c(4, 3, 2, 1)
Reduce('^', nums)
## [1] 4096

Da proverimo?

((4^3)^2)^1
## [1] 4096

To je to :)

R Markdown

R Markdown je ono što koristimo da bismo razvili ove sveščice. Evo knjige iz koje se može naučiti rad u toj jednostavnoj ekstenziji R: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds..


Goran S. Milovanović, Data Scientist & Vlasnik, DataKolektiv.
Kontakt: goran.milovanovic@datakolektiv.com. Ovo je besplatan i slobodan softver: GPL v2.0.