3 Vektori, matrice i liste
3.1 Vektori
Vektor je jedan od “složenih” tipova podataka u jeziku R, u smislu da sadržava više vrijednosti istog tipa. On je kao takav sličan pojmu “polja” u jeziku C. No ovdje postoji jedna bitna razlika, koju je nužno usvojiti budući da se radi o jednoj od najvažnijih karakteristika jezika R - u R-u je (gotovo) svaki tip varijable zapravo vektor. Čak i varijable i konstante koje smo koristili u prethodnoj lekciji su zapravo bili jednoelementni vektori. Ovo ima dalekosežne posljedice o kojima ćemo detaljno raspravljati u nastavku, a za početak se prvo upoznajmo sa sintaksom stvaranja i upravljanja vektorima.
3.1.1 Stvaranje vektora
Novi vektor (koji ima više od jednog elementa) stvaramo uz pomoć funkcije c
(od engl. combine).
# numerički vektor
m <- c(1, 2, 3, 4, 5)
# logički vektor
v <- c(T, F, T)
# znakovni vektor
imena <- c("Ivo", "Pero", "Ana")
Dakle, jednostavno rečeno, vektor je uređeni skup elemenata istog tipa. Ovo konkretno znači da svi elementi vektora moraju biti istog tipa. Ako stvaramo novi vektor sa elementima različitih tipova podataka, R će sve elemente automatski pretvoriti u “najjači” tip, što će na kraju postati i tip samog vektora (termin “jači” tip u ovom kontekstu označavaju mogućnost tipa da pohrani svu informaciju “pohranjenu u slabiji”slabijeg"" tipa, a u općenitom slučaju pretvorba ide u smjeru logički -> numerički -> znakovni tip).
Zadatak 3.1 - stvaranje vektora
# stvorite novi vektor `x` sa četiri proizvoljna elementa sljedećih tipova:
# logički, realni, znakovni i cjelobrojni
# ispišite na zaslon sadržaj vektora i njegovu klasu
# stvorite novi vektor `x` sa četiri proizvoljna elementa sljedećih tipova:
# logički, realni, znakovni i cjelobrojni
x <- c(T, 1.25, "Ivo", 10L)
# ispišite na zaslon sadržaj vektora i njegovu klasu
x
class(x)
## [1] "TRUE" "1.25" "Ivo" "10"
## [1] "character"
Vektor možemo eksplicitno pretvoriti u drugi tip uz pomoć već upoznatih funkcija as.<naziv_tipa>
. Ukoliko je pretvorbu nemoguće provesti element će biti pretvoren u NA
uz prikladno upozorenje.
Zadatak 3.2 - eksplicitna pretvorba tipa vektora
x <- c(1, T, 2L)
y <- c(1L, 2L, 3L)
z <- c(1.25, TRUE, "Ana" )
# razmislite o mogućem rezultatu a potom pokušajte izvršiti sljedeće pretvorbe
# vektor `x` u numerički tip
# vektor `y` u znakovni tip
# vektor `z` u cjelobrojni tip
# razmislite o mogućem rezultatu a potom pokušajte izvršiti sljedeće pretvorbe
# vektor `x` u numerički tip
# vektor `y` u znakovni tip
# vektor `z` u cjelobrojni tip
as.numeric(x)
as.character(y)
as.integer(z)
## Warning: NAs introduced by coercion
## [1] 1 1 2
## [1] "1" "2" "3"
## [1] 1 NA NA
Možete li odgovoriti na pitanje - zašto u zadnjem primjeru vrijednost TRUE
nije postala 1L
već NA
? Pokušajte ispisati vektor z
i uočite rezultate implicitne pretvorbe koju ste možda zanemarili (a koja je logičku vrijednost TRUE
pretvorila u niz znakova "TRUE"
kojeg više nije moguće “vratiti” u numeričku vrijednost 1L
).
Funkcijom c
možemo također i više vektora spojiti u jedan:
a <- c(1, 2, 3)
b <- c(4, 5)
c <- c(6, 7, 8) # varijablu smijemo nazvati "c" usprkos tome što postoji funkcija c()
d <- c(a, b, c) # d je sada c(1, 2, 3, 4, 5, 6, 7, 8)
Pored funkcije c
, R nudi i dodatne pogodne načine stvaranja novih vektora:
:
- operator “raspona” (engl. range), pri čemu dajemo raspon od gornje do donje granice, obje uključiveseq
- funkcija sekvence (engl. sequence), radi slično operatoru raspona, ali s dodatnim mogućnostimarep
- funkcija repliciranja (engl. replicate), ponavlja zadane elemente zadani broj puta
Zadatak 3.3 - pomoćne funkcije za stvaranje vektora
# ispišite rezultate sljedećih naredbi
# 1:5
# rep(c(1, 2, 3), times = 3)
# rep(c(1, 2, 3), each = 3)
# seq(1, 5, by = 0.5)
# ispišite rezultate sljedećih naredbi
1:5
rep(c(1, 2, 3), times = 3)
rep(c(1, 2, 3), each = 3)
seq(1, 5, by = 0.5)
## [1] 1 2 3 4 5
## [1] 1 2 3 1 2 3 1 2 3
## [1] 1 1 1 2 2 2 3 3 3
## [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0
Vektore možemo stvoriti i uz pomoć funkcija koje odgovaraju imenima tipova vektora (numeric
, character
i sl.) pri čemu kao parametar navodimo željenu duljinu vektora. Ovo često radimo kao “pripremu” vektora za naknadno punjenje stvarnim vrijednostima, tj. svojevrsnu rezervaciju mjesta u radnoj memoriji. Ono što je interesantno jest činjenica da možemo stvoriti i “prazan” vektor određenog tipa koji je i dalje vektor, samo sa duljinom nula (a kojem npr. uz pomoć funkcije c
možemo naknadno dodavati elemente).
x <- numeric(2) # vektor se puni "nultim" elementima, u ovom slučaju (0, 0)
y <- character(5)
z <- integer(0) # "prazan" vektor!
z <- c(z, 1) # dodaj vektoru element 1 (zapravo "spoji prazan vektor i element 1")
Konačno, provjeru da li neki vektor sadrži određeni element možemo napraviti uz pomoć operatora %in%
:
Pogledajmo sada kako pristupiti pojedinim elementima vektora
3.1.2 Operator [
Elementima vektora pristupamo preko indeksnog operatora [
, uz pomoć kojeg možemo i mijenjati elemente vektora:
a <- c(2, 4, 6)
a[1] # ispisuje vrijednost 2
a[2] <- 5 # element na 2. mjestu postaje 5
a[5] <- 7 # na 5. mjesto dodaje se 7, a "rupa" se popunjava sa NA
a
## [1] 2
## [1] 2 5 6 NA 7
Uočite jednu pomalo neuobičajenu činjenicu - prvi element vektora u R-u ima indeks 1, a ne 0! Ovo je bitna razlika u odnosu na referenciranje elemenata u drugim programskim jezicima. Razlog ove specifičnosti je jednostavan - R se primarno smatra jezikom za analizu podataka, poglavito u tabličnom obliku, a u praksi je puno lakše brojati retke ili stupce redoslijedom kako se pojavljuju u podatkovnom skupu nego raditi “posmak za 1”.
Primjer gore zapravo prikazuje vrlo pojednostavljeni slučaj pristupanja elementima vektora i način njihove izmjene. Naime, jedna od specifičnosti jezika R je tzv. vektoriziranost, tj. princip da se u R-u vrlo često radi “više stvari odjednom” - ne toliko u smislu paralelnog izvršavanja, već u smislu zadavanja naredbi što želimo da se izvede. Konkretno, u slučaju referenciranja elemenata vektora vrlo rijetko dohvaćamo ili mijenjamo elemente jedan po jedan, već obuhvaćamo veći broj elemenata odjednom korištenjem principa vektorizacije i recikliranja. Razumijevanje ovih pojmova presudno je za svladavanje jezika R, tako da ćemo ih detaljno objasniti u nastavku.
3.1.3 Principi vektorizacije i recikliranja
Pojam vektorizacije ili bolje rečeno vektoriziranih operacija i funkcija jednostavno znači da se operacije rade nad više elemenata odjednom. Ako zadamo R-u da radi neku operaciju ili funkciju nad nekim vektorom vrijednosti, R će funkciju ili operaciju izvesti nad svakim elementom posebno i vratiti rezultantni vektor kao rezultat. Isto tako, ako provodimo binarnu operaciju nad dva vektora, ona će se provesti nad “uparenim” ili “poravnatim” elementima obaju vektora (pretpostavimo za sada da su vektori jednake duljine).
Zadatak 3.4 - princip vektorizacije
x <- seq(-5, 5, 1)
a <- 1:3
b <- 4:6
# pozovite funkciju `abs` za računanje apsolutne vrijednosti
# nad vektorom `x` i ispišite rezultat
# zbrojite vektore `a` i `b` uz pomoć operatora `+`
# i ispišite rezultat
# pomnožite vektore `a` i `b` uz pomoć operatora `*`
# i ispišite rezultat
# pozovite funkciju `abs` za računanje apsolutne vrijednosti
# nad vektorom `x` i ispišite rezultat
abs(x)
cat("-----------\n")
# zbrojite vektore `a` i `b` uz pomoć operatora `+`
# i ispišite rezultat
a + b
cat("-----------\n")
# pomnožite vektore `a` i `b` uz pomoć operatora `*`
# i ispišite rezultat
a * b
## [1] 5 4 3 2 1 0 1 2 3 4 5
## -----------
## [1] 5 7 9
## -----------
## [1] 4 10 18
Pažljivo razmotrite rezultate prethodnog zadatka. Ukoliko je potrebno, skicirajte vektore a
i b
na papiru sa vertikalno poslaganim elementima i uočite kako radi paralelno “uparivanje” elemenata. Primijetite da ovdje ne pričamo o “vektorskim operacijama” u strogom matematičkom smislu, već o poravnavanju elemenata dvaju nizova i provođenja jednostavnih operacija nad svakim od tih parova. Ovo je pogotovo očito u zadnjem primjeru gdje nema nikakvog “množenja vektora” u nekoj od matematičkih interpretacija, već se provodi jednostavno množenje paralelnih elemenata dvaju vektora.
Što ako vektori nisu jednake duljine? R u ovom slučaju koristi princip recikliranja.
Princip recikliranja navodi da se kod nejednake duljine vektora kraći vektor “reciklira” onoliko puta koliko je potrebno da se dostigne duljina duljeg vektora. Najčešći scenarij korištenja ovog principa su operacije u kojima je s jedne strane vektor s više elemenata a s druge strane jednoelementni vektor koji se onda reciklira za svaki element “velikog” vektora. Ono što bismo trebali izbjegavati jest scenarij recikliranja gdje duljina “velikog” vektora nije višekratnik duljine “malog” - R će i dalje reciklirati kraći vektor, samo će ga na kraju morati “odrezati” što će rezultirati odgovarajućim upozorenjem.
Zadatak 3.5 - princip recikliranja
a <- 1:4
b <- c(1, 2)
c <- rep(5, 3)
# udvostručite elemente vektora `a` i ispišite rezultat
# podijelite vektor `a` vektorom `b` i ispišite rezultat
# pomnožite vektore `a` i `c` i ispišite rezultat
a <- 1:4
b <- c(1, 2)
c <- rep(5, 3)
# udvostručite elemente vektora `a` i ispišite rezultat
2 * a
# podijelite vektor `a` vektorom `b` i ispišite rezultat
a / b
# pomnožite vektore `a` i `c` i ispišite rezultat
a * c
## Warning in a * c: longer object length is not a multiple of shorter object
## length
## [1] 2 4 6 8
## [1] 1 1 3 2
## [1] 5 10 15 20
Sada konačno možemo demistificirati razliku između “skalarnih” i “vektorskih” logičkih operatora (podsjetimo se, skalarni su ||
i &&
, dok su vektorski |
i &
).
Skalarni logički operatori namijenjeni su korištenju sa jednoelementnim vektorima, vraćaju jedinstvenu vrijednosti
TRUE
iliFALSE
te su pogodni za korištenje raznim u uvjetnim izrazima.Vektorski logički operatori koriste standardne R-ove principe vektorizacije i recikliranja, tj. namijenjeni su radu sa logičkim vektorima i kao rezultat daju logički vektor
Zadatak 3.6 - skalarni i vektorski logički operatori
a <- c(T, F, F)
b <- c(T, T, F)
# primjenite skalarnu i vektorsku inačicu logičkog operatora "ili"
# nad vektorima `a` i `b` i ispišite rezultat
# primjenite skalarnu i vektorsku inačicu logičkog operatora "ili"
# nad vektorima `a` i `b` i ispišite rezultat
a || b
a | b
## [1] TRUE
## [1] TRUE TRUE FALSE
Vidimo da će skalarna inačica “iskoristiti” samo prvi par elemenata logičkih vektora. Ovo znači da ju u teoriji možemo koristiti u uvjetnim izrazima, iako za to nema opravdanog smisla, a R će se u tom slučaju oglasiti upozorenjem kako bi nam obratio pažnju na činjenicu da vjerojatno koristimo “krivi” operator.
Sljedeći primjer sa usporednim operatorima će možda inicijalno izgledati trivijalan, no potrebno je obratiti posebnu pažnju na rezultate koje ćemo dobiti budući da će oni imati vrlo važnu primjenu u nastavku lekcije. Dakle, pogledajmo što se događa kod vektorizacije usporednih operatora.
Zadatak 3.7 - vektorizacija usporednih operatora
x <- 1:5
y <- seq(-10, 10, 5)
#ispišite x i y
#ispišite rezultat naredbe x > y i objasnite rezultat
#ispišite rezultat naredbe x < 3 i objasnite rezultat
#ispišite x i y
x
y
cat("-----------\n")
#ispišite rezultat naredbe x > y i objasnite rezultat
x > y
cat("-----------\n")
#ispišite rezultat naredbe x < 3 i objasnite rezultat
x < 3
## [1] 1 2 3 4 5
## [1] -10 -5 0 5 10
## -----------
## [1] TRUE TRUE TRUE FALSE FALSE
## -----------
## [1] TRUE TRUE FALSE FALSE FALSE
Dakle vektoriziranom primjenom usporednih operatora nad vektorima (ili kombinacijama vektora i “skalara”) kao rezultat dobivamo logičke vektore. Interpretacija ovih rezultata je ključna - ona zapravo odgovara na pitanje “na kojim indeksima je zadovoljen uvjet zadan ovim izrazom”? Drugim riječima, dobiveni rezultati zapravo predstavljaju predložak koji opisuje kako filtrirati elemente prema zadanom principu. Ovo je osnovni temelj tzv. logičkog referenciranja, što je jedna od metoda dohvaćanja elemenata vektora koje ćemo upoznati u nastavku.
3.2 Indeksni vektori
Već smo naučili da elementu polja možemo pristupiti preko numeričkog indeksa (a nismo zaboravili ni činjenicu da prvi element ima indeks 1). Ovaj koncept možemo proširiti tako da iz vektora uzimamo više elemenata odjednom. što se često naziva “rezanjem” vektora (engl. slicing).
Osnovni princip odabira više elemenata odjednom je jednostavan - samo moramo na određeni način navesti indekse elemenata koje želimo. R nudi tri osnovna načina referenciranja:
- lokacijsko referenciranje (engl. integer- or location-based referencing)
- uvjetno referenciranje (engl. conditional- or boolean-based referencing)
- imensko referenciranje (engl. label-based referencing)
Koje referenciranje ćemo odabrati ovisi o tome želimo li elementima pristupati ovisno o njihovoj lokaciji, imenu ili prema zadanom uvjetu, a svaki tip referenciranja u suštini se svodi na korištenje vektora određenog tipa kao parametra za operator referenciranja. Ovakav vektor se zbog svoje uloge naziva “indeksnim vektorom”.
Upoznajmo detaljno svaki od tipova referenciranja.
3.2.1 Lokacijsko referenciranje
Lokacijsko referenciranje je poopćenje već upoznatog principa referenciranja gdje navodimo redni broj elementa koji nas zanima. Ako želimo više elemenata, jednostavno navedemo njihove indekse “zapakirane” u numerički vektor.
Pokušajte riješiti sljedeći zadatak korištenjem odgovarajućih numeričkih vektora kao parametara indeksnog operatora.
Zadatak 3.8 - lokacijsko referenciranje
x <- 1:10
# ispišite prvi element vektora x
# ispišite prva tri elementa vektora x
# ispišite prvi, peti i sedmi element vektora x
# ispišite prvi element vektora x
x[1]
# ispišite prva tri elementa vektora x
x[1:3]
# ispišite prvi, peti i sedmi element vektora x
x[c(1,5,7)]
## [1] 1
## [1] 1 2 3
## [1] 1 5 7
Dakle, lokacijski indeksni vektor nije ništa drugo nego običan numerički vektor kojeg koristimo zajedno sa indeksnim operatorom da bi odredili koje elemente nekog drugog vektora želimo “zadržati”.
Pogledajmo još neke značajke lokacijskih indeksnih vektora:
Zadatak 3.9 - lokacijsko referenciranje (2)
x <- 1:10
# odgovorite na sljedeća pitanja uz pomoć prikladnog primjera
# što vraća indeks 0?
# što vraća negativni indeks?
# što vraća indeks izvan granica duljine vektora
x <- 1:10
# odgovorite na sljedeća pitanja uz pomoć prikladnog primjera
# što vraća indeks 0?
x[0]
# što vraća negativni indeks?
x[-1]
# što vraća indeks izvan granica duljine vektora
x[20]
## integer(0)
## [1] 2 3 4 5 6 7 8 9 10
## [1] NA
Indeksni operator se ne koristi samo za dohvaćanje elemenata. Kombinacijom indeksnog operatora i operatora pridruživanja možemo mijenjati elemente vektora (i to također po principu “više elemenata odjednom”:
Zadatak 3.10 - lokacijsko referenciranje i pridruživanje
a <- 1:10
# postavite sve elemente vektora `a` od drugog do osmog mjesta na nulu
# ispišite vektor `a`
b <- 1:20
b[2 * 1:5] <- 0
# razmislite kako izgleda vektor `b` nakon gornje naredbe
# ispišite vektor `b` i objasnite rezultat
a <- 1:10
# postavite sve elemente vektora `a` od drugog do osmog mjesta na nulu
# ispišite vektor `a`
a[2:8] <- 0
a
b <- 1:20
b[2 * 1:5] <- NA
# razmislite kako izgleda vektor `b` nakon gornje naredbe
# ispišite vektor `b` i objasnite rezultat
b
## [1] 1 0 0 0 0 0 0 0 9 10
## [1] 1 NA 3 NA 5 NA 7 NA 9 NA 11 12 13 14 15 16 17 18 19 20
3.2.2 Uvjetno referenciranje
Ako smo pažljivo razmotrili rezultate dobivene kod primjera sa vektoriziranim usporednim operatorima onda smo mogli vrlo dobro naslutiti kako radi uvjetno referenciranje. Princip je jednostavan - za indeksni vektor postavljamo logički vektor iste duljine kao i vektor čije elemente želimo dohvatiti. Elementi logičkog vektora određuju koje elemente zadržavamo (pozicije gdje se nalazi vrijednost TRUE
) a koje odbacujemo (pozicije gdje se nalazi vrijednost FALSE
).
Zadatak 3.11 - uvjetno referenciranje
x <- 1:10
# napravite logički vektor `y` duljine 10 sa proizvoljnom kombinacijom
# vrijednosti TRUE i FALSE
# indeksirajte vektor `x` vektorom `y`, ispišite i objasnite rezultat
# ispišite sve elemente vektora `x` manje ili jednake 5
# kao logički indeksni vektor upotrijebite odgovarajući izraz
# koji koristi usporedni operator
x <- 1:10
# napravite logički vektor `y` duljine 10 sa proizvoljnom kombinacijom
# vrijednosti TRUE i FALSE
y <- c(T, T, F, T, F, F, F, T, F, T)
# indeksirajte vektor `x` vektorom `y`, ispišite i objasnite rezultat
x[y]
# ispišite sve elemente vektora `x` manje ili jednake 5
# kao logički indeksni vektor upotrijebite odgovarajući izraz
# koji koristi usporedni operator
x[x <= 5]
## [1] 1 2 4 8 10
## [1] 1 2 3 4 5
Zadnja naredba, naoko jednostavna, predstavlja jedan od ključnih principa odabira elemenata u jeziku R. Kombinacija indeksnog operatora i uvjetnog izraza predstavlja sažet ali vrlo moćan mehanizam rezanja vektora prema odabranom kriteriju.
Isprobajmo ovaj princip na još nekoliko primjera.
Zadatak 3.12 - uvjetno referenciranje
y <- seq(1, 100, 7)
studenti <- c("Ivo", "Petra", "Marijana", "Ana", "Tomislav", "Tin")
# ispišite vektor koji sadrži sve parne, a potom sve neparne elemente vektora `y`
# ispišite sve elemente vektora `studenti` koji predstavljaju imena od 3 slova
# (napomena: za prebrojavanje slova znakovnog niza u R-u koristimo funkciju `nchar`)
y <- seq(1, 100, 7)
studenti <- c("Ivo", "Petra", "Marijana", "Ana", "Tomislav", "Tin")
# ispišite vektor koji sadrži sve parne, a potom sve neparne elemente vektora `y`
c(y[y %% 2 == 0], y[y %% 2 != 0])
# ispišite sve elemente vektora `studenti` koji predstavljaju imena od 3 slova
# (napomena: za prebrojavanje slova znakovnog niza u R-u koristimo funkciju `nchar`)
studenti[nchar(studenti) == 3]
## [1] 8 22 36 50 64 78 92 1 15 29 43 57 71 85 99
## [1] "Ivo" "Ana" "Tin"
Ukoliko koncept uvjetnog referenciranja uz pomoć uvjetnih izraza i dalje nije jasan, jedna od stvari koje mogu pomoći jest skiciranje “međurezultata” - jednostavno na papir ispišite rezultat izraza unutar uglatih zagrada indeksnog operatora i potom razmislite kako taj rezultat utječe na konačno rješenje.
Preostao nam je još samo zadnji tip referenciranja koji radi na principu dohvaćanja elemenata vektora ovisno o njihovom imenu.
3.2.3 Imensko referenciranje
Imensko referenciranje radi na principu eksplicitnog imenovanja elemenata koje želimo “zadržati”. Da bi mogli koristiti ovakav tip referenciranja moramo zadovoljiti nužan preduvjet - elementi vektora moraju imati definirana “imena”.
Vektori koje smo do sada koristili nisu imali imenovane elemente. Svaki element imao je svoju predefiniranu poziciju unutar vektora te svoju vrijednost, ali nije imao nikakav poseban dodatni identifikator. Programski jezik R dopušta pridavanje imena elementima vektora na vrlo jednostavan način - korištenjem funkcije names
, operatora pridruživanja te znakovnog vektora sa odabranim imenima. Moramo voditi računa da vektor imena bude jednake duljine kao originalni vektor!
Zadatak 3.13 - imensko referenciranje
visine <- c(165, 173, 185, 174, 190)
names(visine) <- c("Marica", "Pero", "Josip", "Ivana", "Stipe")
# ispišite vektor `visine`
# ispišite koliko su visoki Pero i Ivana
visine <- c(165, 173, 185, 174, 190)
names(visine) <- c("Marica", "Pero", "Josip", "Ivana", "Stipe")
# ispišite vektor `visine`
visine
# ispišite koliko su visoki Pero i Ivana
visine[c("Pero", "Ivana")]
## Marica Pero Josip Ivana Stipe
## 165 173 185 174 190
## Pero Ivana
## 173 174
Vidimo da se imensko referenciranje očekivano svodi na prosljeđivanje odgovarajućeg znakovnog vektora kao parametra referenciranja.
(NAPOMENA: Pažljiviji čitatelj uočiti će jednu neobičnu činjenicu u gornjem programskom kodu - poziv funkcije se koristi kao lvalue
! Odgovor na pitanje zašto je ovo moguće zahtijeva malo više znanja o internom funkcioniranju jezika R, a za sada je dovoljno reći da se ovdje zapravo radi o pozivu funkcije pravog imena names<-
koji se “skriva” iza puno intuitivnije i lako razumljive sintakse)
Ukoliko iz nekog razloga poželimo obrisati imena elemenata vektora, jednostavno pozivu funkcije names
proslijedimo NULL
.
Ovime ćemo zaključiti priču o vektorima. Naučili smo različite načine stvaranja vektora te dohvaćanja i izmjene njegovih elemenata. Sada je vrijeme da pokušamo vektorima dodati dodatnu “dimenziju” - upoznajmo matrice i polja.
3.3 Matrice i polja
Matrice i polja su, jednostavno rečeno, višedimenzionalni vektori. Matrica (engl. matrix) je tako vektor sa dvije dimenzije, tj. vektor koji elemente smiješta u “retke” i “stupce”. Polje (engl. array) je vektor sa tri ili više dimenzija. Dok se matrice relativno često koriste u praksi, polja su ipak nešto više ograničena na posebne scenarije. Zbog ove činjenice u ovom poglavlju uglavnom ćemo se baviti matricama, iako se prikazani koncepti vrlo lako poopćuju na polja.
Ono što je zajedničko matricama i poljima, a što je poznata činjenica čitateljima sa programerskim iskustvom, jest da je njihova višedimenzionalnost zapravo prividna. I matrice i polja su zapravo jednodimenzionalni vektori kojima je dodan atribut dimenzionalnosti, a uz pomoć tog atributa jezik R mapira naše višedimenzionalno referenciranje u “stvarni” indeks elementa jednodimenzionalnog vektora. Ova činjenica nas ne ograničava - mi i dalje možemo u većini slučajeva tretirati matricu kao da je zaista dvodimenzionalna, a znanje o jednodimenzionalnoj prirodi nam može samo dati dodatnu fleksibilnost u radu s matricama.
Postoji nekoliko načina stvaranja nove matrice:
- uz pomoć funkcije
matrix
kojoj prosljeđujemo jednodimenzionalni vektor i željeni broj redaka i stupaca kroz parametrenrow
incol
- “ručnim” postavljanjem dimenzija jednodimenzionalnog vektora uz pomoć funkcije
dim
i pridruživanja dvoelementnog numeričkog vektora sa dimenzijama matrice - “ljepljenjem” jednodimenzionalnih vektora koji predstavljaju retke ili stupce nove matrice uz pomoć funkcija
rbind
(engl. row bind) icbind
(engl. column bind)
Demonstrirajmo ove načine u primjerima koji slijede.
Zadatak 3.14 - funkcija matrix
x <- 1:12
# uz pomoć funkcije `matrix` i stvorite matricu sa 3 retka i 4 stupca
# koja sadrži elemente vektora `x`
# ispišite rezultat na zaslon
# ponovite postupak ali pozivu funkcije dodajte parametar `byrow = T`
# ispišite rezultat na zaslon i usporedite s prethodnim rezultatom
# uz pomoć funkcije `matrix` stvorite matricu sa 3 retka i 4 stupca
# koja sadrži elemente vektora `x`
# ispišite rezultat na zaslon
matrix(x, nrow = 3, ncol = 4)
# ponovite postupak ali pozivu funkcije dodajte parametar `byrow = T`
# ispišite rezultat na zaslon i usporedite s prethodnim rezultatom
matrix(x, nrow = 3, ncol = 4, byrow = T)
## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12
## [,1] [,2] [,3] [,4]
## [1,] 1 2 3 4
## [2,] 5 6 7 8
## [3,] 9 10 11 12
Uočite da ga ukoliko eksplicitno ne zamolimo drugačije, R matricu popunjava po stupcima. Ovo je napravljeno zbog sličnosti matrice sa tabličnim prikazom podataka koje najčešće analiziramo gledajući pojedine stupce. No budući da nam je često punjenje po retcima “prirodnije”, ne smijemo zaboraviti na vrlo korisni parametar byrow
.
Zadatak 3.15 - funkcija dim
m <- 1:10
# ispišite rezultat poziva funkcije `dim` nad vektorom `m`
# pozivu funkcije `dim` nad vektorom `m` pridružite vektor c(2, 5)
# ispišite `m` i komentirajte rezultat
# ispišite rezultate poziva funkcija `nrow` i `ncol` nad matricom `m`
m <- 1:10
# ispišite rezultat poziva funkcije `dim` nad vektorom `m`
dim(m)
# pozivu funkcije `dim` nad vektorom `m` pridružite vektor c(2, 5)
dim(m) <- c(2, 5)
# ispišite `m` i komentirajte rezultat
m
# ispišite rezultate poziva funkcija `nrow` i `ncol` nad matricom `m`
nrow(m)
ncol(m)
## NULL
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 3 5 7 9
## [2,] 2 4 6 8 10
## [1] 2
## [1] 5
Vidimo da “običan” vektor zapravo nema dimenziju, što se očituje preko NULL
vrijednosti koju smo dobili kao rezultat. Pozivom funkcije dim
mi vektoru zapravo dodajemo atribut naziva dim
čime on formalno postaje matrica (tj. polje u općenitom slučaju). Upravo dimenzije su te koje definiraju kako su elementi složeni po retcima i stupcima a postavljanjem dimenzija moramo biti oprezni da one odgovaraju trenutnom broju elemenata. Jednom kad matrica ima dodane dimenzije, možemo ih dohvatiti “zajedno” uz pomoć funkcije dim
, ili samo broj redaka ili stupaca uz pomoć funkcija nrow
i ncol
.
Rezultantna matrica je poput one iz prethodnog primjera popunjena po stupcima. Budući da ovdje nemamo priliku koristiti parametar byrow
, jedan od načina da dobijemo matricu popunjenu po retcima jest da transponiramo dobiveni rezultat uz pomoć funkcije t
.
Konačno, matricu možemo stvoriti “ljepljenjem” redaka i stupaca uz pomoć funkcija rbind
i cbind
. Ovo je također zgodan način dodavanja novih redaka i stupaca postojećoj matrici.
Zadatak 3.16 - funkcije rbind
i cbind
a <- 1:4
b <- 5:8
c <- c(0,0)
# stvorite matricu `m` u kojoj će vektori `a` i `b` biti retci
# dodajte novi redak na vrh matrice `m` sa elementima vektora `c`
# ispišite matricu `m`
a <- 1:4
b <- 5:8
c <- c(0,0)
# stvorite matricu `m` u kojoj će vektori `a` i `b` biti stupci
m <- cbind(a,b)
# dodajte novi redak na vrh matrice `m` sa elementima vektora `c`
# ispišite matricu `m`
m <- rbind(c, m)
m
## a b
## c 0 0
## 1 5
## 2 6
## 3 7
## 4 8
3.3.1 Rezanje matrica
Sve naučene principe za “rezanje” vektora uz pomoć indeksnih vektora možemo direktno primijeniti nad matricama. Razlike su sljedeće:
- referenciramo svaku dimenziju zasebno
- prvo referenciramo retke, a potom stupce, a indeksne vektore odvajamo zarezom
- ako želimo “sve retke” ili “sve stupce” taj indeksni vektor jednostavno izostavimo (ali i dalje koristimo zarez)
# pretpostavimo da je `m` matrica dimenzija 3 x 5, sa imenima stupaca od `a` do `e`
m[1, 2:5] # prvi redak, svi stupci od drugog do petog
m[c(F, T, T), c("a", "b")] # drugi i treći redak, stupci `a` i `b`
m[,] # svi retci i svi stupci (može i samo `m`)
U praksi kod matrica najčešće koristimo lokacijsko i imensko referenciranje; uvjetno referenciranje nije previše praktično zbog dvodimenzionalne prirode matrice (iako je izvedivo, samo moramo voditi računa da logički indeksni vektori duljinom odgovaraju pripadajućoj dimenziji).
Jedna od stvari na koju moramo voditi računa jest tendencija jezika R da nam “pomaže” pojednostavljujući rezultat. Tako će rezultat operacije rezanja matrice koja ostavlja samo jedan redak ili stupac automatski postati vektor, tj. izgubiti će atribut dimenzije. Ovo nam nekad ne odgovara, pogotovo ako radimo programske skripte koje u daljnjoj proceduri očekuju matricu, pa makar ona imala dimenziju redaka ili stupaca 1. U tom slučaju kod referenciranja moramo postaviti i dodatni parametar drop = F
. Ovo često izgleda dosta nezgrapno, zbog čega danas postoje mnogi paketi proširenja jezika R koji ovo “popravljaju”, tj. koji se trude rezultat ostavljati u konzistentnom obliku. No parametar drop
postavljen na FALSE
treba imati u vidu, budući da će se pojavljivati i na drugim mjestima u sličnoj funkciji.
Zadatak 3.17 - rezanje matrica
m <- matrix(1:30, 6, 5, T)
colnames(m) <- c("a", "b", "c", "d", "e")
# ispišite sve elemente matrice m od drugog do četvrtog retka
# te od trećeg do petog stupca
# sve elemente u stupcu naziva "c" postavite na nulu
# a potom ispišite prva dva retka matrice `m`
# ispišite samo stupac "d"
# ispišite opet stupac "d", ali kod referenciranja dodajte parametar `drop = FALSE`
# parametar odvojite zarezom (kao da se radi o "trećoj" dimenziji referenciranja)
m <- matrix(1:30, 6, 5, T)
colnames(m) <- c("a", "b", "c", "d", "e")
# ispišite sve elemente matrice `m` od drugog do četvrtog retka
# te od trećeg do petog stupca
m[2:4, 3:5]
# sve elemente u stupcu naziva "c" postavite na nulu
# a potom ispišite prva dva retka matrice `m`
m[, "c"] <- 0
m[1:2,]
# ispišite samo stupac "d"
m[, "d"]
# ispišite opet stupac "d", ali kod referenciranja dodajte parametar `drop = FALSE`
# parametar odvojite zarezom (kao da se radi o "trećoj" dimenziji referenciranja)
m[, "d", drop = F]
## c d e
## [1,] 8 9 10
## [2,] 13 14 15
## [3,] 18 19 20
## a b c d e
## [1,] 1 2 0 4 5
## [2,] 6 7 0 9 10
## [1] 4 9 14 19 24 29
## d
## [1,] 4
## [2,] 9
## [3,] 14
## [4,] 19
## [5,] 24
## [6,] 29
Ovdje ćemo završiti priču o matricama. Ove strukture su vrlo korisne kod rješavanja matematičkih zadataka zasnovanih na matricama, pri čemu je često zgodno pogledati dokumentaciju jezika R kako bi vidjeli koje funkcije i operatori su nam dostupni za takav posao. Isto tako, neki prikazani principi upravljanja matricama biti će korisni kod upravljanja tzv. podatkovnim okvirima - vjerojatno najpopularnijem tipu objekta jezika R kojeg ćemo upoznati u jednom od nastupajućih poglavlja.
Konačno, iako se nećemo detaljno baviti poljima, prikažimo radi potpunosti primjer programskog koda koji stvara trodimenzionalno polje te potom ispisuje jedan njegov dio standardnim principom rezanja kojeg smo upoznali kod vektora i matrica.
polje <- array(1:24, dim = c(2, 3, 4)) # polje dimenzija 2 x 3 x 4
polje[, 1:2, 3, drop = FALSE] # ispis svih redaka, prvog i drugog stupca
# trećeg "sloja", uz zadržavanje tipa polja
3.4 Liste
Lista je element programskog jezika R koji se koristi kao “univerzalni spremnik” bilo kakvih podataka. Za razliku od vektora (tj. od pojma vektora kakvog smo ga inicijalno definirali), lista može sadržavati različite tipove podataka ili - češće - skupove različitih tipova podataka.
Listu stvaramo uz pomoć funkcije list
kojom dodajemo niz parova naziva elemenata i njihovih sadržaja. Ovi elementi mogu biti bilo što, pa čak i druge liste.
Probajmo stvoriti vlastitu listu u sljedećem primjeru.
Zadatak 3.18 - stvaranje liste
# stvorite novu listu naziva `svastara` koja će imati sljedeće elemente
# element naziva `brojevi` sa cijelim brojevima od 1 do 3
# element naziva `slova` sa slovima "A" i "B"
# bezimeni element sa logičkim vektorom `c(T,F)`
# element naziva `imena` sa imenima "Ivo" i "Ana"
# ispišite listu `svastara`
svastara <- list(brojevi = c(1,2,3),
slova = c("A", "B"),
c(T,F),
imena = c("Ivo", "Ana"))
# ispišite listu `svastara`
svastara
## $brojevi
## [1] 1 2 3
##
## $slova
## [1] "A" "B"
##
## [[3]]
## [1] TRUE FALSE
##
## $imena
## [1] "Ivo" "Ana"
Uočite da lista zadržava poredak elemenata - element bez imena prikazan je indeksom 3.
Funkcija str
(engl. structure) omogućuje nam uvid u svojstva i sadržaj liste bez ispisivanja cijele liste. Ovu funkciju analitičari često koriste, kako za pregled lista tako i za brzi uvid u već spomenute podatkovne okvire koje ćemo raditi u idućem poglavlju.
Zadatak 3.19 - struktura liste
## List of 4
## $ brojevi: num [1:3] 1 2 3
## $ slova : chr [1:2] "A" "B"
## $ : logi [1:2] TRUE FALSE
## $ imena : chr [1:2] "Ivo" "Ana"
Na početku ove lekcije smo rekli da u R-u vrijedi princip “sve je vektor” te da su vektori zapravo uređeni skupovi elemenata istog tipa. Iz ovog bi se moglo zaključiti da ta činjenica ne vrijedi za liste - oni očito sadržavaju elemente različitih tipova. No pravi odgovor je - i liste su zapravo vektori, a definicija zapravo nije narušena. Naime, svi elementi liste su zapravo male jednoelementne liste, tako da su formalno svi elementi istog tipa.
Zadatak 3.20 - tip elemenata liste
## $brojevi
## [1] 1 2 3
##
## [1] "list"
Dakle, dokazali smo da su elementi liste zapravo male liste, što se vidi iz ispisa samog elementa, kao i provjere njezinog tipa. Možda nam se čini da bi elementi gore stvorene liste trebali biti vektori, budući da smo listu i stvorili “slaganjem” različitih vektora, no u postupku stvaranja objekta R je “umotao” elemente u jednoelementne liste prije nego ih je uklopio u “veliku” listu.
Često ne želimo raditi s elementom liste kao “malom listom”, nego ga trebao u njegovom “originalnom” obliku. Za ovo koristimo operator [[
, tj. operator “dvostruke uglate zagrade”.
Zadatak 3.21 - operator [[
## [1] 1 2 3
## [1] "double"
Navedeni operator najčešće koristimo kako bi dohvatili odabrani element liste kojeg definiramo brojem ili (ako ima ime) nazivom elementa. Kod ovakvog dohvata moramo koristiti kombinaciju simbola lista[["ime_elementa"]]
koja je ponešto nespretna za tipkanja. Zbog toga R nudi alternativni način pristupa elementima liste prema nazivu korištenjem operatora $
, tj. lista$ime_elementa
.
Zadatak 3.22 - operator $
# ispišite element naziva "slova" liste svastara
# korištenjem operatora `[[`
# ispišite isti element korištenjem operatora `$`
# ispišite element naziva "slova" liste svastara
# korištenjem operatora `[[`
svastara[["slova"]]
# ispišite isti element korištenjem operatora `$`
svastara$slova
## [1] "A" "B"
## [1] "A" "B"
Liste su iznimno popularni tip objekta u R-u budući da predstavljaju univerzalni predložak za kompleksnije podatkovne strukture, između ostalog i kompleksnije objekte u užem smislu (kao što ćemo vidjeti kasnije). Lista je također “temelj” za daleko najpopularniji i najčešće korišteni element jezika R - podatkovni okvir - kojeg ćemo upoznati u idućoj lekciji.
Za kraj naučimo dodati element u listu. Ovo je najjednostavnije učiniti korištenjem već spomenutog operatora $
- kao npr. lista$noviElement <- noviElement
. Element brišemo tako da mu dodijelimo vrijednost NULL
.
Zadatak 3.23 - dodavanje elementa u listu
# listi `svastara` dodajte element `parniBrojevi` koji sadrži
# sve parne brojeve od 1 do 100
# obrišite treći element liste
# ispišite listu `svastara`
# listi `svastara` dodajte element `parniBrojevi` koji sadrži
# sve parne brojeve od 1 do 100
svastara$parniBrojevi <- seq(2, 100, 2)
# obrišite treći element liste
svastara[[3]] <- NULL
# ispišite listu `svastara`
print(svastara)
## $brojevi
## [1] 1 2 3
##
## $slova
## [1] "A" "B"
##
## $imena
## [1] "Ivo" "Ana"
##
## $parniBrojevi
## [1] 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34
## [18] 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68
## [35] 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100
U sljedećoj lekciji konačno ćemo upoznati već više puta spominjane podatkovne okvire kao daleko najpopularniji i najčešće korištenu podatkovnu strukturu jezika R.
Zadaci za vježbu
- Stvorite sljedeće vektore:
- (11, 12, 13,…, 99)
- (0, 0, 0, 0, … , 0) (100 nula)
- (0, 0.1, 0.2, …., 1.0)
Kolika je suma svih brojeva od 101 do 1001, ako preskočimo sve brojeve djeljive sa 10? Koristite se funkcijom
sum
.Stvorite matricu 3 x 3 sa brojevima izvođenjem sljedećih naredbi (funkciju
sample
ćemo pobliže upoznati u jednoj od sljedećih lekcija):
# stvaramo matricu 3x3 nasumično odabranih elemenata iz skupa od 1 do 100
set.seed(1234)
m <- matrix(c(sample(1:100, 9, T)), nrow = 3, ncol = 3, byrow = T)
Izračunajte inverznu matricu uz pomoć funkcije solve
. Provjerite da li umnožak originalne i inverzne matrice daje jediničnu matricu (za množenje matrica koristite se operatorom %*%
).
- Inicijalizirajte ponovo listu
svastara
korištenu u lekciji. Napravite sljedeće:
- ispišite klasu drugog elementa liste
- ispišite element na trećem mjestu elementa liste naziva
slova
- provjerite duljinu elementa naziva
imena
te na zadnje mjesto dodajte ime"Pero"
- provjerite da li se broj
4
nalazi u prvom elementu liste - na zadnje mjesto liste dodajte novu listu sa tri vektora
a
,b
ic
koji svi sadrže elemente (1,2,3)
Programirajmo u R-u by Damir Pintar is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Based on a work at https://ratnip.github.io/FER_OPJR/