Elemente de programare în R

Funcții

O funcție este un obiect în R care primește câteva obiecte de intrare (care se numesc argumentele funcției) și întoarce un obiect de ieșire. Structura unei funcții va avea următoarele patru părți:

  • Nume: Care este numele funcției? Aveți grijă să nu folosiți nume ale funcțiilor deja existente în R!

  • Argumente: Care sunt datele de intrare pentru funcție? Puteți specifica oricâte date de intrare doriți!

  • Corp sau acțiune: Ce vreți să facă această funcție? Să traseze un grafic? Să calculeze o statistică?

  • Rezultat: Ce vreți să vă întoarcă funcția? Un scalar? Un vector? Un data.frame?

# Structura de baza a unei functii
NUME <- function(ARGUMENTE) {

  ACTIUNI

  return(REZULTAT)

}

Funcțiile în R sunt obiecte de primă clasă (first class objects), ceea ce înseamnă că ele pot fi tratate ca orice alt obiect din R. Este important de reținut că, în R,

  • funcțiile pot fi date ca argumente pentru alte funcții (de exemplu familia de funcții apply())

  • funcțiile pot fi imbricate (nested), cu alte cuvinte puteți crea funcții în interiorul altor funcții

Mai jos avem un exemplu de funcție care nu are niciun argument și nu întoarce nicio valoare:

f <- function() {
        ## Aceasta este o functie goala
}
## Functiile au clasa lor speciala 
class(f)  
[1] "function"
f()       
NULL

Următoarea funcție întoarce numărul de caractere al textului dat ca argument:

f <- function(mesaj){
  chars <- nchar(mesaj)
  chars
}

mes <- f("curs de statistica si probabilitati")
mes
[1] 35

În funcția de mai sus nu am indicat nimic special pentru ca funcția să ne întoarcă numărul de caractere. În R, rezultatul unei funcții este întotdeauna ultima expresie evaluată. De asemenea există funcția return() care poate fi folosită pentru a întoarce o valoare explicită, dar de multe ori această funcție este omisă.

Dacă utilizatorul nu specifică valoarea argumentului mesaj în funcția de mai sus atunci R-ul întoarce o eroare:

f()
Error in f(): argument "mesaj" is missing, with no default

Acest comportament al funcției poate fi modificat prin definirea unei valori implicite (de default). Orice argument al funcției poate avea o valoare de default.

f <- function(mesaj = "Valoare de default"){
  chars <- nchar(mesaj)
  chars
}

# Folosim valoarea implicita 
f()
[1] 18
# Folosim o valoare specificata
f("curs de statistica si probabilitati")
[1] 35

Argumentele funcțiilor în R pot fi potrivite după poziția lor sau după numele lor. Potrivirea după poziție înseamnă că R atribuie prima valoare primului argument, a doua valoare celui de-al doilea argument, etc. De exemplu atunci când folosim funcția rnorm(),

str(rnorm)
function (n, mean = 0, sd = 1)  
set.seed(1234) # pentru repetabilitate
mydata <- rnorm(10, 3, 1) 
mydata
 [1] 1.7929343 3.2774292 4.0844412 0.6543023 3.4291247 3.5060559 2.4252600
 [8] 2.4533681 2.4355480 2.1099622

valoarea 10 este atribuită argumentului n, valoarea 3 argumentului mean iar valoarea 1 argumentului sd, toate prin potrivire după poziție.

Atunci când specificăm argumentele funcției după nume, ordinea acestora nu contează. De exemplu

set.seed(1234)
rnorm(mean = 3, n = 10, sd = 1)
 [1] 1.7929343 3.2774292 4.0844412 0.6543023 3.4291247 3.5060559 2.4252600
 [8] 2.4533681 2.4355480 2.1099622

întoarce același rezultat cu cel obținut mai sus.

De cele mai multe ori, argumentele cu nume sunt folositoare atunci când funcția are un șir lung de argumente și ne dorim să folosim valorile implicite pentru majoritatea dintre ele. De asemenea aceste argumente pot fi folositoare și atunci când știm numele argumentului dar nu și poziția în lista de argumente. Un exemplu de astfel de funcție este funcția plot(), care are multe argumente folosite în special pentru customizare:

