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