Tipuri și structuri de date

R are cinci tipuri de date principale (atomi), după cum urmează:

  • character: "a", "swc"
  • numeric: 2, 15.5
  • integer: 2L (sufix-ul L îi spune R-ului să stocheze numărul ca pe un întreg)
  • logical: TRUE, FALSE
  • complex: 1+4i (numere complexe)

R pune la dispoziție mai multe funcții cu ajutorul cărora se pot examina trăsăturile vectorilor sau a altor obiecte, cum ar fi de exemplu

  • class() - ce tip de obiect este
  • typeof() - care este tipul de date al obiectului (similar cu mode())
  • length() - care este lungimea obiectului
  • attributes() - care sunt atributele obiectului (metadata)
# Exemplu
x <- "curs de statistica"
typeof(x)
[1] "character"
attributes(x)
NULL
y <- 1:10
y
 [1]  1  2  3  4  5  6  7  8  9 10
typeof(y)
[1] "integer"
length(y)
[1] 10
z <- as.numeric(y)
z
 [1]  1  2  3  4  5  6  7  8  9 10
typeof(z)
[1] "double"

În plus, putem să testăm dacă un obiect x aparține unui anumit tip de date prin apelarea funcțiilor de forma is.xxx unde xxx este tipul de date (character, numeric, integer, logical și complex):

x <- "statistica"

is.numeric(x)
[1] FALSE
is.character(x)
[1] TRUE

Mai mult, R permite conversia între diferite tipuri de date prin folosirea funcțiilor de forma as.xxx unde xxx este tipul de date:

as.numeric("1234")
[1] 1234
as.complex("2")
[1] 2+0i
as.logical("TRUE")
[1] TRUE
as.character(3.0e10)
[1] "3e+10"
as.logical(5)
[1] TRUE
as.logical(0)
[1] FALSE
as.logical(-1)
[1] TRUE

Tabelul 1 de mai jos sumarizează tipurile de conversie:

Tabelul 1: Exemple de conversie a tipurilor de date.
De la tipul La tipul Funcția Conversie
logical numeric as.numeric FALSE \(\to\) 0
TRUE \(\to\) 1
logical character as.character FALSE \(\to\) “FALSE”
TRUE \(\to\) “TRUE”
character numeric as.numeric “123” \(\to\) 123
“stat” \(\to\) NA
character logical as.logical “FALSE” \(\to\) FALSE
“TRUE” \(\to\) TRUE
“alte caractere” \(\to\) NA
numeric logical as.logical 0 \(\to\) FALSE
alte numere \(\to\) TRUE
numeric character as.character 123 \(\to\) “123”

În limbajul R regăsim mai multe structuri de date. Printre acestea enumerăm

  • vectori (structuri atomice)
  • matrice și array
  • liste
  • data frame-uri
  • factori

În cele ce urmează vom prezenta fiecare structură de date în parte precizând moduri de construcție, operații și metode de indexare.

Scalari și vectori

Cel mai de bază tip de obiect în R este vectorul. Una dintre regulile principale ale vectorilor este că aceștia pot conține numai obiecte de același tip, cu alte cuvinte putem avea doar vectori de tip caracter, numeric, logic, etc.. În cazul în care încercăm să combinăm diferite tipuri de date, acestea vor fi forțate la tipul cel mai flexibil (a se vedea Tabelul 1). Tipurile de la cel mai puțin la cele mai flexibile sunt: logice, întregi, numerice și caractere.

Metode de construcție a vectorilor

Putem crea vectori fără elemente (empty) cu ajutorul funcției vector(), modul default este logical dar acesta se poate schimba în funcție de necesitate.

vector() # vector logic gol
logical(0)
vector("character", length = 5) # vector de caractere cu 5 elemente
[1] "" "" "" "" ""
character(5) # acelasi lucru dar in mod direct
[1] "" "" "" "" ""
numeric(5)   # vector numeric cu 5 elemente
[1] 0 0 0 0 0
logical(5)   # vector logic cu 5 elemente
[1] FALSE FALSE FALSE FALSE FALSE

Putem crea vectori specificând în mod direct conținutul acestora. Pentru aceasta folosim funcția c() de concatenare:

x <- c(0.5, 0.6)       ## numeric
x <- c(TRUE, FALSE)    ## logical
x <- c(T, F)           ## logical
x <- c("a", "b", "c")  ## character
x <- 9:29              ## integer
x <- c(1+0i, 2+4i)     ## complex

Funcția poate fi folosită de asemenea și pentru (combinarea) adăugarea de elemente la un vector

z <- c("Sandra", "Traian", "Ionel")
z <- c(z, "Ana")
z
[1] "Sandra" "Traian" "Ionel"  "Ana"   
z <- c("George", z)
z
[1] "George" "Sandra" "Traian" "Ionel"  "Ana"   

O altă funcție des folosită în crearea vectorilor, în special a celor care au repetiții, este funcția rep(). Pentru a vedea documentația acestei funcții apelați help(rep). De exemplu, pentru a crea un vector de lungime 5 cu elemente de 0 este suficient să scriem

rep(0, 5)
[1] 0 0 0 0 0

Dacă în plus vrem să creăm vectorul 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 sau 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3 atunci putem scrie

rep(c(1,2,3), 5)
 [1] 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