args(plot.default)
function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, 
    log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, 
    ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL, 
    panel.last = NULL, asp = NA, xgap.axis = NA, ygap.axis = NA, 
    ...) 
NULL

În R există un argument special notat ..., care indică un număr arbitrar de argumente care sunt atribuite altor funcții din corpul funcției. Acest argument este folosit în special atunci când vrem să extindem o altă funcție și nu vrem să copiem întreaga listă de argumente a acesteia. De exemplu, putem crea o funcție de plotare în care specificăm tipul în prealabil

myplot <- function(x, y, type = "l", ...) {
        plot(x, y, type = type, ...)         ## Atribuie '...' functiei 'plot'
}

Argumentul ... poate fi folosit (și este necesar) și atunci când numărul de argumente pe care îl ia funcția nu este cunoscut în prealabil. De exemplu să considerăm funcțiile paste() și cat()

args(paste)
function (..., sep = " ", collapse = NULL, recycle0 = FALSE) 
NULL
args(cat)
function (..., file = "", sep = " ", fill = FALSE, labels = NULL, 
    append = FALSE) 
NULL

Deoarece ambele funcții afișează text în consolă combinând mai mulți vectori de caractere împreună, este imposibil ca acestea să cunoască în prealabil câți vectori de caractere vor fi dați ca date de intrare de către utilizator, deci primul argument pentru fiecare funcție este ....

Este important de menționat că toate argumentele care apar după argumentul ... trebuie explicitate după nume.

paste("Curs", "Probabilitati si Statistica", sep = ":")
[1] "Curs:Probabilitati si Statistica"

Exerciții

Exercițiul 1 Să presupunem că Ionel este convins că poate prezice cât aur va găsi pe o insulă folosind următoarea ecuație: \(ab - 324c + \log(a)\), unde \(a\) este aria insulei (în \(m^2\)), \(b\) este numărul de copaci de pe insulă iar \(c\) reprezintă cât de obosit este pe o scală de la 1 la 10. Creați o funcție numită Gasit_Aur care primește ca argumente \(a\), \(b\) și \(c\) și întoarce valoare prezisă.

Un exemplu ar fi

Gasit_Aur(a = 1000, b = 30, c = 7)
[1] 27738.91

Exercițiul 2 Precizați care este rezultatul următoarelor funcții:

f1 <- function(d, n, max){
   nums <- seq(from=1, by=d, length.out=n)
   return(nums[nums <= max])
}

f1(4,5,10)
f2 <- function(n,a,b,c,d){
  x <- (1:n)*5
  x <- x[a:b]
  print(x)
  x <- x[-c]
  print(x)
  x <- x[x<d]
  return(x)
}

f2(10,3,7,2,32)
f3 <- function(a,b) {
  a <- a[a<b]
  return(a)
}

f3(3:7,(1:5)^2)

Exercițiul 3 Creați următoarele funcții:

  1. Funcțiile \(f_1\) și \(f_2\) care primesc ca argument vectorul \(x = \left(x_1, x_2, \ldots, x_n\right)\) și returnează vectorul \(\left(x_1, x_2^2, \ldots, x_n^n\right)\) și respectiv \(\left(x_1, \frac{x_2^2}{2}, \ldots, \frac{x_n^n}{n}\right)\)

  2. Funcția \(f_3\) care primește două argumente, un scalar \(x\) și un scalar \(n\) și returnează

\[ 1+\frac{x}{1}+\frac{x^2}{2}+\frac{x^3}{3}+\cdots+\frac{x^n}{n} \]

  1. Funcția \(f_4\) care primește ca argument vectorul \(x = \left(x_1, x_2, \ldots, x_n\right)\) și returnează vectorul sumelor mobile

\[ \frac{x_1+x_2+x_3}{3}, \quad \frac{x_2+x_3+x_4}{3}, \quad \ldots, \quad \frac{x_{n-2}+x_{n-1}+x_n}{3} \]

Exercițiul 4 Presupunem că dintr-o urnă ce conține \(r\) bile roșii și \(a\) bile albastre extragem \(n\) bile fără întoarcere. Scrieți o funcție care calculează probabilitatea ca exact \(k\) dintre bilele extrase să fie de culoare roșie (Indicație: Ce face funcția choose?).

Exercițiul 5 Scrieți o funcție care primește ca argumente două numere \(n\) și \(k\) și returnează matricea pătratică de dimensiune \(n\times n\):

\[ \left[\begin{array}{ccccccc} k & 1 & 0 & 0 & \cdots & 0 & 0 \\ 1 & k & 1 & 0 & \cdots & 0 & 0 \\ 0 & 1 & k & 1 & \cdots & 0 & 0 \\ 0 & 0 & 1 & k & \cdots & 0 & 0 \\ \cdots & \cdots & \cdots & \cdots & \cdots & \cdots & \cdots \\ 0 & 0 & 0 & 0 & \cdots & k & 1 \\ 0 & 0 & 0 & 0 & \cdots & 1 & k \end{array}\right] \]

Exercițiul 6 Fie \(T_n\) suma numerelor de la \(1\) la \(n\). Ne propunem să afișăm toate numerele de la \(1\) la \(T_{1500000}\) care sunt pătrate perfecte. Pentru aceasta se cere să construiți:

  1. Funcția T(n) definită de șirul \(T_n\)

  2. Funcția este.patrat.perfect(x) care verifică dacă x este pătrat perfect

  3. Funcția afisare_patrate_perfecte(n) care afișează numerele \(T_k\), \(1\leq k\leq n\), care sunt pătrate perfecte.

Structuri de control (if-else, switch, etc.)

Structurile de control, în R, permit structurarea logică și controlul fluxului de execuție al unei serii de comenzi. Cele mai folosite structuri de control sunt:

  • if și else: testează o condiție și acționează asupra ei

  • switch: compară mai multe opțiuni și execută opțiunea pentru care avem potrivire

Structura if-else

Structura if-else este una dintre cele mai folosite structuri de control în R permițând testarea unei condiții și acționând în funcție de valoarea de adevăr a acesteia.

Forma de bază a acestei structuri este

if(<conditie>) {
        # executa instructiuni atunci cand conditia este adevarata
} 
else {
        # executa instructiuni atunci cand conditia este falsa
}

dar putem să avem și o serie de teste, de tipul

if(<conditie1>) {
        # executa instructiuni
} else if(<conditie2>)  {
        # executa instructiuni
} else {
        # executa instructiuni
}

Avem următorul exemplu

# Generam un numar uniform in [0,10]
x <- runif(1, 0, 10)  

if(x > 3) {
        y <- 10
} else {
        y <- 0
}

Comanda switch

Comanda switch este folosită cu precădere atunci când avem mai multe alternative dintre care vrem să alegem. Structura generală a aceste comenzi este

switch (Expresie, "Optiune 1", "Optiune 2", "Optiune 3", ....., "Optiune N")

sau într-o manieră extinsă

switch (Expresie,
        "Optiune 1" = Executa aceste expresii atunci cand expresia se 
                      potriveste cu Optiunea 1,
        "Optiune 2" = Executa aceste expresii atunci cand expresia se 
                      potriveste cu Optiunea 2,
        "Optiune 3" = Atunci cand expresia se potriveste cu Optiunea 3, 
                      executa aceste comenzi,
        ....
        "Optiune N" = Atunci cand expresia se potriveste cu Optiunea N, 
                      executa aceste comenzi
)

Considerăm următorul exemplu

number1 <- 30
number2 <- 20
# operator <- readline(prompt="Insereaza OPERATORUL ARITMETIC: ")
 
operator = "*"