rep(c(1,2,3), each = 5)
 [1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3

Ce se întâmplă dacă apelăm rep(c(1,2,3), 1:3) ? Vom obține vectorul 1, 2, 2, 3, 3, 3 unde primul element din vectorul c(1,2,3) a fost repetat o dată, al doilea de două ori și al treilea de trei ori

rep(c(1,2,3), 1:3)
[1] 1 2 2 3 3 3

În cazul în care vrem să creăm un vector care are elementele egal depărtate între ele, de exemplu 1.3, 2.3, 3.3, 4.3, 5.3, atunci putem folosi funcția seq():

seq(1, 10, by = 1)
 [1]  1  2  3  4  5  6  7  8  9 10
1:10 # acelasi rezultat
 [1]  1  2  3  4  5  6  7  8  9 10
seq(25, 1, by = -1)
 [1] 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1
25:1 # acelasi rezultat
 [1] 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1
seq(1, 10, length.out = 15)
 [1]  1.000000  1.642857  2.285714  2.928571  3.571429  4.214286  4.857143
 [8]  5.500000  6.142857  6.785714  7.428571  8.071429  8.714286  9.357143
[15] 10.000000

Tabelul 2 sumarizează funcțiile de creare a vectorilor:

Tabelul 2: Funcții utile pentru crearea unui vector.
Funcție Exemplu Rezultat
c(a, b, ...) c(1, 5, 9) 1, 5, 9
a:b 1:5 1, 2, 3, 4, 5
seq(from, to, by, length.out) seq(from = 0, to = 6, by = 2) 0, 2, 4, 6
rep(x, times, each, length.out) rep(c(7, 8), times = 2, each = 2) 7, 7, 8, 8, 7, 7, 8, 8

Operații cu vectori

Operațiile elementare (binare) pe care le puteam face cu scalari (adunarea +, scăderea -, înmulțirea *, împărțirea /, ridicarea la putere ^, etc.) putem să le facem și cu vectori (între vectori sau între vectori și scalari):

a <- 1:10

b <- 5

a + b
 [1]  6  7  8  9 10 11 12 13 14 15
a - b
 [1] -4 -3 -2 -1  0  1  2  3  4  5
a * b
 [1]  5 10 15 20 25 30 35 40 45 50
a / b
 [1] 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0
a ^ b
 [1]      1     32    243   1024   3125   7776  16807  32768  59049 100000
a %/% b
 [1] 0 0 0 0 1 1 1 1 1 2
a %% b
 [1] 1 2 3 4 0 1 2 3 4 0

Observăm că atunci când facem o operație între un scalar și un vector, se aplică scalarul la fiecare element al vectorului. În cazul în care aplicăm o operație la doi vectori de aceeași lungime, elementele vectorului rezultat se obțin prin aplicarea operației între elementele de pe pozițiile corespunzătoare ale celor doi vectori:

# vectori de aceeasi lungime
a <- 1:5
b <- c(2, 2, 1, 3, 4)

a + b
[1] 3 4 4 7 9
a - b
[1] -1  0  2  1  1
a * b
[1]  2  4  3 12 20
a / b
[1] 0.500000 1.000000 3.000000 1.333333 1.250000
a ^ b
[1]   1   4   3  64 625
a %/% b
[1] 0 1 3 1 1
a %% b
[1] 1 0 0 1 1

Dacă efectuăm o operație binară între doi vectori de lungimi diferite, R va recicla (completa prin repetiție) în mod automat vectorul de lungime mai mică până când se obține un vector de aceeași lungime cu cel mai mare după care se va efectua operația.

# vectori cu lungimi diferite
a <- 1:6
b <- c(2, 2, 1)

a + b
[1] 3 4 4 6 7 7
a + c(2, 2, 1, 2, 2, 1)
[1] 3 4 4 6 7 7
a <- 1:5
b <- c(2, 2, 1)

a + b
Warning in a + b: longer object length is not a multiple of shorter object
length
[1] 3 4 4 6 7
a + c(2, 2, 1, 2, 2)
[1] 3 4 4 6 7

Funcțiile elementare, exp(), log(), sin(), cos(), tan(), asin(), acos(), atan(), etc. sunt funcții vectoriale în R, prin urmare pot fi aplicate unor vectori.

x = seq(0, 2*pi, length.out = 10)

exp(x)
 [1]   1.000000   2.009994   4.040076   8.120527  16.322211  32.807544
 [7]  65.942965 132.544960 266.414564 535.491656
sin(x)
 [1]  0.000000e+00  6.427876e-01  9.848078e-01  8.660254e-01  3.420201e-01
 [6] -3.420201e-01 -8.660254e-01 -9.848078e-01 -6.427876e-01 -2.449213e-16
tan(x)
 [1]  0.000000e+00  8.390996e-01  5.671282e+00 -1.732051e+00 -3.639702e-01
 [6]  3.639702e-01  1.732051e+00 -5.671282e+00 -8.390996e-01 -2.449294e-16
atan(x)
 [1] 0.0000000 0.6094710 0.9492822 1.1253388 1.2269250 1.2917899 1.3364502
 [8] 1.3689550 1.3936244 1.4129651

Alte funcții utile des întâlnite în manipularea vectorilor numerici sunt: min(), max(), sum(), prod(), mean(), sd(), length(), round(), ceiling(), floor(), %% (operația modulo), %/% (div), table(), unique(). Pentru mai multe informații privind modul lor de întrebuințare apelați help(nume_functie) sau ?nume_functie.

length(x)
[1] 10
min(x)
[1] 0
sum(x)
[1] 31.41593
mean(x)
[1] 3.141593
round(x, digits = 4)
 [1] 0.0000 0.6981 1.3963 2.0944 2.7925 3.4907 4.1888 4.8869 5.5851 6.2832
y =  c("M", "M", "F", "F", "F", "M", "F", "M", "F")
unique(y)
[1] "M" "F"
table(y)
y
F M 
5 4 

O altă operație importantă care apare des în practică este operația de sortare. Funcția sort() permite sortarea vectorilor în ordine crescătoare sau descrescătoare:

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

sort(x) # ordonat crescator
 [1]  1  2  3  4  5  6  7  8  9 10
sort(x, decreasing = TRUE) # ordonat descrescator
 [1] 10  9  8  7  6  5  4  3  2  1

O funcție similară cu funcția sort() dar care întoarce pozițiile corespunzătoare ordinii este funcția order():

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

order(x) # indicii corespunzatori ordonarii crescatoare
 [1]  9 10  1  6  4  5  2  8  3  7
x[order(x)] # similar cu sort
 [1]  1  2  3  4  5  6  7  8  9 10

Adițional, funcția rev() rearanjează elementele unui vector în sens invers:

rev(x)
 [1]  2  1  8 10  4  6  5  9  7  3

Trebuie menționat de asemenea că limbajul R suportă și o serie de operații cu mulțimi pe vectori. Cele mai des întâlnite astfel de operații sunt sumarizate în Tabelul 3 de mai jos:

Tabelul 3: Funcții utile operații cu mulțimi pe vectori.
Funcție Scurtă descriere
union(A, B) Reuniunea mulțimilor (elementelor vectorilor) \(A\) și \(B\): \(A\cup B\)
intersect(A, B) Intersecția mulțimilor (elementelor vectorilor) \(A\) și \(B\): \(A\cap B\)
setdiff(A, B) Diferența mulțimilor (elementelor vectorilor) \(A\) și \(B\): \(A\setminus B\)
setequal(A, B) Test pentru egalitatea mulțimilor
A %in% B Testarea apartenenței elementelor din \(A\) în \(B\)
A <- c("mere", "pere", "banane", "struguri") # fructe
B <- c("lapte", "branza", "unt", "banane") # cumparaturi

union(A, B)
[1] "mere"     "pere"     "banane"   "struguri" "lapte"    "branza"   "unt"     
intersect(A, B)
[1] "banane"
setdiff(A, B)
[1] "mere"     "pere"     "struguri"
setdiff(B, A)
[1] "lapte"  "branza" "unt"   
setequal(A, B)
[1] FALSE
setequal(B, c("banane", "branza", "lapte", "unt"))
[1] TRUE
"pere" %in% A
[1] TRUE
c("mere", "pere", "branza") %in% A
[1]  TRUE  TRUE FALSE

Este important de remarcat faptul că mulțimea vidă (i.e. \(\emptyset\)) este reprezentată în R prin intermediul unui vector de lungime zero.

A <- 1:5
B <- 1:7
C <- 6:9

# diferenta vida
setdiff(A, B)
integer(0)
# intersectie vida
intersect(A, C)
integer(0)

Valori speciale

Limbajul R pune la dispoziție o serie de valori speciale: NA, NaN, NULL, Inf și -Inf.

  1. Valorile de tip NA (Not available sau Not assigned) sunt folosite cu precădere atunci când vrem să reprezentăm date lipsă , e.g. date care nu au putut fi colectate în timpul experimentului. Să presupunem că în timpul unui sondaj, patru persoane au fost întrebate ce înălțime au iar a doua persoana a refuzat să răspundă:
h <- c(168, NA, 173, 185)
h
[1] 168  NA 173 185

Valorile Na sunt de mai multe tipuri, în funcție de tipul de date:

x <- c(1, NA, 2)
mode(x[1])
[1] "numeric"
mode(x[2]) # NA de tip numeric
[1] "numeric"
y <- c("a", NA, "b")
mode(y[1])
[1] "character"
mode(y[2]) # NA de tip caracter
[1] "character"

Pentru a determina valorile lipsă ale unui vector x putem folosi funcția is.na() care întoarce un vector logic de aceeași lungime cu vectorul inițial cu valoare de adevăr pe pozițiile unde apare NA:

is.na(c(168, NA, 173, 185))
[1] FALSE  TRUE FALSE FALSE
  1. Valorile de tip Inf și -Inf sunt folosite atunci când un număr este prea mare \(\infty\), respectiv prea mic \(-\infty\), pentru a fi reprezentat în R. Valorile numerice maxime și minime permise de R depind de arhitectura sistemului pe care este instalat R-ul, de procesor sau de compilator. Putem obține aceste valori accesând variabila .Machine (a se vedea documentația ?.Machine pentru informații suplimentare):
.Machine$double.xmax
[1] 1.797693e+308
.Machine$double.xmin
[1] 2.225074e-308
.Machine$double.eps
[1] 2.220446e-16
.Machine$double.max
[1] 1024

Valorile în afara acestor limite sunt stocate drept Inf și -Inf. Putem efectua operații matematice cu aceste valori în același mod în care efectuăm operații cu \(\infty\) și \(-\infty\):

Inf + 5
[1] Inf
Inf - 20
[1] Inf
Inf * 3
[1] Inf
Inf * (-2)
[1] -Inf
Inf / 42
[1] Inf
# Orice valoare reala impartita la infinit este 0
2 / Inf
[1] 0
- 10 / -Inf
[1] 0
# O valoare reala impartita la 0 este infinit
3 / 0
[1] Inf
-3 / 0
[1] -Inf

Uneori, rezultatul unui calcul poate conduce la o valoare nedefinită, care în R se exprimă prin NaN (Not a number):

Inf / Inf
[1] NaN
Inf - Inf
[1] NaN
0 / 0
[1] NaN

Orice operație matematică care implică valoarea NaN rezultă tot în valoarea NaN iar identificarea acestei valori se poate face prin intermediul funcției is.nan():

NaN + 10
[1] NaN
NaN * 5
[1] NaN
x <- c(5, 1, NaN, NA)
is.nan(x)
[1] FALSE FALSE  TRUE FALSE

Ultimul tip de valoare specială este NULL. Această valoare este utilizată atunci când vrem să specificăm că un obiect este vid, nu există. Spre deosebire de NA care face referire la un obiect în memorie ce poate fi modificat sau accesat, NULL nu permite acest lucru.

# Exemplificare NULL si NA
x <- NULL
x
NULL
length(x)
[1] 0
y <- NA
y
[1] NA
length(y)
[1] 1
# NULL nu ocupa o pozitie in vector 
c(2, 3, NULL, NULL)
[1] 2 3
c(2, 3, NA, NA)
[1]  2  3 NA NA

Metode de indexare a vectorilor

Sunt multe situațiile în care nu vrem să efectuăm operații pe întreg vectorul ci pe o submulțime de valori ale lui selecționate în funcție de anumite proprietăți. Putem, de exemplu, să ne dorim să accesăm al 2-lea element al vectorului sau toate elementele mai mari decât o anumită valoare. Pentru aceasta vom folosi operația de indexare folosind parantezele pătrate [].

În general, sunt două tehnici principale de indexare: indexarea numerică și indexarea logică.

Indexare numerică

Atunci când folosim indexarea numerică, inserăm între parantezele pătrate un vector numeric (de numere întregi) ce corespunde poziției elementelor pe care vrem să le accesăm sub forma x[index] (x este vectorul inițial iar index este vectorul de indici). Indexarea începe cu poziția 1:

x = seq(1, 10, length.out = 21) # vectorul initial 

x[1] # accesam primul element
[1] 1
x[c(2,5,9)] # accesam elementul de pe pozitia 2, 5 si 9
[1] 1.45 2.80 4.60
x[4:10] # accesam toate elementele dintre pozitiile 4 si 9
[1] 2.35 2.80 3.25 3.70 4.15 4.60 5.05

Putem folosi orice vector de indici atât timp cât el conține numere întregi (numerice), lungimea acestuia nu trebuie să fie egală sau mai mică cu cea a vectorului pe care îl indexăm. De exemplu, putem să accesăm elementele vectorului x și de mai multe ori:

x[c(1,1,2,2,2)]
[1] 1.00 1.00 1.45 1.45 1.45

Atunci când valoarea cu care indexăm nu corespunde unei poziții din vectorul pe care în indexăm, rezultatul va fi NA:

x[22] # vectorul are 21 de elemente
[1] NA

De asemenea dacă vrem să afișăm toate elementele mai puțin elementul de pe poziția i atunci putem folosi indexare cu numere negative. Această metodă este folositoare și în cazul în care vrem să ștergem unul sau mai multe elemente ale vectorului:

x[-5] # toate elementele mai putin cel de pe pozitia 5 
 [1]  1.00  1.45  1.90  2.35  3.25  3.70  4.15  4.60  5.05  5.50  5.95  6.40
[13]  6.85  7.30  7.75  8.20  8.65  9.10  9.55 10.00
x[-(1:3)] # toate elementele mai putin primele 3
 [1]  2.35  2.80  3.25  3.70  4.15  4.60  5.05  5.50  5.95  6.40  6.85  7.30
[13]  7.75  8.20  8.65  9.10  9.55 10.00
x <- x[-10] # vectorul x fara elementul de pe pozitia a 10-a

Să remarcăm că indexarea numerică se poate face folosind și numere reale nu doar întregi, dar R-ul va face conversia la numere întregi în mod automat:

x[1.99]
[1] 1
x[-1.99]
 [1]  1.45  1.90  2.35  2.80  3.25  3.70  4.15  4.60  5.50  5.95  6.40  6.85
[13]  7.30  7.75  8.20  8.65  9.10  9.55 10.00

Indexare logică

A doua modalitate de indexare se face prin intermediul vectorilor logici. Atunci când indexăm cu un vector logic acesta trebuie să aibă aceeași lungime cu a vectorului pe care vrem să îl indexăm.

Să presupunem că vrem să extragem din vectorul x doar elementele care verifică o anumită proprietate, spre exemplu sunt mai mari decât 3, atunci:

x>3 # un vector logic care ne arata care elemente sunt mai mari decat 3
 [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
[13]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
x[x>3] # elementele din x care sunt mai mari decat 3
 [1]  3.25  3.70  4.15  4.60  5.50  5.95  6.40  6.85  7.30  7.75  8.20  8.65
[13]  9.10  9.55 10.00

În cazul în care indexăm cu un vector logic de lungime mai mică acesta se va recicla până ajunge la dimensiunea (lungimea) vectorului pe care îl indexăm.

x[TRUE] # afiseaza toate elementele lui x
 [1]  1.00  1.45  1.90  2.35  2.80  3.25  3.70  4.15  4.60  5.50  5.95  6.40
[13]  6.85  7.30  7.75  8.20  8.65  9.10  9.55 10.00
x[c(TRUE, FALSE)] # afiseaza elementele lui x din 2 in 2
 [1] 1.00 1.90 2.80 3.70 4.60 5.95 6.85 7.75 8.65 9.55
x[c(TRUE, FALSE, FALSE)] # afiseaza elementele lui x din 3 in 3
[1] 1.00 2.35 3.70 5.50 6.85 8.20 9.55

Pentru a determina care sunt toate elementele din x cuprinse între 5 și 19 putem să ne folosim de operații cu operatori logici:

x[(x>5)&(x<19)]
 [1]  5.50  5.95  6.40  6.85  7.30  7.75  8.20  8.65  9.10  9.55 10.00

O listă a operatorilor logici din R se găsește în Tabelul 4 de mai jos:

Tabelul 4: Operatori logici.
Operator Descriere
== Egal
!= Diferit
< Mai mic
<= Mai mic sau egal
> Mai mare
>= Mai mare sau egal
| sau || Sau (primul are valori vectoriale al doilea scalare)
& sau && Și (primul are valori vectoriale al doilea scalare)
! Negație
%in% În mulțimea

Comenzile următoare descriu modul de utilizare al operatorilor logici:

x <- seq(1,10,length.out = 8)
x == 3
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
x != 3
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
x <= 8.6
[1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE
(x<8) & (x>2)
[1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE
(x<8) && (x>2)
Error in (x < 8) && (x > 2): 'length = 8' in coercion to 'logical(1)'
(x<7) | (x>3)
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
(x<7) || (x>3)
Error in (x < 7) || (x > 3): 'length = 8' in coercion to 'logical(1)'
x %in% c(1,9)
[1]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Funcția which() face legătura dintre indexarea logică și cea numerică. Această funcție întoarce indicii corespunzători valorilor TRUE din vectorul logic și este folosită cu precădere atunci când suntem interesați de indicii elementelor dintr-un vector care verifică o proprietate dată.

which(c(TRUE, TRUE, FALSE, FALSE, TRUE))
[1] 1 2 5
x > 20
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
which(x > 20)
integer(0)

Exerciții

Exercițiul 1 Creați vectorii următori:

  1. \((1,2,3, \ldots, 29,30)\)

  2. \((50,49, \ldots, 2,1)\)

  3. \((1,2,3, \ldots, 29,30,29,18, \ldots, 2,1)\)

  4. \((4,6,3)\) și atribuiți valoarea acestuia variabilei \(a\)

  5. \((4,6,3,4,6,3, \ldots, 4,6,3)\) unde avem \(10\) repetări ale valorii \(4\)

  6. \((4,6,3,4,6,3, \ldots, 4,6,3,4)\) unde sunt \(11\) repetări ale valorii \(4\), \(10\) repetări ale valorii \(6\) și \(10\) repetări ale valorii \(3\)

  7. \((4,4, \ldots, 4,6,6, \ldots, 6,3,3, \ldots, 3)\) unde \(4\) apare de \(10\) ori, \(6\) de \(15\) ori și \(3\) de \(20\) ori.

Exercițiul 2 Să presupunem că am înregistrat în fiecare zi, pe parcursul a 4 săptămâni (de Luni până Duminică), numărul de minute petrecute la telefonul mobil (convorbiri + utilizare) și am obținut următoarele valori: 106, 123, 123, 111, 125, 113, 130, 113, 114, 100, 120, 130, 118, 114, 127, 112, 121, 114, 120, 119, 127, 114, 108, 127, 131, 157, 102, 133. Ne întrebăm: care sunt zilele din săptămână în care am vorbit cel mai mult? dar cel mai puțin? dar zilele în care am vorbit mai mult de 120 de minute? (Indicație: Folosiți funcțiile rep și cat sau paste)

Exercițiul 3 Creați următorii vectori:

  1. \(\left(0.1^3 0.2^1, 0.1^6 0.2^4, \ldots, 0.1^{36} 0.2^{34}\right)\)

  2. \(\left(2, \frac{2^2}{2}, \frac{2^3}{3}, \ldots, \frac{2^{25}}{25}\right)\)

  3. \(\left(e^{5}\cos(2^{5}), e^{5.1}\cos(2^{5.1}), \cdots, e^{7}\cos(2^{7})\right)\)

Exercițiul 4 Calculați:

  1. \(\sum_{i=10}^{200}\left(i^3+5 i^2\right)\).

  2. \(\sum_{i=1}^{250}\left(\frac{3^i}{i}+\frac{2^i}{i^2}\right)\)

  3. Folosind funcția cumprod determinați

\[ 1+\frac{2}{3}+\left(\frac{2}{3} \frac{4}{5}\right)+\left(\frac{2}{3} \frac{4}{5} \frac{6}{7}\right)+\cdots+\left(\frac{2}{3} \frac{4}{5} \cdots \frac{100}{101}\right) \]

Exercițiul 5 Folosind funcția paste creați următorii vectori:

  1. (“label 1”, “label 2”, …., “label 30”)
  2. (“fn1”, “fn2”, .,, “fn30”). (Indicație: Ce face funcția paste0?)

Exercițiul 6 Executați următoarele linii de cod care crează doi vectori de lungime \(250\) obținuți prin extragerea cu întoarcere din mulțimea \(0,1, \ldots, 999\).

set.seed(1234)

x <- sample(0:999, 250, replace = TRUE)
y <- sample(0:999, 250, replace = TRUE)

Presupunând că \(\mathbf{x}=\left(x_1, x_2, \ldots, x_n\right)\) și \(\mathbf{y}=\left(y_1, y_2, \ldots, y_n\right)\) creați următorii vectori:

  1. \(\left(y_2-x_1, \ldots, y_n-x_{n-1}\right)\)

  2. \(\left(\frac{\sin \left(y_1\right)}{\cos \left(x_2\right)}, \frac{\sin \left(y_2\right)}{\cos \left(x_3\right)}, \ldots, \frac{\sin \left(y_{n-1}\right)}{\cos \left(x_n\right)}\right)\)

  3. \(\left(x_1+2 x_2-x_3, x_2+2 x_3-x_4, \ldots, x_{n-2}+2 x_{n-1}-x_n\right)\)

  4. \(\sum_{i=1}^{n-1} \frac{e^{-x_{i+1}}}{x_i+20}\).

Exercițiul 7 Pentru vectorii \(x\) și \(y\) creați în Exercițiul 6 răspundeți la următoarele cerințe:

  1. Alegeți valorile din \(y\) care sunt mai mari de \(600\).

  2. Care sunt pozițiile corespunzătoare din \(y\) pentru care valorile lui \(y\) sunt mai mari de \(600\) ?

  3. Care sunt valorile din \(x\) care corespund valorilor din \(y\) care sunt mai mari de \(600\) ?

  4. Creați vectorul \(\left(\left|x_1-\overline{\mathbf{x}}\right|^{1 / 2},\left|x_2-\overline{\mathbf{x}}\right|^{1 / 2}, \ldots,\left|x_n-\overline{\mathbf{x}}\right|^{1 / 2}\right)\) unde \(\overline{\mathbf{x}}\) reprezintă media vectorului \(\mathbf{x}=\left(x_1, x_2, \ldots, x_n\right)\)

  5. Câte valori din \(x\) sunt divizibile cu \(2\)?

  6. Sortați valorile vectorului \(x\) în ordinea crescătoare a valorilor lui \(y\)

  7. Alegeți elementele din \(y\) de pe pozițiile \(1,4,7,10,13, \ldots\).

Matrice

Matricele sunt structuri de date care extind vectorii și sunt folosite la reprezentarea datelor de același tip în două dimensiuni. Matricele sunt similare tablourilor din Excel și pot fi văzute ca vectori cu două atribute suplimentare: numărul de linii (rows) și numărul de coloane (columns).

Figura 1: Scalari, vectori, matrice

Indexarea liniilor și a coloanelor pentru o matrice începe cu 1. De exemplu, elementul din colțul din stânga sus al unei matrice este notat cu x[1,1]. De asemenea este important de menționat că stocarea (internă) a metricelor se face pe coloane în sensul că prima oară este stocată coloana 1, apoi coloana 2, etc..

Metode de construcție

Există mai multe moduri de creare a unei matrici în R. Funcțiile cele mai uzuale sunt prezentate în tabelul de mai jos. Cum matricele sunt combinații de vectori, fiecare funcție primește ca argument unul sau mai mulți vectori (toți de același tip) și ne întoarce o matrice.

Tabelul 5: Funcții care permit crearea matricelor.
Funcție Descriere Exemple
cbind(a, b, c) Combină vectorii ca și coloane într-o matrice cbind(1:5, 6:10, 11:15)
rbind(a, b, c) Combină vectorii ca și linii într-o matrice rbind(1:5, 6:10, 11:15)
matrix(x, nrow, ncol, byrow) Crează o matrice dintr-un vector x matrix(x = 1:12, nrow = 3, ncol = 4)

Pentru a vedea ce obținem atunci când folosim funcțiile cbind() și rbind() să considerăm exemplele următoare:

x <- 1:5
y <- 6:10
z <- 11:15

# Cream o matrice cu x, y si z ca si coloane
cbind(x, y, z)
     x  y  z
[1,] 1  6 11
[2,] 2  7 12
[3,] 3  8 13
[4,] 4  9 14
[5,] 5 10 15
# Cream o matrice in care x, y si z sunt linii
rbind(x, y, z)
  [,1] [,2] [,3] [,4] [,5]
x    1    2    3    4    5
y    6    7    8    9   10
z   11   12   13   14   15

Funcția matrix() crează o matrice plecând de la un singur vector. Funcția are mai multe argumente de intrare: data – un vector cu date, nrow – numărul de linii pe care le vrem în matrice sau ncol – numărul de coloane pe care să le aibă matricea. Trebuie avut grijă ca lungimea vectorului de date să fie egală cu produsul dintre numărul de linii și numărul de coloane specificat, în caz contrar are loc fenomenul de reciclare văzut și în cazul vectorilor:

# matrice cu 5 linii si 2 coloane
matrix(data = 1:10,
       nrow = 5,
       ncol = 2)
     [,1] [,2]
[1,]    1    6
[2,]    2    7
[3,]    3    8
[4,]    4    9
[5,]    5   10
# matrice cu 2 linii si 5 coloane
matrix(data = 1:10,
       nrow = 2,
       ncol = 5)
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    3    5    7    9
[2,]    2    4    6    8   10
# matrice pentru care lungimea vectorului de intrare nu este egala 
# cu produsul numarului de linii si coloane
matrix(data = 1:10,
       nrow = 3, 
       ncol = 5)
Warning in matrix(data = 1:10, nrow = 3, ncol = 5): data length [10] is not a
sub-multiple or multiple of the number of rows [3]
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    4    7   10    3
[2,]    2    5    8    1    4
[3,]    3    6    9    2    5

Este important de observat modul în care un vector este convertit într-o matrice. Comportamentul implicit al R-ului este de a aloca valorile pe coloane începând din poziția 1 a vectorului și continuând pe coloane de sus în jos. Dacă se dorește schimbarea acestui mod de alocare, se poate specifica opțiunea byrow – o valoare logică care permite crearea matricei pe linii:

# aceeasi matrice cu 2 linii si 5 coloane, umpluta pe linii 
matrix(data = 1:10,
       nrow = 2,
       ncol = 5,
       byrow = TRUE)
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10

Operații

Operațiile uzuale cu vectori se aplică și matricelor. Pe lângă acestea avem la dispoziție și operații de algebră liniară clasice, cum ar fi determinarea dimensiunii acestora, transpunerea matricelor sau înmulțirea lor:

diag(M) # Diagonala matricei M
dim(M) # Dimensiunea matricei M
nrow(M) # Numarul de linii ale matricei M
ncol(M) # Numarul de coloane ale matricei M
t(M) # Transpusa
colSums(M), rowSums(M) # Suma pe coloane si suma pe linii

De exemplu:

m = matrix(data = 1:10,
       nrow = 2,
       ncol = 5)
m
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    3    5    7    9
[2,]    2    4    6    8   10
dim(m) # dimensiunea matricei
[1] 2 5
nrow(m) # numarul de linii
[1] 2
ncol(m) # numarul de coloane
[1] 5

Adunarea și scăderea matricelor se face pe componenete:

A = matrix(c(1, 3, 2, 2, 2, 1, 3, 1, 3), ncol = 3)
B = matrix(c(4, 6, 4, 5, 5, 6, 6, 4, 5), ncol = 3)
a = 2

A + a
     [,1] [,2] [,3]
[1,]    3    4    5
[2,]    5    4    3
[3,]    4    3    5
A + B
     [,1] [,2] [,3]
[1,]    5    7    9
[2,]    9    7    5
[3,]    6    7    8
A - B
     [,1] [,2] [,3]
[1,]   -3   -3   -3
[2,]   -3   -3   -3
[3,]   -2   -5   -2

Înmulțirea și împărțirea se face tot pe componenete:

A * a
     [,1] [,2] [,3]
[1,]    2    4    6
[2,]    6    4    2
[3,]    4    2    6
A * B
     [,1] [,2] [,3]
[1,]    4   10   18
[2,]   18   10    4
[3,]    8    6   15
A / B
     [,1]      [,2] [,3]
[1,] 0.25 0.4000000 0.50
[2,] 0.50 0.4000000 0.25
[3,] 0.50 0.1666667 0.60

Transpusa unei matrice (\(A^\intercal\)) se obține cu ajutorul funcției t()

t(A)
     [,1] [,2] [,3]
[1,]    1    3    2
[2,]    2    2    1
[3,]    3    1    3

iar inversa (\(A^{-1}\)) cu ajutorul funcției solve()

solve(A)
            [,1]  [,2]       [,3]
[1,] -0.41666667  0.25  0.3333333
[2,]  0.58333333  0.25 -0.6666667
[3,]  0.08333333 -0.25  0.3333333

Înmulțirea (uzuală a) matricelor se face folosind operatorul %*%

A %*% B # inmultirea matricelor
     [,1] [,2] [,3]
[1,]   28   33   29
[2,]   28   31   31
[3,]   26   33   31
A %*% solve(A)
              [,1]          [,2]         [,3]
[1,]  1.000000e+00 -1.110223e-16 2.220446e-16
[2,]  4.163336e-17  1.000000e+00 5.551115e-17
[3,] -1.110223e-16  1.110223e-16 1.000000e+00

iar funcția crossprod() calculează produsul \(A^\intercal B\) (mai repede decât folosind instrucțiunea t(A) %*% B)

crossprod(A, B)
     [,1] [,2] [,3]
[1,]   30   32   28
[2,]   24   26   25
[3,]   30   38   37

Determinantul și urma unei matrice se obțin folosind funcțiile det() și respectiv sum(diag())

det(A) # determinantul
[1] -12
sum(diag(A)) # urma matricei A
[1] 6

iar matricea identitate se obține folosind comanda \(I_n=\)diag(x = n):

diag(x = 3)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

Metode de indexare

Metodele de indexare discutate pentru vectori se aplică și în cazul matricelor ([,]) numai că acum în loc să folosim un vector să indexăm putem să folosim doi vectori. Sintaxa are structura generală m[linii, coloane] unde linii și coloane sunt vectori cu valori întregi.

m = matrix(1:20, nrow = 4, byrow = TRUE)

# Linia 1
m[1, ]
[1] 1 2 3 4 5
# Coloana 5
m[, 5]
[1]  5 10 15 20
# Liniile 2, 3 si coloanele 3, 4
m[2:3, 3:4]
     [,1] [,2]
[1,]    8    9
[2,]   13   14

Indexarea logică poate fi combinată cu cea numerică:

# Elementele din coloana 3 care corespund liniilor pentru care elementele 
# de pe prima coloana sunt > 3
m[m[,1]>3, 3]
[1]  8 13 18

Dacă dorim să adăugăm linii sau coloane la o matrice dată atunci putem să folosim funcțiile rbind() și respectiv cbind():

# adaugarea unei linii la sfarsit
m1 <- rbind(m, 1:4)
Warning in rbind(m, 1:4): number of columns of result is not a multiple of
vector length (arg 2)
m1 
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
[5,]    1    2    3    4    1
# adaugarea unei coloane la sfarsit
m2 <- cbind(m, 1:3)
Warning in cbind(m, 1:3): number of rows of result is not a multiple of vector
length (arg 2)
m2
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    2    3    4    5    1
[2,]    6    7    8    9   10    2
[3,]   11   12   13   14   15    3
[4,]   16   17   18   19   20    1

Pentru a șterge una sau mai multe linii sau coloane putem indexa cu numere negative:

m[-1, ] # stergerea primei linii
     [,1] [,2] [,3] [,4] [,5]
[1,]    6    7    8    9   10
[2,]   11   12   13   14   15
[3,]   16   17   18   19   20
m[, -3] # stergerea coloanei 3
     [,1] [,2] [,3] [,4]
[1,]    1    2    4    5
[2,]    6    7    9   10
[3,]   11   12   14   15
[4,]   16   17   19   20

Este important de observat că atunci când extragem o linie sau o coloană dintr-o matrice rezultatul obținut este un vector și nu o matrice unidimensională după cum se poate vedea și în exemplul următor:

v <- m[2, ]
v 
[1]  6  7  8  9 10
dim(v)
NULL

Pentru a evita această reducere automată a dimensiunii, R pune la dispiziție argumentul logic drop = FALSE (implicit drop = TRUE):

v <- m[2, , drop = FALSE]
v 
     [,1] [,2] [,3] [,4] [,5]
[1,]    6    7    8    9   10
dim(v)
[1] 1 5

Exerciții

Exercițiul 8 Rezolvați următoarele cerințe:

  1. Calculați

\[ \frac{2}{7}\left(\left[\begin{array}{ll} 1 & 2 \\ 2 & 4 \\ 7 & 6 \end{array}\right]-\left[\begin{array}{ll} 10 & 20 \\ 30 & 40 \\ 50 & 60 \end{array}\right]\right) \]

  1. Pentru

\[ \mathbf{A}=\left[\begin{array}{ccc} 1 & 1 & 3 \\ 5 & 2 & 6 \\ -2 & -1 & -3 \end{array}\right] \] Verificați că \(\mathbf{A}^3=\mathbf{0}\) unde \(\mathbf{0}\) este matricea nulă de \(3 \times 3\). Înlocuiți a treia coloană a lui \(\mathbf{A}\) cu suma dintre coloana a doua și a treia.

Exercițiul 9 Fie următoarele matrice:

\[ A=\left[\begin{array}{l} 1 \\ 2 \\ 7 \end{array}\right] \quad B=\left[\begin{array}{l} 3 \\ 4 \\ 8 \end{array}\right] \]

Care dintre următoarele înmulțiri sunt posibile, și în caz că sunt posibile calculați rezultatul:

  1. \(A \cdot B\)
  2. \(A^{\top} \cdot B\)
  3. \(B^{\top} \cdot\left(A \cdot A^{\top}\right)\)
  4. \(\left(A \cdot A^{\top}\right) \cdot B^{\top}\)
  5. \(\left[\left(B \cdot B^{\top}\right)+\left(A \cdot A^{\top}\right)-100 I_3\right]^{-1}\)

Exercițiul 10 Creați următoarele matrice așa încât soluția să țină cont de forma particulară a matricelor (Indicație: încercați să folosiți funcția outer):

\[ \left(\begin{array}{lllll}0 & 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 & 0 \\ 2 & 3 & 4 & 0 & 1 \\ 3 & 4 & 0 & 1 & 2 \\ 4 & 0 & 1 & 2 & 3\end{array}\right) \quad \left(\begin{array}{cccccccccc}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 0 \\ \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\ 8 & 9 & 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 \\ 9 & 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8\end{array}\right) \]

\[ \left(\begin{array}{lllllllll}0 & 8 & 7 & 6 & 5 & 4 & 3 & 2 & 1 \\ 1 & 0 & 8 & 7 & 6 & 5 & 4 & 3 & 2 \\ 2 & 1 & 0 & 8 & 7 & 6 & 5 & 4 & 3 \\ 3 & 2 & 1 & 0 & 8 & 7 & 6 & 5 & 4 \\ 4 & 3 & 2 & 1 & 0 & 8 & 7 & 6 & 5 \\ 5 & 4 & 3 & 2 & 1 & 0 & 8 & 7 & 6 \\ 6 & 5 & 4 & 3 & 2 & 1 & 0 & 8 & 7 \\ 7 & 6 & 5 & 4 & 3 & 2 & 1 & 0 & 8 \\ 8 & 7 & 6 & 5 & 4 & 3 & 2 & 1 & 0\end{array}\right) \]

Exercițiul 11 Rezolvați următorul sistem liniar de ecuații:

\[ \begin{array}{lr} x_1+2 x_2+3 x_3+4 x_4+5 x_5= & 7 \\ 2 x_1+x_2+2 x_3+3 x_4+4 x_5= & -1 \\ 3 x_1+2 x_2+x_3+2 x_4+3 x_5= & -3 \\ 4 x_1+3 x_2+2 x_3+x_4+2 x_5= & 5 \\ 5 x_1+4 x_2+3 x_3+2 x_4+x_5= & 17 \end{array} \]

scris sub formă matriceală \(\mathbf{A x}=\mathbf{y}\) (Indicație: Folosiți funcțiile solve, col sau row).

Exercițiul 12 Calculați:

  1. \(\sum_{i=1}^{20} \sum_{j=1}^5 \frac{i^4}{(3+j)}\)

  2. \(\sum_{i=1}^{20} \sum_{j=1}^5 \frac{i^4}{(3+i j)}\)

  3. \(\sum_{i=1}^{10} \sum_{j=1}^i \frac{i^4}{(3+i j)}\)

Tablouri multidimensionale

Pe lângă vectori, care au dimensiune \(1\), și matrice, care sunt structuri de date de dimensiune \(2\), R-ul pune la dispoziție și structuri de date multidimensionale. Tablourile multidimensionale (arrays) constituie un tip de structură de date care generalizează structurile de tip matrice și, ca și acestea, conțin doar date de același tip. Pentru crearea acestor structuri de date se folosește funcția array() care primește ca prin argument un vector de date iar apoi se specifică dimensiunea tabloului folosind argumentul dim =:

a <- array(data = 1:20, dim = c(5, 2, 2))
a
, , 1

     [,1] [,2]
[1,]    1    6
[2,]    2    7
[3,]    3    8
[4,]    4    9
[5,]    5   10

, , 2

     [,1] [,2]
[1,]   11   16
[2,]   12   17
[3,]   13   18
[4,]   14   19
[5,]   15   20

Observăm că am obținut un tablou tridimensional cu două straturi în care fiecare strat este format dintr-o matrice cu cinci linii și două coloane. În acest exemplu, prin argumentul dim = c(5, 2, 2) am specificat, de la stânga la dreapta, numărul de linii, numărul de coloane și respectiv straturile.

Indexarea tablourilor multidimensionale se face în manieră similară cu indexarea matricelor, numărul de indici variind în funcție de dimensiunea tabloului:

dim(a)
[1] 5 2 2
a[1, 1, 1] # elementul de pe pozitia 1,1,1
[1] 1
a[,,1] # matricea de pe stratul 1
     [,1] [,2]
[1,]    1    6
[2,]    2    7
[3,]    3    8
[4,]    4    9
[5,]    5   10
a[1, , 1] # prima linie a matricii de pe primul strat
[1] 1 6

Exerciții

Exercițiul 13 Creați un tablou multidimensional cu șase straturi astfel încât fiecare strat este o matrice de dimensiune \(4\times 2\) și care conține elemente de la \(1\) la \(48\).

Exercițiul 14 Investigați ce fac funcțiile aperm, sweep, margin.table și addmargins și prezentați câte un exemplu de utilizare din fiecare.

Liste

Spre deosebire de vectori în care toate elementele trebuie să aibă același tip de date, structura de date din R de tip listă (list) permite combinarea obiectelor de mai multe tipuri. Cu alte cuvinte, o listă poate avea de exemplu primul element un scalar, al doilea un vector, al treilea o matrice iar cel de-al patrulea element poate fi o altă listă. Tehnic listele sunt tot vectori, vectorii pe care i-am văzut anterior se numesc vectori atomici, deoarece elementele lor nu se pot diviza, pe când listele se numesc vectori recursivi.

Ca un prim exemplu să considerăm cazul unei baze de date ce face referire la studenții FMI. Pentru fiecare student, ne dorim să stocăm numele și prenumele acestuia (șir de caractere), nota la statistică (valoare numerică) și o valoare de tip logic care poate reprezenta apartenența lui în asociația studenților. Pentru crearea listei folosim funcția list():

a <-  list(nume = "Popescu", prenume = "Ionel", nota_stat = 9, apartenenta = T)
a
$nume
[1] "Popescu"

$prenume
[1] "Ionel"

$nota_stat
[1] 9

$apartenenta
[1] TRUE
str(a) # structura listei
List of 4
 $ nume       : chr "Popescu"
 $ prenume    : chr "Ionel"
 $ nota_stat  : num 9
 $ apartenenta: logi TRUE
names(a) # numele listei
[1] "nume"        "prenume"     "nota_stat"   "apartenenta"

Numele componentelor listei a (nume, prenume, nota_stat, apartenența) nu sunt obligatorii, dar cu toate acestea, pentru claritate și pentru evitarea erorilor este indicată folosirea lor. Pe de altă parte, putem crea o listă care conține aceleași elemente cu lista a dar care nu are numite componentele:

a2 <-  list("Ionel", 9, T)
a2
[[1]]
[1] "Ionel"

[[2]]
[1] 9

[[3]]
[1] TRUE

Putem numi componentele listei folosind funcția names():

names(a2) <- c("prenume", "nota_proba", "bursa")
a2
$prenume
[1] "Ionel"

$nota_proba
[1] 9

$bursa
[1] TRUE

Deoarece listele sunt vectori ele pot fi create și prin intermediul funcției vector() și putem determina numărul de componente folosind funcția length():

z <- vector(mode="list")
z
list()
z[["a"]] <- 3
z
$a
[1] 3
length(a)
[1] 4
length(a2)
[1] 3

Indexarea listelor

Elementele unei liste pot fi accesate în diferite moduri. Dacă dorim să extragem o componentă (un element al) a listei atunci vom folosi indexarea care folosește o singură pereche de paranteze pătrate []

a[1]
$nume
[1] "Popescu"
a[2]
$prenume
[1] "Ionel"
# ce obtinem cand extragem un element al listei a ?
str(a[1]) 
List of 1
 $ nume: chr "Popescu"

În cazul în care vrem să accesăm structura de date corespunzătoare elementului i al listei vom folosi două perechi de paranteze pătrate [[]] sau în cazul în care lista are nume operatorul $ urmat de numele elementului i:

a[[1]]
[1] "Popescu"
a[[2]]
[1] "Ionel"
a$nume
[1] "Popescu"
a[["nume"]]
[1] "Popescu"

Este important de punctat diferența dintre indexarea cu o pereche de paranteze pătrate [] și respectiv două perechi de paranteze pătrate [[]]: în primul caz rezultatul este tot o listă (o sublistă a primei liste) iar în al doilea caz este o structură de date de același tip cu cea a componentei extrase:

a3 <- list(nume = "Ionel", 
           note = c(10, 9, 5, 7), 
           materii = c("Algebra", "Analiza", "Probabilitati", "Statistica"), 
           graf = matrix(1:12, 3, 4))

str(a3[1])
List of 1
 $ nume: chr "Ionel"
class(a3[1])
[1] "list"
str(a3[[1]])
 chr "Ionel"
class(a3[[1]])
[1] "character"
# Diverse modalitati de indexare

# notele la Probabilitati si Statistica
a3[[2]][3:4]
[1] 5 7
a3[2][[1]][3:4]
[1] 5 7
a3[["note"]][3:4]
[1] 5 7
a3$note[3:4]
[1] 5 7
a3$note[which(a3$materii %in% c("Probabilitati", "Statistica"))]
[1] 5 7
# submatrice
a3[[4]][1:2, 3:4]
     [,1] [,2]
[1,]    7   10
[2,]    8   11
a3[["graf"]][1:2, 3:4]
     [,1] [,2]
[1,]    7   10
[2,]    8   11
a3$graf[1:2, 3:4]
     [,1] [,2]
[1,]    7   10
[2,]    8   11

Operațiile de adăugare, respectiv ștergere, a elementelor unei liste sunt des întâlnite în practică.

Putem adăuga elemente după ce o listă a fost creată folosind numele componentei

z <- list(a = "abc", b = 111, c = c(TRUE, FALSE))
z
$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE
# adaugam componenta d
z$d <- "un nou element"
z
$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE

$d
[1] "un nou element"

sau indexare vectorială

# adaugam elementul 5
z[[5]] <- 200

# adaugam elementele 6 si 7
z[6:7] <- c("unu", "doi")
z
$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE

$d
[1] "un nou element"

[[5]]
[1] 200

[[6]]
[1] "unu"

[[7]]
[1] "doi"

O altă modalitate de a adăuga elemente unei liste (sau vector) este prin intermediul funcției append(). Această funcție permite adăugarea de elemente atât la începutul listei, la finalul acesteia precum și într-o poziție intermediară:

l1 <- list(a1 = matrix(1:12, 3, 4), b1 = seq(0, 1, length.out = 10), c1 = letters[1:5])

# adaugam la sfarsitul listei l1 primele 3 elemente ale listei z 
append(l1, z[1:3])
$a1
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12

$b1
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
 [8] 0.7777778 0.8888889 1.0000000

$c1
[1] "a" "b" "c" "d" "e"

$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE
# adaugam la lista l1 dupa a doua pozitie primele 3 elemente ale listei z 
append(l1, z[1:3], after = 2)
$a1
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12

$b1
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
 [8] 0.7777778 0.8888889 1.0000000

$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE

$c1
[1] "a" "b" "c" "d" "e"
# adaugam la inceputul listei l1 primele 3 elemente ale listei z 
append(l1, z[1:3], after = 0)
$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE

$a1
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12

$b1
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
 [8] 0.7777778 0.8888889 1.0000000

$c1
[1] "a" "b" "c" "d" "e"

Putem șterge o componentă a listei atribuindu-i valoarea NULL:

z[4] <- NULL
z
$a
[1] "abc"

$b
[1] 111

$c
[1]  TRUE FALSE

[[4]]
[1] 200

[[5]]
[1] "unu"

[[6]]
[1] "doi"

Putem de asemenea să concatenăm două liste folosind funcția c() și să determinăm lungimea noii liste cu funcția length().

l1 <- list(1:10, matrix(1:6, ncol = 3), c(T, F))
l2 <- list(c("Ionel", "Maria"), seq(1,10,2))

l3 <- c(l1, l2)
length(l3)
[1] 5
str(l3)
List of 5
 $ : int [1:10] 1 2 3 4 5 6 7 8 9 10
 $ : int [1:2, 1:3] 1 2 3 4 5 6
 $ : logi [1:2] TRUE FALSE
 $ : chr [1:2] "Ionel" "Maria"
 $ : num [1:5] 1 3 5 7 9

De asemenea, R pune la dispoziție funcția unlist() prin care putem să reducem o listă la un vector ce conține elementele sale (aplatizăm lista):

l1 <- list(a = 1:10, b = matrix(1:6, ncol = 3), c = c(T, F))
l1u <- unlist(l1)
l1u
 a1  a2  a3  a4  a5  a6  a7  a8  a9 a10  b1  b2  b3  b4  b5  b6  c1  c2 
  1   2   3   4   5   6   7   8   9  10   1   2   3   4   5   6   1   0 
class(l1u)
[1] "integer"

Rezultatul este un vector care are fiecare componentă denumită după elementele listei. Pentru a șterge aceste nume putem folosi funcția unname():

unname(l1u)
 [1]  1  2  3  4  5  6  7  8  9 10  1  2  3  4  5  6  1  0

Exerciții

Exercițiul 15 Creați o listă care să conțină: un șir de lungime \(25\) ce reprezintă o diviziune echidistantă a intervalului \([-5,5]\), o matrice de dimensiune \(3\times 3\) cu elemente logice și un vector de \(20\) de litere (Indicație: verificați ce face comanda letters sau LETTERS).

  1. Extrageți elementele de pe linia \(2\) și \(1\) din coloanele \(3\) și \(2\), în această ordine, din matricea logică a listei
  2. Obțineți toate elementele mai mari ca \(0.5\) din vectorul cu valori între \(-5\) și \(5\)

Exercițiul 16 Ordonați componentele următoarei liste în ordine alfabetică:

l <- list(b = 3, a = "Bogdan", 
          c = c(1,2,3), f = TRUE,
          e = c("x","y","z"), d = 37)

Data frame-uri

La nivel intuitiv, o structură de date de tip dataframe este ca o matrice, având o structură bidimensională cu linii și coloane. Cu toate acestea ea diferă de structura de date de tip matrice prin faptul că fiecare coloană poate avea tipuri de date diferite. Spre exemplu, o coloană poate să conțină valori numerice pe când o alta, valori de tip caracter sau logic. Din punct de vedere tehnic, o structură de tip data frame este o listă a cărei componente sunt vectori (atomici) de lungimi egale.

Pentru a crea un dataframe din vectori putem folosi funcția data.frame(). Această funcție funcționează similar cu funcția list() sau cbind(), diferența față de cbind() este că avem posibilitatea să dăm nume coloanelor atunci când le unim. Dată fiind flexibilitatea acestei structuri de date, majoritatea seturilor de date din R sunt stocate sub formă de dataframe (această structură de date este și cea mai des întâlnită în analiza statistică). Proprietatea de reciclare se aplică și în cazul dataframe-urilor, astfel dacă în crearea unui dataframe furnizăm vectori de lungimi diferite, vectorul/rii mai scurt/ți va/vor fi reciclat/ți până când se atinge lungimea dorită. Trebuie remarcat că lungimea vectorului mai scurt trebuie să fie un divizor al lungimii dorite altfel vom avea eroare.

Să creăm un dataframe simplu numit survey folosind funcția data.frame():

# reciclarea nu are loc - eroare
survey <- data.frame(index = c(1, 2, 3, 4, 5),
                     sex = c("m", "f"), 
                     age = c(99, 46, 23, 54, 23))
# reciclarea are loc
survey <- data.frame(index = c(1, 2, 3, 4, 5, 6),
                     sex = c("m", "f"), 
                     age = c(99, 46, 23, 54, 23, 61))

survey 
  index sex age
1     1   m  99
2     2   f  46
3     3   m  23
4     4   f  54
5     5   m  23
6     6   f  61
# clasa obiectului survey
class(survey)
[1] "data.frame"
# un obiect de tip data.frame este derivat din clasa list
mode(survey)
[1] "list"
# verificam daca este data.frame sau list
is.data.frame(survey)
[1] TRUE
is.list(survey)
[1] TRUE

Funcția data.frame() prezintă un argument specific numit stringsAsFactors care permite convertirea coloanelor ce conțin elemente de tip caracter într-un tip de obiect numit factor (a se vedea secțiunea Secțiunea 1.6). Un factor este o variabilă nominală care poate lua un număr bine definit de valori. De exemplu, putem crea o variabilă de tip factor sex care poate lua doar două valori: masculin și feminin. Comportamentul implicit al funcției data.frame() (stringAsFactors = FALSE) nu transformă automat coloanele de tip caracter în factor, dar în cazul în care dorim acest lucru trebuie să includem argumentul stringsAsFactors = FALSE.

# Structura initiala
str(survey)
'data.frame':   6 obs. of  3 variables:
 $ index: num  1 2 3 4 5 6
 $ sex  : chr  "m" "f" "m" "f" ...
 $ age  : num  99 46 23 54 23 61
survey <- data.frame(index = c(1, 2, 3, 4, 5, 6),
                     sex = c("m", "f"), 
                     age = c(99, 46, 23, 54, 23, 61),
                     stringsAsFactors = TRUE)

# Structura de dupa 
str(survey)
'data.frame':   6 obs. of  3 variables:
 $ index: num  1 2 3 4 5 6
 $ sex  : Factor w/ 2 levels "f","m": 2 1 2 1 2 1
 $ age  : num  99 46 23 54 23 61

R are mai multe funcții care permit vizualizarea structurilor de tip dataframe. Tabelul 6 de mai jos include câteva astfel de funcții:

Tabelul 6: Exemple de funcții necesare pentru înțelegerea structurii de tip dataframe.
Funcție Descriere
head(x), tail(x) Printarea primelor linii (sau ultimelor linii).
View(x) Vizualizarea obiectului într-o fereastră nouă, tabelară.
nrow(x), ncol(x), dim(x) Numărul de linii și de coloane.
rownames(), colnames(), names() Numele liniilor sau coloanelor.
str(x) Structura dataframe-ului

Comanda data() ne permite vizualizarea seturilor de date disponibile în sesiunea curentă (?data pentru mai multe detalii).

data() # vedem ce seturi de date exista

data("ChickWeight")
data("mtcars")

Alegem un set de date, e.g. mtcars, și apelăm funcțiile din Tabelul 6 pentru a înțelege mai bine structura acestuia:

# Alegem setul de date mtcars
# ?mtcars
str(mtcars) # structura setului de date
'data.frame':   32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
tail(mtcars)
                mpg cyl  disp  hp drat    wt qsec vs am gear carb
Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2
Ford Pantera L 15.8   8 351.0 264 4.22 3.170 14.5  0  1    5    4
Ferrari Dino   19.7   6 145.0 175 3.62 2.770 15.5  0  1    5    6
Maserati Bora  15.0   8 301.0 335 3.54 3.570 14.6  0  1    5    8
Volvo 142E     21.4   4 121.0 109 4.11 2.780 18.6  1  1    4    2
dim(mtcars)
[1] 32 11
rownames(mtcars)
 [1] "Mazda RX4"           "Mazda RX4 Wag"       "Datsun 710"         
 [4] "Hornet 4 Drive"      "Hornet Sportabout"   "Valiant"            
 [7] "Duster 360"          "Merc 240D"           "Merc 230"           
[10] "Merc 280"            "Merc 280C"           "Merc 450SE"         
[13] "Merc 450SL"          "Merc 450SLC"         "Cadillac Fleetwood" 
[16] "Lincoln Continental" "Chrysler Imperial"   "Fiat 128"           
[19] "Honda Civic"         "Toyota Corolla"      "Toyota Corona"      
[22] "Dodge Challenger"    "AMC Javelin"         "Camaro Z28"         
[25] "Pontiac Firebird"    "Fiat X1-9"           "Porsche 914-2"      
[28] "Lotus Europa"        "Ford Pantera L"      "Ferrari Dino"       
[31] "Maserati Bora"       "Volvo 142E"         
names(mtcars)
 [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
[11] "carb"

Vizualizarea setului de date sub formă tabelară se face prin intermediul funcției View():

View(mtcars) 

Metode de indexare

Indexarea structurilor de tip dataframe se face la fel ca și indexarea listelor și a matricelor. Astfel, în primul caz, coloanele pot fi văzute ca elementele unei liste și pot fi accesate atât prin nume cât și prin indicele de poziție. Indexarea prin nume poate fi făcută atât prin folosirea parantezelor pătrate duble [[]] cât și prin operatorul $. Atunci când se folosesc parantezele duble, numele coloanei (variabilei) trebuie trecut ca un șir de caractere (folosind "" sau prin intermediul unei variabile de tip caracter) iar în cazul folosirii operatorului $ numele este trecut fără ghilimele (și nu poate fi o variabilă).

# prima coloana si primele 5 elementele 
mtcars[[1]][1:5] # indice
[1] 21.0 21.0 22.8 21.4 18.7
mtcars[["mpg"]][1:5] # nume
[1] 21.0 21.0 22.8 21.4 18.7
nume.var <- "mpg" # variabila de tip caracter cu numele coloanei dorite
mtcars[[nume.var]][1:5] # nume
[1] 21.0 21.0 22.8 21.4 18.7
mtcars$mpg[1:5] # nume
[1] 21.0 21.0 22.8 21.4 18.7

Spre deosebire de liste, structurile de tip dataframe sunt structuri de date bidimensionale (dreptunghiulare) a căror elemente pot fi accesate într-un mod similar cu modul de accesare a elementelor unei matrice, folosind doi indici (primul indice arată liniile iar al doilea coloanele).

mtcars[1:5, 1]
[1] 21.0 21.0 22.8 21.4 18.7
mtcars[1,1:4]
          mpg cyl disp  hp
Mazda RX4  21   6  160 110
mtcars[c(1,2),2]
[1] 6 6

La fel ca vectorii, dataframe-urile (dar și listele) pot fi indexate logic

mtcars[mtcars$mpg > 25, ]
                mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Fiat X1-9      27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
mtcars[(mtcars$mpg > 25) & (mtcars$wt < 1.8), ]
              mpg cyl disp  hp drat    wt  qsec vs am gear carb
Honda Civic  30.4   4 75.7  52 4.93 1.615 18.52  1  1    4    2
Lotus Europa 30.4   4 95.1 113 3.77 1.513 16.90  1  1    5    2

O altă metodă de indexare este prin folosirea funcției subset().

Tabelul 7: Principalele argumente ale funcției subset().
Argument Descriere
x Un dataframe
subset Un vector logic care indică liniile pe care le vrem
select Coloanele pe care vrem să le păstrăm

Să presupunem că dorim să afișăm variabilele disp și wt din setul de date mtcars pentru acele mașini care au mpg < 12 și cyl > 6. Dacă nu am folosi funcția subset() am putea scrie:

mtcars[mtcars$mpg < 12 & mtcars$cyl > 6, c("disp", "wt")]
                    disp    wt
Cadillac Fleetwood   472 5.250
Lincoln Continental  460 5.424

iar prin intermediul funcției subset() avem:

subset(x = mtcars,
      subset = mpg < 12 &
               cyl > 6,
      select = c(disp, wt))
                    disp    wt
Cadillac Fleetwood   472 5.250
Lincoln Continental  460 5.424

Observăm că numele variabilelor ce apar în cel de-al doilea argument al funcției nu mai includ și denumirea setului de date ca și în cazul indexării logice matriceale, acest fenomen are loc deoarece acestea sunt căutate, pentru început, în setul de date.

Uneori poate fi incomod să adăugăm numele setului de date (a listei sau a dataframe-ului) la numele variabilei pe care vrem să o accesăm și din acest motiv R pune la dispoziție o serie de funcții ajutătoare: attach(), detach() și with(). De exemplu să considerăm că avem un set de date cu un nume lung pentru care vrem să creăm o nouă variabilă folosindu-ne de cele existente:

nume_foarte_foarte_lung_df <- data.frame(A1 = 1:10, B1 = 5)

nume_foarte_foarte_lung_df$C1 <- 
  (nume_foarte_foarte_lung_df$A1 + nume_foarte_foarte_lung_df$B1)/nume_foarte_foarte_lung_df$A1

head(nume_foarte_foarte_lung_df, 3)
  A1 B1       C1
1  1  5 6.000000
2  2  5 3.500000
3  3  5 2.666667

Funcția attach() permite R-ului să adauge obiectul la calea de căutare (search path) astfel acesta să fie mai ușor accesat iar funcția detach() restabilește starea inițială.

attach(nume_foarte_foarte_lung_df)

nume_foarte_foarte_lung_df$C2 <- (A1 + B1)/A1

head(nume_foarte_foarte_lung_df, 3)
  A1 B1       C1       C2
1  1  5 6.000000 6.000000
2  2  5 3.500000 3.500000
3  3  5 2.666667 2.666667
detach(nume_foarte_foarte_lung_df)

Funcția with() primește ca prim argument setul de date și evaluează o expresie bazată pe date folosind doar numele acestora:

nume_foarte_foarte_lung_df$C3 <- with(nume_foarte_foarte_lung_df, 
                                      (A1 + B1)/A1)
head(nume_foarte_foarte_lung_df, 3)
  A1 B1       C1       C2       C3
1  1  5 6.000000 6.000000 6.000000
2  2  5 3.500000 3.500000 3.500000
3  3  5 2.666667 2.666667 2.666667

Metode de manipulare

În această secțiune vom prezenta câteva metode mai avansate de manipulare a seturilor de date (a data frame-urilor).

Vom începe prin introducerea comenzii order() care permite sortarea liniilor unui dataframe în funcție de valorile coloanei de interes (am văzut în Secțiunea 1.1.2 că funcția order() întoarce pozițiile corespunzătoare ordinii). De exemplu să considerăm cazul setului de date mtcars. Vrem să afișăm primele 10 mașini în funcție de greutatea lor (crescător și descrescător):

cars_increasing <- rownames(mtcars[order(mtcars$wt),])
# afisarea celor mai usoare 10 masini
cars_increasing[1:10]
 [1] "Lotus Europa"   "Honda Civic"    "Toyota Corolla" "Fiat X1-9"     
 [5] "Porsche 914-2"  "Fiat 128"       "Datsun 710"     "Toyota Corona" 
 [9] "Mazda RX4"      "Ferrari Dino"  
cars_decreasing <- rownames(mtcars[order(mtcars$wt, decreasing = TRUE),])
# afisarea celor mai grele 10 masini
cars_decreasing[1:10]
 [1] "Lincoln Continental" "Chrysler Imperial"   "Cadillac Fleetwood" 
 [4] "Merc 450SE"          "Pontiac Firebird"    "Camaro Z28"         
 [7] "Merc 450SLC"         "Merc 450SL"          "Duster 360"         
[10] "Maserati Bora"      

Funcția order() permite ordonarea după mai mult de o coloană, de exemplu dacă vrem să ordonăm mașinile după numărul de cilindrii și după greutate atunci apelăm

head(mtcars[order(mtcars$cyl, mtcars$wt), 1:6])
                mpg cyl  disp  hp drat    wt
Lotus Europa   30.4   4  95.1 113 3.77 1.513
Honda Civic    30.4   4  75.7  52 4.93 1.615
Toyota Corolla 33.9   4  71.1  65 4.22 1.835
Fiat X1-9      27.3   4  79.0  66 4.08 1.935
Porsche 914-2  26.0   4 120.3  91 4.43 2.140
Fiat 128       32.4   4  78.7  66 4.08 2.200

Sunt multe situațiile în care avem la dispoziție două sau mai multe seturi de date și am vrea să construim un nou set de date care să combine informațiile din acestea. Pentru aceasta vom folosi funcția merge(). Principalele argumente ale acestei funcții se regăsesc în tabelul de mai jos:

Tabelul 8: Argumentele funcției merge().
Argument Descriere
x, y Două data frame-uri ce urmează a fi unite
by Un vector de caractere ce reprezintă una sau mai multe coloane după care se va face lipirea. De exemplu by = "id" va combina coloanele care au valori care se potrivesc într-o coloană care se numește "id". by = c("last.name", "first.name") va combina coloanele care au valori care se potrivesc în ambele coloane "last.name" și "first.name"
all Un vector logic care indică dacă vrem să includem sau nu liniile care nu se potrivesc conform argumentului by.

Să presupunem că avem la dispoziție un set de date în care apar 5 studenți și notele pe care le-au obținut la examenul de statistică:

stat_course <- data.frame(student = c("Ionel", "Maria", "Gigel", "Vasile", "Ana"),
                         note_stat = c(9, 8, 5, 7, 9)) 

și să presupunem că avem notele acestor studenți la examenul de algebră

alg_course <- data.frame(student = c("Maria", "Ana" , "Gigel", "Ionel", "Vasile"),
                         note_alg = c(10, 8, 9, 7, 9)) 

Scopul nostru este să creăm un singur tabel în care să regăsim notele la ambele materii:

combined_courses <- merge(x = stat_course, 
                         y = alg_course,
                         by = "student")
combined_courses
  student note_stat note_alg
1     Ana         9        8
2   Gigel         5        9
3   Ionel         9        7
4   Maria         8       10
5  Vasile         7        9

O a treia funcție care joacă un rol important în manipularea data frame-urilor este funcția aggregate() care, după cum îi spune și numele, permite calcularea de funcții pe grupe de date din setul inițial. Argumentele principale ale acestei funcții sunt date în Tabelul 9 de mai jos:

Tabelul 9: Argumentele funcției aggregate().
Argument Descriere
formula O formulă de tipul y ~ x1 + x2 + ... unde y este variabila dependentă iar x1, x2, … sunt variabilele independente. De exemplu, salary ~ sex + age va agrega o coloană salary la fiecare combinație unică de sex și age
FUN O funcție pe care vrem să o aplicăm lui y la fiecare nivel al variabilelor independente. E.g. mean sau max.
data Data frame-ul care conține variabilele din formula
subset O submulțime din data pe care vrem să le analizăm. De exemplu, subset(sex == "f" & age > 20) va restrânge analiza la femei mai învârstă de 20 de ani.

Structura generală a funcției aggregate() este

aggregate(dv ~ iv, # dv este data, iv este grupul 
          FUN = fun, # Functia pe care vrem sa o aplicam
          data = df) # setul de date care contine coloanele dv si iv

Să considerăm setul de date ChickWeight și să ne propunem să calculăm pentru fiecare tip de dietă greutatea medie:

# Fara functia aggregate
mean(ChickWeight$weight[ChickWeight$Diet == 2])
[1] 122.6167
mean(ChickWeight$weight[ChickWeight$Diet == 3])
[1] 142.95
mean(ChickWeight$weight[ChickWeight$Diet == 4])
[1] 135.2627
# Cu ajutorul functiei aggregate
aggregate(weight ~ Diet,  # DV este weight, IV este Diet
          FUN = mean,               # calculeaza media pentru fiecare grup
          data = ChickWeight)       # dataframe este ChickWeight
  Diet   weight
1    1 102.6455
2    2 122.6167
3    3 142.9500
4    4 135.2627

Funcția aggregate() a întors un data.frame cu o coloană pentru variabila independentă Diet și o coloană pentru greutatea medie.

Dacă vrem să calculăm greutățile medii în funcție de dietă pentru găinile care au mai puțin de 10 săptămâni de viață atunci folosim opțiunea subset:

aggregate(weight ~ Diet,  # DV este weight, IV este Diet
          FUN = mean,               # calculeaza media pentru fiecare grup
          subset = Time < 10,       # gainile care au mai putin de 10 saptamani
          data = ChickWeight)       # dataframe este ChickWeight
  Diet   weight
1    1 58.03093
2    2 63.40000
3    3 65.94000
4    4 69.36000

Putem să includem de asemenea mai multe variabile independente în formula funcției aggregate(). De exemplu putem să calculăm greutatea medie a găinilor atât pentru fiecare tip de dietă cât și pentru numărul de săptămâni de la naștere:

df <- aggregate(weight ~ Diet + Time,  # DV este weight, IV sunt Diet și Time
          FUN = mean,               # calculeaza media pentru fiecare grup
          data = ChickWeight)       # dataframe este ChickWeight

head(df)
  Diet Time weight
1    1    0  41.40
2    2    0  40.70
3    3    0  40.80
4    4    0  41.00
5    1    2  47.25
6    2    2  49.40

Exerciții

Exercițiul 17 Creați un data.frame pe baza următorului tabel:

persoană sex simț umor
Stan \(M\) Ridicat
Felicia \(F\) Mediu
Ștefan \(M\) Scăzut
Radu \(M\) Ridicat
Ana \(F\) Ridicat
George \(M\) Mediu

Variabilele sex și simț umor sunt de tip factor (a se vedea Secțiunea 1.6).

  1. Adăugați o coloană cu vârsta, unde vârstele sunt \(41\), \(41\), \(15\), \(100\), \(21\) și respectiv \(60\)

  2. Reordonați coloanele după ordinea: persoană, vârstă, sex, nostim

  3. Creați un data.frame similar pe baza următorului tabel

persoană vârstă sex simț umor
Fane 42 \(M\) Ridicat
Roberta 37 \(F\) Mediu
Alex 19 \(M\) Scăzut
Bogdan 35 \(M\) Ridicat

și apoi uniți cele două seturi de date în data.frame-ul numit df

  1. Extrageți din df acele persoane de sex feminin care au un nivel de simț al umorului mediu sau ridicat.

Exercițiul 18 Considerați setul de date mtcars. Calculați:

  1. Greutatea medie în funcție de tipul de transmisie

  2. Greutatea medie în funcție de numărul de cilindrii

  3. Consumul mediu în funcție de numărul de cilindrii și tipul de transmisie

Exercițiul 19 Considerăm setul de date Seatbelts din pachetul datasets care face referire la numărul de șoferi accidentați mortal sau grav în Marea Britanie în perioada Ianuarie 1969 - Decembrie 1984.

  1. Vizualizați setul de date. Ce observați? Transformați setul de date într-un data.frame folosind funcția as.data.frame() și stocați rezultatul în variabila dat
  2. Creați două coloane la setul de date dat una pentru an (numită an) și alta pentru luna (numită luna) aferentă fiecărei observații.
  3. Determinați câți șoferi au murit între 1971 și 1983. Dar între Aprilie 1970 și Mai 1980.
  4. Creați un subset de date care conține informații despre șoferii accidentați mortal între 1975 și 1984.
  5. Determinați numărul maxim de șoferi accidentați din luna august între 1970 și 1984.
  6. Precizați care este diferența între numărul mediu de pasageri accidentați pe locuri din față și cel al pasagerilor de pe locurile din spate pentru fiecare an în parte.

Factori

Din punct de vedere conceptual, structurile de date de tip factor pot fi văzute ca vectori la care se adaugă o serie de informații suplimentare. În practică, aceste structuri de date sunt folosite pentru modelarea variabilelor categoriale (e.g. sex, etnie, naționalitate, culoarea ochilor, etc.), variabile care iau un număr finit de valori, și pot fi create atât din vectori numerici cât și din vectori de tip caracter. Factorii joacă un rol important în modelarea statistică din R deoarece funcțiile de modelare din R tratează în mod diferit datele de tip categorial față de cele de tip continuu. Spre exemplu în cazul modelelor liniare, tipul de model ajustat (fitted) este decis de tipul covariabilelor (variabilelor explicative): dacă acestea sunt de tip factor atunci avem ANOVA iar dacă sunt de tip numeric avem regresie.

Pentru a crea un factor se folosește funcția factor() care primește ca prim argument un vector (numeric sau de tip caracter) ce urmează să fie transformat în factor. Ceea ce caracterizează un factor în R este modul de stocare al acestuia: vectorul atomic ce urmează să fie transformat într-un factor este stocat ca un vector de numere întregi la care se adaugă un argument suplimentar levels (niveluri), o mulțime de valori de tip caracter folosite pentru ilustrarea vectorului. Astfel un factor nu include numai valorile variabilei categoriale corespunzătoare ci și diferitele niveluri (categorii) posibile ale acestei variabile (eventual și cele care nu sunt reprezentate în date).

x <- c(5, 7, 10, 7, 9)

xf <- factor(x)
xf
[1] 5  7  10 7  9 
Levels: 5 7 9 10
str(xf)
 Factor w/ 4 levels "5","7","9","10": 1 2 4 2 3

Putem observa din structura obiectului xf că acesta nu este stocat drept (5, 7, 10, 7, 9) ci mai degrabă ca (1,2,4,2,3). Vectorul (1,2,4,2,3) reprezintă modul de stocare pe niveluri din R a vectorului x. Nivelurile, dacă nu este specificat altfel, sunt date de valorile unice ale vectorului x sortate alfabetic "5","7","9","10". Astfel vectorul (1,2,4,2,3) ne spune că primul element (5) este de nivel 1 , al doilea (7) de nivel 2, al treilea (10) de nivel 4, al patrulea (7) de nivel 2 iar ultimul (9) de nivel 3. Pentru a transforma un factor într-un vector numeric (atunci când factorul a fost creat dintr-un vector numeric) nu este suficient să îl transformăm folosind funcția de conversie as.numeric:

as.numeric(xf)
[1] 1 2 4 2 3

ci trebuie să efectuăm doi pași: în primul rând transformăm factorul într-un șir de caractere (deoarece nivelurile sunt stocate drept șiruri de caractere) prin as.character() iar apoi convertim într-un vector numeric prin as.numeric(). Putem obține același lucru folosind și funcția levels():

as.numeric(as.character(xf))
[1]  5  7 10  7  9
# acelasi lucru se poate obtine si folosind functia levels
as.numeric(levels(xf)[xf])
[1]  5  7 10  7  9

Dacă dorim să schimbăm ordinea în care sunt afișate nivelurile putem adăuga argumentul levels = la funcția factor() specificând atât ordinea dorită dar și nivelurile. Același lucru se poate obține și utilizând funcția levels().

fh <- c("H", "F", "F", "F", "F", "H", "F", "H", "H", "F", "F", "F")
sex <- factor(fh)

# niveluri ordonate alfabetic
sex
 [1] H F F F F H F H H F F F
Levels: F H
#niveluri specificate 1
sex2 <- factor(fh, levels = c("H", "F"))
sex2
 [1] H F F F F H F H H F F F
Levels: H F
#niveluri specificate 2
sex3 <- factor(fh)
levels(sex3) <- c("H", "F")
sex3
 [1] F H H H H F H F F H H H
Levels: H F

De asemenea putem schimba și denumirea nivelurilor folosind argumentul labels = din funcția factor() sau folosind direct funcția levels():

sex4 <- factor(fh, levels = c("H", "F"), labels = c("Barbat", "Femeie"))
sex4
 [1] Barbat Femeie Femeie Femeie Femeie Barbat Femeie Barbat Barbat Femeie
[11] Femeie Femeie
Levels: Barbat Femeie
levels(sex4) <- c("B", "F")
sex4
 [1] B F F F F B F B B F F F
Levels: B F

Trebuie să fim atenți atunci când folosim funcția levels(), în exemplul anterior comanda levels(sex4) <- c("B", "F") îi cere R-ului să înlocuiască primul nivel al variabilei factor sex4 în B iar al doilea în F. Dacă am fi utilizat levels(sex4) <- c("F", "B") atunci am fi inversat cele două niveluri. Mai mult, fiecare termen al unui factor este restrâns la valorile determinate de nivelurile factorului ori la NA și putem avea factori care au mai multe niveluri definite decât ceea ce se observă în date (luna Iunie nu apare în date):

luni <- c("Martie","Aprilie","Ianuarie","Noiembrie","Ianuarie",
       "Septembrie","Octombrie","Septembrie","Noiembrie","August",
       "Ianuarie","Noiembrie","Noiembrie","Februarie","Mai","August",
       "Iulie","Decembrie","August","August","Septembrie","Noiembrie",
       "Februarie","Aprilie")

#fara sa specificam nivelurile
luni_f <- factor(luni)
table(luni_f)
luni_f
   Aprilie     August  Decembrie  Februarie   Ianuarie      Iulie        Mai 
         2          4          1          2          3          1          1 
    Martie  Noiembrie  Octombrie Septembrie 
         1          5          1          3 
#specificam nivelurile
luni_f <- factor(luni, levels = c("Ianuarie","Februarie","Martie",
                                  "Aprilie","Mai","Iunie","Iulie",
                                  "August","Septembrie", "Octombrie",
                                  "Noiembrie","Decembrie"))

table(luni_f)
luni_f
  Ianuarie  Februarie     Martie    Aprilie        Mai      Iunie      Iulie 
         3          2          1          2          1          0          1 
    August Septembrie  Octombrie  Noiembrie  Decembrie 
         4          3          1          5          1 

Dacă dorim să adăugăm la un factor o valoare care nu se regăsește printre nivelurile sale atunci apare o atenționare și valoarea se înlocuiește cu NA:

luni_f[length(luni_f) + 1] <- "May"
Warning in `[<-.factor`(`*tmp*`, length(luni_f) + 1, value = "May"): invalid
factor level, NA generated
luni_f
 [1] Martie     Aprilie    Ianuarie   Noiembrie  Ianuarie   Septembrie
 [7] Octombrie  Septembrie Noiembrie  August     Ianuarie   Noiembrie 
[13] Noiembrie  Februarie  Mai        August     Iulie      Decembrie 
[19] August     August     Septembrie Noiembrie  Februarie  Aprilie   
[25] <NA>      
12 Levels: Ianuarie Februarie Martie Aprilie Mai Iunie Iulie ... Decembrie

Putem menționa că funcția factor() permite și creare de factori ordonați corespunzători variabilelor ordinale (variabile calitative ale căror valori pot fi ordonate în mod natural dar pentru care nu este definită diferența dintre acestea: gradul de studii, clasa socială, gradul de satisfacție a unui serviciu, etc.) prin specificarea argumentului ordered = TRUE. În acest caz putem compara valorile prin utilizarea operatorilor logici de comparare:

luni_fo <- factor(luni, levels = c("Ianuarie","Februarie","Martie",
                                  "Aprilie","Mai","Iunie","Iulie",
                                  "August","Septembrie", "Octombrie",
                                  "Noiembrie","Decembrie"), 
                  ordered = TRUE)

luni_fo
 [1] Martie     Aprilie    Ianuarie   Noiembrie  Ianuarie   Septembrie
 [7] Octombrie  Septembrie Noiembrie  August     Ianuarie   Noiembrie 
[13] Noiembrie  Februarie  Mai        August     Iulie      Decembrie 
[19] August     August     Septembrie Noiembrie  Februarie  Aprilie   
12 Levels: Ianuarie < Februarie < Martie < Aprilie < Mai < Iunie < ... < Decembrie
luni_fo[1] < luni_fo[2]
[1] TRUE

Trebuie să avem grijă atunci când dorim să concatenăm două variabile care sunt factori folosind funcția c() în versiunile de R < 4.1.0 deoarece aceasta va interpreta cei doi factori ca pe doi vectori de numere întregi și pentru a combina cei doi factori trebuie să îi convertim la valorile originale. Pentru versiunile de R > 4.1.0 funcția c() permite concatenarea factorilor (atenție la ordinea nivelurilor):

x1 <- factor(sample(letters, 5, replace = TRUE))
x2 <- factor(sample(letters, 5, replace = TRUE))

# R < 4.1.0
factor(c(levels(x1)[x1], levels(x2)[x2]))
 [1] r o b x j a q q s e
Levels: a b e j o q r s x
# R>= 4.1.0
c(x1, x2)
 [1] r o b x j a q q s e
Levels: b j o r x a e q s

Atunci când forma nivelurilor unui factor este regulată putem folosi funcția gl() pentru a genera un factor cu niveluri specificate:

# un factor cu 3 niveluri si 4 repetari 
v <- gl(3, 4, labels = c("Bucuresti", "Iasi", "Cluj"))
v
 [1] Bucuresti Bucuresti Bucuresti Bucuresti Iasi      Iasi      Iasi     
 [8] Iasi      Cluj      Cluj      Cluj      Cluj     
Levels: Bucuresti Iasi Cluj

O funcție utilă în convertirea unei variabile numerice într-o variabilă de tip factor este funcția cut(). Argumentul breaks = asigură specificarea numărului de subintervale (niveluri) în care se dorește împărțirea variabilei sau a capetelor acestor intervale. Argumentul labels = permite denumirea nivelurilor variabilei factor obținute:

# intervalul de valori
r <- range(mtcars$mpg)

# utilizarea functiei cut cu 3 intervale
cut(mtcars$mpg, breaks = 3)
 [1] (18.2,26.1] (18.2,26.1] (18.2,26.1] (18.2,26.1] (18.2,26.1] (10.4,18.2]
 [7] (10.4,18.2] (18.2,26.1] (18.2,26.1] (18.2,26.1] (10.4,18.2] (10.4,18.2]
[13] (10.4,18.2] (10.4,18.2] (10.4,18.2] (10.4,18.2] (10.4,18.2] (26.1,33.9]
[19] (26.1,33.9] (26.1,33.9] (18.2,26.1] (10.4,18.2] (10.4,18.2] (10.4,18.2]
[25] (18.2,26.1] (26.1,33.9] (18.2,26.1] (26.1,33.9] (10.4,18.2] (18.2,26.1]
[31] (10.4,18.2] (18.2,26.1]
Levels: (10.4,18.2] (18.2,26.1] (26.1,33.9]
# utilizarea functiei cut cu 3 intervale folosind comanda pretty
cut(mtcars$mpg, breaks = pretty(mtcars$mpg, 3))
 [1] (20,30] (20,30] (20,30] (20,30] (10,20] (10,20] (10,20] (20,30] (20,30]
[10] (10,20] (10,20] (10,20] (10,20] (10,20] (10,20] (10,20] (10,20] (30,40]
[19] (30,40] (30,40] (20,30] (10,20] (10,20] (10,20] (10,20] (20,30] (20,30]
[28] (30,40] (10,20] (10,20] (10,20] (20,30]
Levels: (10,20] (20,30] (30,40]
# utilizarea functiei cut cu borne specificate
cut(mtcars$mpg, breaks = seq(r[1]-0.1, r[2]+0.1, by = 0.1))
 [1] (20.9,21]   (20.9,21]   (22.7,22.8] (21.3,21.4] (18.6,18.7] (18,18.1]  
 [7] (14.2,14.3] (24.3,24.4] (22.7,22.8] (19.1,19.2] (17.7,17.8] (16.3,16.4]
[13] (17.2,17.3] (15.1,15.2] (10.3,10.4] (10.3,10.4] (14.6,14.7] (32.3,32.4]
[19] (30.3,30.4] (33.8,33.9] (21.4,21.5] (15.4,15.5] (15.1,15.2] (13.2,13.3]
[25] (19.1,19.2] (27.2,27.3] (25.9,26]   (30.3,30.4] (15.7,15.8] (19.6,19.7]
[31] (14.9,15]   (21.3,21.4]
237 Levels: (10.3,10.4] (10.4,10.5] (10.5,10.6] (10.6,10.7] ... (33.9,34]
# utilizarea optiunii labels
cut(mtcars$hp, breaks = 3, labels = c("low", "medium", "high"))
 [1] low    low    low    low    medium low    high   low    low    low   
[11] low    medium medium medium medium medium medium low    low    low   
[21] low    medium medium high   medium low    low    low    high   medium
[31] high   low   
Levels: low medium high

Exerciții

Exercițiul 20 Profesorii \(A\) și \(B\) colectează notele și sexul a \(10\) studenți fiecare și le stochează în următorii vectori:

A_note <- c("A","B","B","D","A","A","C","A","B","B")
A_sex <- c(1,1,1,0,1,0,0,0,1,1)  # aici 1 reprezinta sexul masculin si 0 feminin
B_note <- c(97,93,92,57,75,90,72,88,82,60)
B_sex <- c("M","F","F","M","M","M","F","F","F","M")
  1. Convertiți A_note într-un vector de tip factor A_note_fac cu nivelele ordonate \(A>B>C>D>F\)

  2. Convertiți B_note într-un vector de tip factor B_note_fac cu nivelele ordonate \(A>B>C>D>F\) presupunâbd că avem următoarea corespondență

\[ \begin{array}{cc} A & 90-100 \\ B & 80-89 \\ C & 70-79 \\ D & 60-69 \\ F & 0-59 \end{array} \]

  1. Convertiți A_sex într-un vector de tip factor A_sex_fac cu nivelele \(M\) și \(F\)

  2. Convertiți B_sex într-un vector de tip factor B_sex_fac cu nivelele \(M\) și \(F\)

  3. Combinați cei doi vectori A_note_fac și B_note_fac într-un singur vector note.fac. De asemenea, combinați vectorii A_sex_fac și B_sex_fac într-un singur vector sex.fac.

  4. Creați următorul tabel

A B C D F Sum
F 4 2 2 1 0 9
M 4 4 1 1 1 11
Sum 8 6 3 2 1 20