switch(operator,
       "+" = print(paste("Suma celor doua numere este: ", 
                         number1 + number2)),
       "-" = print(paste("Diferenta celor doua numere este: ", 
                         number1 - number2)),
       "*" = print(paste("Inmultirea celor doua numere este: ", 
                         number1 * number2)),
       "^" = print(paste("Ridicarea la putere a celor doua numere este: ", 
                         number1 ^ number2)),
       "/" = print(paste("Impartirea celor doua numere este: ", 
                         number1 / number2)),
       "%/%" = print(paste("Catul impartirii celor doua numere este: ", 
                           number1 %/% number2)),
       "%%" = print(paste("Restul impartirii celor doua numere este: ", 
                          number1 %% number2))
)
[1] "Inmultirea celor doua numere este:  600"

Exerciții

Exercițiul 7 Scrieți o funcție f care implementează funcția

\[ f(n)= \begin{cases}7 n+3 & \text { dacă } n \text { este multiplu de } 3 \\ \frac{7 n+2}{3} & \text { dacă } n \text { are restul } 1 \text { la împărțirea cu } 3 \\ \frac{n-2}{3} & \text { dacă } n \text { are restul } 2 \text { la împărțirea cu } 3 \end{cases} \]

Exercițiul 8 Un număr palindromic (pe scurt palindrom) este un număr care se citește la fel atunci când este scris de la stânga la dreapta și de la dreapta la stânga. De exemplu următoarele numere:

\[ 0,1,2,3,4,5,6,7,8,9,11,22,33,44,55,66,77,88,99,101,111,121, \ldots \]

sunt palindroame. Ne propunem să determinăm proporția de numere palindromice între \(1\) și \(100000\):

  1. Creați o funcție cifre(n) care întoarce vectorul cifrelor numărului \(n\), e.g.
cifre(1234)
[1] 1 2 3 4
  1. Creați o funcție rasturnat(v) care să inverseze elementele vectorului v, e.g.
rasturnat(c(1, 3, 5, 4, 2))
[1] 2 4 5 3 1
  1. Creați funcția este.palindrom(n) care returnează valoarea TRUE dacă \(n\) este palindrom și FALSE altfel, e.g.
este.palindrom(123321)
[1] TRUE
este.palindrom(1234)
[1] FALSE

Putem răspunde acum la întrebarea inițială?

Exercițiul 9 Algoritmul lui Zeller este folosit pentru a returna ziua din săptămână corespunzătoare datei introduse. Formula de calcul este

\[ f = \left([2.6 m-0.2]+k+y+\left[\frac{y}{4}\right]+\left[\frac{c}{4}\right]-2 c\right) \bmod 7 \]

unde - \(f\) este ziua cu \(1\) pentru Duminică, \(2\) pentru Luni, etc. - \(k\) este ziua din lună - \(y\) este anul (scris cu două cifre, e.g. \(63\)) - \(c\) este secolul (scris cu două cifre, e.g \(19\)) - \(m\) este luna (unde Ianuarie este luna 11 a anului trecut, Februarie luna 12 a anului trecut iar Martie este luna 1)

Astfel, data de 21/07/1963 are \(m=5\), \(k=21\), \(c=19\), \(y=63\) iar data de 21/2/1963 are \(m=12\), \(k=21\), \(c=19\), \(y=62\). Scrieți funcția zeller(ziua, luna, anul) care să implementeze algoritmul lui Zeller.

Structuri repetitive (for, while, etc.)

Structurile repetitive din R permit controlul fluxului de execuție al unei serii de comenzi, iar cele mai întâlnite sunt:

  • for: execută o acțiune repetitivă de un număr fix de ori

  • while: execută o acțiune repetitivă cât timp o condiție este adevărată

  • repeat: execută o acțiune repetitivă de o infinitate de ori (trebuie să folosim break pentru a ieși din ea)

  • break: întrerupe execuția unei acțiuni repetitive

  • next: sare peste un pas în executarea unei acțiuni repetitive

Bucle for

În R, buclele for iau o variabilă care se iterează și îi atribuie valori succesive dintr-un șir sau un vector. Buclele for au următoarea structură de bază

for (<i> in <vector>) {
        # executa instructiuni
} 

de exemplu

for(i in 1:10) {
        print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10

Următoarele trei bucle prezintă același comportament

x <- c("a", "b", "c", "d")

for(i in 1:4) {
        # Afiseaza fiecare elemnt din 'x'
        print(x[i])  
}
[1] "a"
[1] "b"
[1] "c"
[1] "d"

Funcția seq_along() este des întâlnită atunci când folosim bucle for deoarece crează un șir întreg folosind lungimea obiectului (în acest caz al lui x)

# Genereaza un sir folosind lungimea lui 'x'
for(i in seq_along(x)) {   
        print(x[i])
}
[1] "a"
[1] "b"
[1] "c"
[1] "d"

De asemenea putem folosi chiar pe x ca vector de indexare

for(letter in x) {
        print(letter)
}
[1] "a"
[1] "b"
[1] "c"
[1] "d"

Atunci când folosim comenzile din buclele for pe o singură linie nu este necesară folosirea parantezelor {}

for(i in 1:4) print(x[i])
[1] "a"
[1] "b"
[1] "c"
[1] "d"

Putem folosi buclele for și imbricat (nested)

x <- matrix(1:6, 2, 3)

for(i in seq_len(nrow(x))) {
        for(j in seq_len(ncol(x))) {
                print(x[i, j])
        }   
}

Bucle de tip while

Acțiunile repetitive de tip while încep prin testarea unei condiții și în cazul în care aceasta este adevărată atunci se execută corpul comenzii. Odată ce corpul buclei este executat, condiția este testată din nou până când devine falsă (se poate ca bucla while să rezulte într-o repetiție infinită !). Structura generală a acestei bucle este

while(<conditie>) {
        # executa instructiuni
} 

Considerăm următorul exemplu

count <- 0
while(count < 4) {
        print(count)
        count <- count + 1
}
[1] 0
[1] 1
[1] 2
[1] 3

Uneori putem testa mai multe condiții (acestea sunt întotdeauna evaluate de la stânga la dreapta)

z <- 5
set.seed(123)

while(z >= 3 && z <= 10) {
        ban <- rbinom(1, 1, 0.5) # arunc cu banul
        
        if(ban == 1) {  # random walk
                z <- z + 1
        } else {
                z <- z - 1
        } 
}
print(z)
[1] 11

Bucle de tip repeat

Acest tip de acțiuni repetitive nu sunt foarte des întâlnite, cel puțin în statistică sau analiză de date. O situație în care ar putea să apară este atunci când avem un algoritm iterativ în care căutăm o soluție și nu vrem să oprim algoritmul până când soluția nu este suficient de bună.

x0 <- 1
tol <- 1e-8

repeat {
        x1 <- o_functie_definita()
        
        if(abs(x1 - x0) < tol) {  # este suficient de buna solutia 
                break
        } else {
                x0 <- x1
        } 
}

Comezile break și next

Comanda next este folosită pentru a sării un pas într-o buclă

for(i in 1:10) {
        if(i <= 5) {
                # sari peste primele 5 iteratii
                next                 
        }
  print(i)
        
}
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10

Comanda break este folosită pentru a părăsi o buclă imediat

for(i in 1:10) {
      print(i)

      if(i > 5) {
              # opreste dupa 5 iteratii
              break  
      }     
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6

Calculul radicalului unui număr cu ajutorul buclei de tip repeat și a comenzii break se scrie

# folosind repeat
a <- 12223

x <- a/2
repeat{
  x <- (x + a/x)/2
  if (abs(x^2 - a) < 1e-10) break
}

Exerciții

Exercițiul 10 Care este rezultatul următoarelor funcții:

f1 <-  function(n) {
   x <- c(0,1,2)
   while (length(x) < n) {
        x <- c(x,3)
    }
    return(x)
}

f1(6)
f2 <- function(n) {
   x <- 30
   while (x > 0) {
      x <- x-n
   }
   return(x)
}

f2(7)
f3 <- function(n){
   x <- 1:(2*n)
   while(x[1] < n){
       x <- x[-1]
   }
  return(x)
 }

f3(5)

Exercițiul 11 Reamintiți-vă ce fac funcțiile head și tail. Scrieți o funcție middle care să primească două argumente, x (un vector, matrice sau data.frame) și n un scalar, și care să returneze n valori (rânduri) în jurul mijlocului lui x.

Exercițiul 12 Creați funcția numere_pare(v) care returnează numărul de numere pare din vectorul v. Puteți crea această funcție și fără să folosiți bucle for?

Exercițiul 13 Construiți următoarele matrice de dimensiune \(10 \times 10\): \(M_{i,j} = \frac{1}{\sqrt{|i-j|+1}}\) și \(N_{i,j} = \frac{i}{j^2}\). Puteți construi matricea \(M\) și matricea \(N\) fără a folosi bucle for? (Hint: ce face comanda outer?)

Exercițiul 14 Scrieți un program, folosind bucle de tip while, care să permită calcularea radicalului numărului \(a\in\mathbb{N}\) plecând de la relația de recurență:

\[ 2x_{n+1} = x_n + \frac{a}{x_n},\quad x_1 = \frac{a}{2} \]

Exercițiul 15 Ne propunem să rezolvăm relația de recurență \(x_n=r x_{n-1}\left(1-x_{n-1}\right)\), cu valoarea inițială \(x_1\).

  1. Scrieți o funcție f_sir(x1, r, n) care returnează vectorul \(\left(x_1, \ldots, x_n\right)\), a primilor \(n\) termeni ai șirului.

  2. Afișați grafic termenii șirului (a se vedea secțiunea Elemente de grafică în R) folosind comanda plot.

  3. Scrieți o nouă funcție care determină numărul de iterații (termeni) pentru care \(\left|x_n-x_{n-1}\right|<tol\). Această funcția va avea trei argumente: x1, r și tol.

Exercițiul 16 Șirul lui Fibonacci este definit prin următoarea relație de recurență de ordin \(2\):

\[ F_{n+1}=F_n+F_{n-1} ; \quad n=2,3,4,5, \ldots, \]

unde \(F_1=F_2=1\). Scrieți o funcție în R care întoarce primii \(n\) termeni ai șirului.

Exercițiul 17 Dat fiind eșantionul \(x_1, \ldots, x_n\), coeficientul de autocorelare de rang \(k\) este definit prin

\[ r_k=\frac{\sum_{i=k+1}^n\left(x_i-\bar{x}\right)\left(x_{i-k}-\bar{x}\right)}{\sum_{i=1}^n\left(x_i-\bar{x}\right)^2} \]

Scrieți o funcție care să primească două argumente, vectorul x și rangul k (cu \(1\leq k\leq n-1\)), și să returneze vectorul \(\left(r_0=1, r_1, \ldots, r_k\right)\).

Familia de funcții apply

Pe lângă buclele for și while, în R există și un set de funcții care permit scrierea și rularea într-o manieră mai compactă a codului dar și aplicarea de funcții unor grupuri de date.

  • lapply(): Evaluează o funcție pentru fiecare element al unei liste

  • sapply(): La fel ca lapply numai că încearcă să simplifice rezultatul

  • apply(): Aplică o funcție după fiecare dimensiune a unui array

  • tapply(): Aplică o funcție pe submulțimi ale unui vector

  • mapply(): Varianta multivariată a funcției lapply

  • split: Împarte un vector în grupuri definite de o variabilă de tip factor.

Funcția lapply()

Funcția lapply() efectuează următoarele operații:

  1. buclează după o listă, iterând după fiecare element din acea listă
  2. aplică o funcție fiecărui element al listei (o funcție pe care o specificăm)
  3. întoarce ca rezultat tot o listă (prefixul l vine de la listă).

Această funcție primește următoarele trei argument: (1) o listă X; (2) o funcție FUN; (3) alte argumente via .... Dacă X nu este o listă atunci aceasta va fi transformată într-una folosind comanda as.list().

Considerăm următorul exemplu în care vrem să aplicăm funcția mean() tuturor elementelor unei liste

set.seed(222)
x <- list(a = 1:5, b = rnorm(10), c = rnorm(20, 1), d = rnorm(100, 5))
lapply(x, mean)
$a
[1] 3

$b
[1] 0.1996044

$c
[1] 0.7881026

$d
[1] 5.064188

Putem să folosim funcția lapply() pentru a evalua o funcție în moduri repetate. Mai jos avem un exemplu în care folosim funcția runif() (permite generarea observațiilor uniform repartizate) de patru ori, de fiecare dată generăm un număr diferit de valori aleatoare. Mai mult, argumentele \(min=0\) și \(max=3\) sunt atribuite, prin intermediul argumentului ..., funcției runif.

x <- 1:4
lapply(x, runif, min = 0, max = 3)
[[1]]
[1] 0.03443616

[[2]]
[1] 1.267361 1.365441

[[3]]
[1] 1.8084700 2.1902665 0.4139585

[[4]]
[1] 1.5924650 0.7355067 2.1483841 1.6082945

Funcția sapply()

Funcția sapply() are un comportament similar cu lapply() prin faptul că funcția sapply() apelează intern lapply() pentru valorile de input, după care evaluează:

  • dacă rezultatul este o listă în care fiecare element este de lungime 1, atunci întoarce un vector

  • dacă rezultatul este o listă în care fiecare element este un vector de aceeași lungime (>1), se întoarce o matrice

  • în caz contrar se întoarce o listă.

Considerăm exemplul de mai sus

set.seed(222)
x <- list(a = 1:4, b = rnorm(10), c = rnorm(20, 1), d = rnorm(100, 5))
sapply(x, mean)
        a         b         c         d 
2.5000000 0.1996044 0.7881026 5.0641876 

Funcția split()

Funcția split() primește ca argument un vector sau o listă (sau un data.frame) și împarte datele în grupuri determinate de o variabilă de tip factor (sau o listă de factor).

Argumentele aceste funcții sunt

str(split)
function (x, f, drop = FALSE, ...)  

unde

  • x este un vector, o listă sau un data.frame
  • f este un factor sau o listă de factori

Considerăm următorul exemplu în care generăm un vector de date și îl împărțim după o variabilă de tip factor creată cu ajutorul funcției gl() (generate levels, după cum am văzut în secțiunea Factori).

x <- c(rnorm(10), runif(10), rnorm(10, 1))
f <- gl(3, 10)
split(x, f)
$`1`
 [1] -2.27414224 -0.11266780  0.61308167  0.07733545  0.57137727  0.11672493
 [7] -0.95685256 -1.90008460 -1.48972089  0.55925676

$`2`
 [1] 0.91159086 0.03291829 0.78368939 0.11852882 0.64443831 0.78790988
 [7] 0.82451477 0.05642366 0.65075027 0.95426854

$`3`
 [1]  2.6666242  2.6634334  1.8106280 -0.7837308  1.6575684  0.1546575
 [7]  0.4930056 -0.9031544  2.4042311  1.4106863

Putem folosi funcția split și în conjuncție cu funcția lapply (atunci când vrem să aplicăm o funcție FUN pe grupuri de date).

lapply(split(x, f), mean)
$`1`
[1] -0.4795692

$`2`
[1] 0.5765033

$`3`
[1] 1.157395

Funcția tapply()

Funcția tapply() este folosită pentru aplicarea unei funcții FUN pe submulțimile unui vector și poate fi văzută ca o combinație între split() și sapply(), dar doar pentru vectori.

str(tapply)
function (X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)  

Argumentele acestei funcții sunt date de următorul tabel:

Tabelul 1: Argumentele funcției tapply().
Argument Descriere
X un vector
INDEX este o variabilă de tip factor sau o listă de factori
FUN o funcție ce urmează să fie aplicată
... argumente ce vor fi atribuite funcției FUN
simplify dacă vrem să simplificăm rezultatul

Următorul exemplu calculează media după fiecare grupă determinată de o variabilă de tip factor a unui vector numeric.

x <- c(rnorm(10), runif(10), rnorm(10, 1))
f <- gl(3, 10)   
f
 [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
Levels: 1 2 3
tapply(x, f, mean)
            1             2             3 
-0.0007774025  0.3736457792  0.5789436983 

Putem să aplicăm și funcții care întorc mai mult de un rezultat. În această situație rezultatul nu poate fi simplificat:

tapply(x, f, range)
$`1`
[1] -2.1904113  0.9249901

$`2`
[1] 0.004445296 0.998309704

$`3`
[1] -0.3379675  1.9327099

Funcția apply()

Funcția apply() este folosită cu precădere pentru a aplica o funcție liniilor și coloanelor unei matrice (care este un array bidimensional). Cu toate acestea poate fi folosită pe tablouri multidimensionale (array) în general. Folosirea funcției apply() nu este mai rapidă decât scrierea unei bucle for, dar este mai compactă.

str(apply)
function (X, MARGIN, FUN, ..., simplify = TRUE)  

Argumentele funcției apply() sunt

  • X un tablou multidimensional
  • MARGIN este un vector numeric care indică dimensiunea sau dimensiunile după care se va aplica funcția
  • FUN este o funcție ce urmează să fie aplicată
  • ... alte argumente pentru funcțiaFUN

Considerăm următorul exemplu în care calculăm media pe coloane într-o matrice

x <- matrix(rnorm(200), 20, 10)
apply(x, 2, mean)  # media fiecarei coloane
 [1]  3.745002e-02  1.857656e-01 -2.413659e-01 -2.093141e-01 -2.562272e-01
 [6]  8.986712e-05  7.444137e-02 -7.460941e-03  6.275282e-02  9.801550e-02

precum și media după fiecare linie

apply(x, 1, sum)   # media fiecarei linii
 [1]  2.76179139  2.53107681  0.87923177  1.80480589  0.98225832 -3.06148753
 [7] -1.40358820 -0.65969812 -1.63717046 -0.29330726 -2.41486442 -3.15698523
[13]  2.27126822 -3.88290287 -3.15595194  5.41211963  2.32985530 -3.05330574
[19] -0.02110926 -1.34909559

Exerciții

Exercițiul 18 Fie vectorii \(x = \left(x_1, \ldots, x_n\right)\) și \(y = \left(y_1, \ldots, y_m\right)\). Definim vectorul \(z = \left(z_1, \ldots, z_n\right)\) prin

\[ z_k=\sum_{j=1}^m \mathbf{1}_\left\{y_j<x_k\right\} \quad \text { pentru } k=1,2, \ldots, n \]

  1. Folosind funcția outer, creați o funcție care primește ca argumente pe \(x\) și \(y\) și returnează vectorul \(z\)

  2. Creați aceeași funcție de la punctul a) folosind funcția sapply în loc de outer

  3. Repetați cerința folosind acum vapply în loc de sapply sau de outer

  4. Verificați care dintre cele trei funcții create este mai eficientă în termeni de rapiditate de execuție (Indicație: folosiți funcția system.time pentru a măsura timpul de execuție)

Exercițiul 19 Fie \(A\) o matrice care conține elemente de tip NA. Se cere:

  1. Să se construiască o funcție care extrage submatricea care conține toate coloanele din \(A\) ce nu au elemente de tip NA

  2. Să se construiască o funcție care extrage submatricea care este formată prin ștergerea fiecărei linii și coloane din \(A\) care conține elemente de tip NA.

Exercițiul 20 Setul de date airquality prezintă măsurători zilnice ale nivelului de ozon (Ozone), radiații solare (Solar.R), viteza vântului (Wind) și temperatură (Temp) din New York între lunile Mai-Septembrie 1973. Determinați:

  1. Temperatura medie din fiecare lună

  2. Nivelul mediu de radiații solare din ziua 15 a lunilor considerate

Exercițiul 21 Ne propunem să construim mai multe funcții care evaluează funcția

\[ f(n)=\sum_{i=1}^n \sum_{s=1}^r \frac{s^2}{10+4 r^3} \]

și să verificăm care sunt mai eficiente.

  1. Creați o funcție care evaluează \(f(n)\) folosind doar bucle for

  2. Creați o funcție care evaluează \(f(n)\) folosind funcțiile row și col pentru a construi o matrice a cărei sumă a elementelor să conducă la rezultat

  3. Creați o funcție care evaluează \(f(n)\) folosind funcția outer

  4. Creați o funcție care evaluează \(f(n)\) folosind funcția sapply (sau unlist și apply)