uvod - unizg.hr · web viewrealni tip sadrži određenu nepreciznost koja je rezultat činjenice da...

109
Osnove programiranja u R-u

Upload: others

Post on 09-Aug-2020

6 views

Category:

Documents


0 download

TRANSCRIPT

Osnove programiranja u R-u (S760)

Osnove programiranja u R-u (S760)

(Osnove programiranja u R-uS760)

Ovu inačicu priručnika izradio je autorski tim Srca u sastavu:

Autori: dr. sc. Damir Pintar i dr. sc. Mihaela Vranić

Recenzentica: mr. sc. Melita Perčec Tadić

Urednica: Sabina Rako

Lektorica: Mia Kožul

Sveučilište u Zagrebu

Sveučilišni računski centar

Josipa Marohnića 5, 10000 Zagreb

[email protected]

ISBN 978-953-8172-45-8 (meki uvez)

ISBN 978-953-8172-46-5 (PDF)

Verzija priručnika: 20200205

Ovo djelo dano je na korištenje pod licencom Creative Commons Imenovanje-Nekomercijalno-Dijeli pod istim uvjetima 4.0 međunarodna. Licenca je dostupna na stranici: http://creativecommons.org/licenses/by-nc-sa/4.0/.

Sadržaj1.Uvod11.1.Osnovne informacije11.2.Osnovno o jeziku R21.2.1.R – programsko okruženje za statističke metode, vizualizaciju, eksploratornu i dubinsku analizu podataka21.2.2.Načini učenja R-a21.2.3.Pregled sadržaja tečaja32.Osnovni tipovi podataka i podatkovne strukture42.1.Osnovni tipovi podataka42.1.1.Pregled osnovnih tipova42.1.2.Pojam varijable42.1.3.Provjera tipa podatka52.2.Složene podatkovne strukture62.2.1.Vektor62.2.2.Operator [72.2.3.Matrice i polja82.2.4.Liste102.2.5.Podatkovni okviri (Data frames)113.Vektorizacija i indeksni vektori143.1.Principi vektorizacije i recikliranja143.1.1.“Sve je vektor”143.1.2.Matrica kao vektor143.1.3.Lista kao vektor153.1.4.Podatkovni okvir kao vektor163.1.5.Vektorizirane operacije i princip recikliranja173.2.Indeksni vektori193.2.1.Definicija indeksnoga vektora193.2.2.Lokacijsko indeksiranje203.2.3.Uvjetno indeksiranje223.2.4.Imensko indeksiranje233.2.5.Indeksni vektori i matrice243.2.6.Indeksni vektori i podatkovni okviri243.3.Dodatni zadaci za vježbu26

4.R i programske paradigme274.1.Što su programske paradigme?274.1.1.Općenito o programskim paradigmama274.1.2.Imperativna programska paradigma274.1.3.Objektna programska paradigma274.1.4.Funkcijska programska paradigma274.2.Programski jezici i programske paradigme284.2.1.Odabir jezika = odabir paradigme?284.2.2.R i programske paradigme285.Upravljanje programskim tokom295.1.Uvjetno izvođenje naredbi295.1.1.Naredba if - else295.1.2.Naredba ifelse295.2.Programske petlje305.2.1.R i programske petlje305.2.2.Petlja repeat305.2.3.Petlja while315.2.4.Petlja for315.2.5.Korištenje programskih petlji u jeziku R326.Funkcije u jeziku R336.1.Ugrađene funkcije336.1.1.Paketi i staza pretrage336.1.2.Dohvat pomoći346.1.3.Klasične, primitivne i interne funkcije356.2.Korisnički definirane funkcije376.2.1.Sintaksa pisanja funkcije376.2.2.Princip kopiranja-kod-izmjene (engl. copy-on-change)396.2.3.Anonimne funkcije417.Objekti u jeziku R427.1.Različiti objektni modeli jezika R427.1.1.Pregled objektnoga modela S3427.1.2.Generičke funkcije447.2.Stvaranje vlastitih konstruktorskih i generičkih funkcija457.2.1.Konstruktorske funkcije457.2.2.Vlastite generičke funkcije467.3.Dodatni zadaci za vježbu47

8.Deklarativne programske petlje498.1.Porodica funkcija apply498.1.1.Općenito o porodici funkcija apply498.1.2.Funkcija apply508.1.3.Funkcija lapply528.1.4.Funkcija sapply548.1.5.Ostale funkcije iz porodice apply568.2.Alternativa funkcijama iz porodice apply568.2.1.Paket purrr568.2.2.Paket plyr579.Učinkovito programiranje i upravljanje podatkovnim skupovima589.1.Operator cjevovoda589.1.1.Korištenje operatora cjevovoda589.1.2.Operator cjevovoda i drugi operatori609.2.Učinkovito upravljanje podatkovnim skupovima619.2.1.Paket dplyr619.2.2.Odabir redaka649.2.3.Odabir stupaca659.2.4.Agregacija i grupiranje6710.Zaključak7010.1.Dodatni zadaci za vježbu72

UvodOsnovne informacije

U tečaju Osnove programiranja u R-u (S760) obrađuje se jezik R s programerskog stajališta. Polazniku se pruža uvid u osnovne elemente jezika R uz obrazloženje njihovih specifičnosti u odnosu na druge programske jezike. Posebna pažnja posvećena je principima vektorizacije i recikliranja, te različitim načinima korištenja indeksnih vektora za učinkovito upravljanje skupovima podataka. Također se polaznike upoznaje s funkcionalnom orijentiranošću jezika R i njezinim učinkovitim korištenjem u pisanju vlastitoga programskog kôda. Obrađuju se različiti tipovi funkcija, načini stvaranja vlastitih funkcija te daju savjeti o tome kako izbjeći klasične zamke kod pisanja programskoga kôda u R-u. Konačno, polazniku se daje kratki osvrt na neke od popularnijih dodatnih paketa jezika R koji omogućuju pisanje čistog, jednostavnog i preglednog programskog kôda.

Tečaj je namijenjen studentima, djelatnicima visokih učilišta i javnih instituta, zaposlenicima tvrtki i institucija te ostalim zainteresiranima.

Za pohađanje ovoga tečaja potrebno je poznavanje osnova rada na računalu i operacijskom sustavu MS Windows te poznavanje osnova rada na Internetu. Minimalno iskustvo u programiranju je prednost.

U ovom su priručniku naredbe pisane proporcionalnim slovima (na primjer, naredba install.packages()).

Sintaksa naredbi pisana je proporcionalnim slovima te komentarima za dijelove koje program ne izvodi: >library(help = "base") #pomoć za paket base.

Gradivo je uglavnom obrađeno kroz niz primjera i vježbi. Rješenja vježbi i rezultati izvođenja programskih naredbi dani su u interaktivnim prozorima (HTML inačica) ili na kraju priručnika (PDF inačica).

Ovaj priručnik predviđa korištenje elektroničkih radnih bilježnica koje uz njega čine nedjeljivu cjelinu.

Osnovno o jeziku RR – programsko okruženje za statističke metode, vizualizaciju, eksploratornu i dubinsku analizu podataka

U današnje doba postoji vrlo bogata ponuda različitih programskih jezika. Za razliku od jezika koju su ciljano dizajnirani kao “jezici općenite namjene”, kao što su Java, C++ ili Python, postoje i jezici čiji je dizajn usredotočen na točno određenu svrhu. Jezik R primjer je ovakvoga jezika – iako sadrži sve nužne elemente prema kojima bismo ga mogli također uvrstiti u skup jezika opće namjene, njegova primarna uloga jest podrška za eksploratornu, statističku i dubinsku analizu podatkovnih skupova, uglavnom na interaktivni način izvođenjem naredbi uz pomoć programske konzole.

Za jezik R često se kaže da su ga “dizajnirali statističari za statističare”. Ako pojam “statističar” zamijenimo širim pojmom “podatkovnog analitičara” i uzmemo u obzir domensku orijentiranost jezika onda ovu izjavu možemo interpretirati u pozitivnom smislu, da su dizajneri jezika R ljudi koji razumiju potrebe analitičara i koji su jezik oblikovali upravo kako bi istima rad učinili što lakšim i učinkovitijim. No, izjavu je lako interpretirati i u smislu da ovo nije jezik smišljen “od programera za programere”, tj. da učenjem jezika R moramo biti spremni na rušenje određenih konvencija i predrasuda te prihvaćanje činjenice da “R-ovski” pristup programiranju nije nužno u skladu s pristupom kojim bismo se koristili u nekom “klasičnijem” programskom jeziku. R je jezik koji primarno želi omogućiti korisniku/analitičaru postizanje rezultata na lak, brz i fleksibilan način. Dizajn osnovnih tipova podataka, programskih struktura i funkcija odražava ovu filozofiju. Postoje jasni razlozi zašto se jezik R nametnuo kao de facto standard u području podatkovne znanosti te zašto je toliko dobro prihvaćen i od strane korisnika čija struka nije primarno programerska. No, za učinkovito svladavanje R-a vrlo je bitno odabrati učinkovit način učenja ovoga jezika.

Načini učenja R-a

Posebnosti jezika R mogu uzrokovati poteškoće kod početnika koji se priklanjaju dvama – podjednako pogrešnim pristupima učenju ovoga jezika. Prvi je da se R-u pristupa kao jeziku opće namjene te da ga se pokušava naučiti “klasičnom” metodologijom učenja programskih jezika, upoznavajući njegove elemente, programske strukture, principe pisanja progresivno složenijih programa i sl. Ovakav način učenja R-a nije nemoguć jer R sadrži sve nužne elemente jezika opće namjene te ga je i moguće koristiti za primjene koje nisu usko povezane s podatkovnom znanosti. Problem nastaje ako se tijekom učenja sustavno zanemaruje orijentiranost R-a prema analizi podataka. Razdruživanjem elemenata R-a kao programskoga jezika i njegove svrhe naići ćemo na niz prividnih nelogičnosti i nekonvencionalnih elemenata koji su teško objašnjivi ako ne razmišljamo u kontekstu upotrebe istih u okvirima podatkovne analize, kada oni najčešće postaju logični i jasni. Na primjer, većina programskih jezika za prvi indeks elemenata nekoga skupa koristi broj 0, tj. prosječan programer prirodno o prvom elementu razmišlja kao o “nultom” usprkos poteškoćama koje ovo potencijalno uzrokuje. R u potpunosti zaobilazi ovu konvenciju i počinje enumeraciju članova nekoga skupa s 1. Zašto? Zato što je analitičaru puno prirodnije i praktičnije razmišljati o “prvom retku tablice” nego “nultom” te je u potpunosti rasterećen potrebe “mentalnog posmaka za 1”, toliko prirodne u programerskom svijetu.

Drugi pogrešan način učenja jezika R, kojem najčešće teže ljudi bez programerskog iskustva, jest onaj koji koristi dijametralno suprotni pristup, a to je učenje samo onih elemenata jezika R orijentiranih neposrednoj svrsi njegovoga korištenja. Ta svrha može biti, na primjer, učitavanje skupa podataka iz datoteke, crtanje neke vizualizacije ili izrada linearnoga prediktivnog modela. Zanemarivanjem programskih elemenata i internoga načina funkcioniranja jezika R isti se tako svodi na niz naredbi koje korisnik uči napamet (ili zapisuje na “šalabahter”) i koji onda predstavlja jedini i isključivi način korištenja ovoga jezika. Ovo praktički reducira R na “aplikaciju bez sučelja” koja time postaje zbunjujuća i manje atraktivna alternativa nekom grafičkom analitičkom alatu. Usprkos domenskoj orijentiranosti, jezik R je i dalje programski jezik i iskorištavanje njegovoga punog potencijala zahtjeva svladavanje vještine programiranja, kao i razumijevanje svih njegovih elemenata, a ne samo ograničene kolekcije “korisnih” funkcija.

Ideja ovoga tečaja jest prvenstveno naći kompromis između ova dva “ekstremna” pristupa učenja R-a te pokazati kako programerski pristup učenju uz prihvaćanje domenske orijentiranosti jezika može uvelike olakšati svladavanje R-a te dugoročno omogućiti lakši i učinkovitiji rad uz pisanje jasnoga i preglednoga programskog kôda. Posebna pažnja posvetit će se specifičnostima jezika te će se neke njegove neobičnije strane demistificirati i razjasniti u kontekstu korištenja istih u konkretnim primjerima i zadacima. Konačno, dio tečaja posvetit će se i tome kako izbjeći klasične početničke greške i zablude koje toliko često stvaraju krivu percepciju u ovom programskom jeziku.

Pregled sadržaja tečaja

Prvi dan posvetit ćemo osnovnim elementima jezika R – tipovima podataka i podatkovnim strukturama. Posebno ćemo se usredotočiti na fundamentalne elemente R-a, principe vektorizacije i recikliranja te dizajna indeksnih vektora, koji su ključni za kvalitetno upoznavanje s jezikom i njegovo učinkovito korištenje.

Drugi dan bit će uglavnom usmjeren prema funkcijama kako onim ugrađenim ili dostupnim kroz raznorazne dodatne pakete tako i onima koje korisnik može samostalno definirati. Bit će rečeno i nekoliko riječi o paradigmama programskih jezika, osobito funkcionalnom programiranju, koje predstavlja bitan segment učinkovitoga rada s jezikom R. Obradit će se i elementi kontrole programskoga toka te razjasniti zašto pojedini od njih (osobito programske petlje) nisu toliko prisutni ni poželjni u jeziku R koliko u drugim programskim jezicima.

Konačno, treći dan ćemo se usredotočiti na najpopularnije funkcije i dodatne pakete posebno dizajnirane za to da korisniku olakšaju i ubrzaju korištenje i razumijevanje jezika R. Jedan od bitnih aspekata ovoga jezika jest i vrlo aktivna R zajednica koja svojim doprinosima unapređuje iskustvo rada s jezikom svim njegovim korisnicima. Kao rezultat ovoga nastao je niz paketa koji doslovno redefiniraju način korištenja R-a i koje veliki dio R zajednice smatra neizostavnim, obaveznim dijelom jezika.

Krenimo sada od početka i pogledajmo osnovne elemente jezika – elementarne tipove podataka, varijable i temeljne podatkovne strukture.

Osnovni tipovi podataka i podatkovne struktureOsnovni tipovi podataka

Osnovni (elementarni ili atomarni) tipovi podataka nekoga programskog jezika predstavljaju temeljne vrste informacije kojima neki jezik može upravljati. Ovi tipovi su danas uglavnom standardizirani i svode se na način reprezentacije podataka logičkoga, numeričkoga ili znakovnoga tipa.

Pregled osnovnih tipova

R poznaje šest osnovnih tipova podataka:

tip

izvorni naziv tipa

primjeri

logički

logical

TRUE, FALSE ili T, F

cjelobrojni

integer

2L, 5L, 123456789L

realni

double

4, 6, 3.14, 2e5

kompleksni

complex

5 + 2i, 7 + 1i

znakovni

character

"A", "B", "Pero", "ABCDEFGHijklmnoPQRSTUVwyz"

bajtovni

raw

as.raw(2), charToRaw("1")

Cjelobrojni i realni tip podatka zajedno se svrstavaju u takozvani “numerički” tip podatka (engl. numeric). Razlika između njih jest u tome što cjelobrojni pohranjuje cijele brojeve s potpunom preciznošću, ali ima ograničeniji opseg i nema mogućnost pohrane brojeva koji sadrže decimale. Realni tip sadrži određenu nepreciznost koja je rezultat činjenice da postoji beskonačan broj realnih brojeva, ali konačan broj bitova u registru, onome u koji pohranjujemo realni broj. U velikoj većini slučajeva u radu s R-om o ovome uopće ne moramo brinuti – ako to eksplicitno ne zatražimo (slovo L!), R će numeričku informaciju pohraniti u realnom tipu, a nepreciznost će nam smetati samo ako se koristimo brojevima s više od 16 znamenaka, što je rijedak slučaj.

Dakle, za praktične svrhe uglavnom su nam zanimljivi samo logički, znakovni i realni tip podatka (koji ćemo zbog jednostavnosti zvati “numerički”). Cjelobrojni, kompleksni i “sirovi” (bajtovni) tip možemo zanemariti, osim ako smo suočeni s vrlo specifičnom situacijom gdje su isti potrebni.

Pojam varijable

Podatke pohranjujemo u imenovane spremnike zvane “varijable”. R je takozvani “slabo tipizirani” jezik (engl. weakly typed), što znači da varijable možemo slobodno definirati, a da ne navedemo unaprijed koji tip podatka pohranjuju, a istu varijablu možemo vrlo lako “preusmjeriti” da označava neki novi tip podatka.

Primjer 1: Inicijalizacija varijabli različitih tipova

# inicijaliziramo varijable a, b i c na sljedeći načina <- 5b <- TRUEc <- 14Lc <- "Pero"

# funkcija `cat` - "sirovi" ispis, samo "izbacuje" sadržaj parametara na zasloncat(a, b, c)

## 5 TRUE Pero

U primjeru smo inicijalizirali tri varijable, a, b i c. Vidimo da tipove podataka nismo morali navoditi, već je R automatski dodijelio tip varijabli ovisno o tipu podatka koji smo joj pridružili. Isto tako, možemo vidjeti da smo varijablu c prvo inicijalizirali na cjelobrojni tip, da bi joj potom pridružili znakovni tip. Ovim vidimo da je varijabla zapravo samo svojevrsni “pokazivač” na informaciju u radnoj memoriji. Cjelobrojni podatak 14L više nije dohvatljiv i predstavlja “smeće” – R ima posebnu dretvu zvanu “sakupljač smeća” koja periodički provjerava postoji li ovakva nedohvatljiva informacija i ako da, onda istu trajno briše označavajući taj dio memorije kao slobodan.

(NAPOMENA: jedan od češćih problema u radu s R-om jest pojava nedostatka radne memorije, koja se može dogoditi kod rada s velikim podatkovnim skupovima, a koja rezultira usporavanjem izvođenja programa ili njegovim rušenjem. Često je uzrok ovoga neiskustvo analitičara/programera, čiji programski kôd stvara previše “kopija” podatkovnoga skupa koje se jednokratno koriste i odmah čine zalihosnima, ali koje sakupljač smeća ne stigne na vrijeme osloboditi. Tijekom ovoga tečaja naučit ćemo kako izbjeći ovu pojavu.)

Provjera tipa podatka

Tip podatka (tj. varijable) možemo provjeriti uz pomoć funkcija typeof ili class. Pokušajmo upotrijebiti ove funkcije nad varijablama a, b i c koje smo inicijalizirali u prethodnom primjeru.

Vježba 1: Funkcije typeof i class

# primijenite funkciju `typeof` redom na varijable a, b i c

# primijenite funkciju `class` redom na varijable a, b i c

Razlika između typeof i class možda nije očita. Funkcija typeof odgovara na pitanje “Kako je ovaj podatak pohranjen u memoriji?”, dok nam funkcija class otkriva odgovor na pitanje “Što ovaj podatak predstavlja?” (točnije: “Koje je klase ovaj objekt?”). U praksi ćemo gotovo uvijek koristiti funkciju class, jer je – kao što ćemo kasnije naučiti – većina kompleksnijih objekata u R-u zapravo pohranjena kao liste, tako da nam tip podatka koji vraća typeof neće biti previše koristan.

U gornjem primjeru, varijable smo inicijalizirali s jednostavnim, elementarnim vrijednostima. U nastavku ćemo se upoznati sa složenijim podatkovnim strukturama koje predstavljaju temeljni dio jezika R.

Složene podatkovne strukture

Kao što ćemo vidjeti u nastavku, jedan od češćih uzoraka koji susrećemo u R-u jest činjenica da radimo više stvari odjednom. Ovo je nužan nusprodukt činjenice da je R dizajniran za potrebe analize velikih podatkovnih skupova – analitičar se češće orijentira na obradu podatkovnoga skupa u cijelosti nego što se usredotočuje na jedan podatak unutar skupa. U skladu s time, umjesto da inicijaliziramo varijable u koje pohranjujemo jedan podatak, puno češće ćemo upravljati varijablama koje pohranjuju kolekciju ili skup podataka. Ovisno o tipu i strukturi podataka koje pohranjujemo, R nudi četiri temeljne složene podatkovne strukture: vektor, matricu, listu i podatkovni okvir.

Vektor

Vektor je jednodimenzionalna uređena kolekcija elemenata istoga tipa. Novi vektor (koji ima više od jednog elementa) stvaramo uz pomoć funkcije c (od engl. combine).

Primjer 2: Inicijalizacija vektora

# numerički vektorm <- c(1, 2, 3, 4, 5)

# logički vektorv <- c(T, F, T)

# znakovni vektorimena <- c("Ivo", "Pero", "Ana")

# korištenje samog imena varijable pokrenut će tzv. `autoprint`# isti rezultat postigli bismo i eksplicitnim pozivom funkcije `print`mvimena

## [1] 1 2 3 4 5## [1] TRUE FALSE TRUE## [1] "Ivo" "Pero" "Ana"

Ako stvaramo novi vektor s elementima različitih tipova podataka, R će sve elemente automatski pretvoriti u “najjači” tip, što će na kraju postati i tip samoga vektora (termin “jači” tip u ovom kontekstu označava mogućnost tipa da pohrani svu informaciju “pohranjenu u slabiji tip”, a u općenitom slučaju pretvorba ide u smjeru logički -> numerički -> znakovni tip).

Vježba 2: Stvaranje vektora

# stvorite novi vektor x s četiri proizvoljna elementa sljedećih tipova: # logički, realni, znakovni i cjelobrojni

# ispišite na zaslon sadržaj vektora i njegovu klasu

Funkcijom c možemo također i više vektora spojiti u jedan:

Primjer 3: Spajanje vektora

# inicijaliziramo vektore a, b i ca <- c(1, 2, 3)b <- c(4, 5) # uočite – varijablu smijemo nazvati c usprkos tome što postoji funkcija c()c <- c(6, 7, 8)

d <- c(a, b, c) # d je sada c(1, 2, 3, 4, 5, 6, 7, 8)d

## [1] 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čive

· seq – funkcija sekvence (engl. sequence), radi slično operatoru raspona, ali s dodatnim mogućnostima

· rep – funkcija repliciranja (engl. replicate), ponavlja zadane elemente zadani broj puta.

Primjer 4: Stvaranje vektora – pomoćne funkcije i operatori

1:5rep(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

Operator [

Elementima vektora pristupamo preko indeksnog operatora [, uz pomoć kojega možemo i mijenjati elemente vektora:

Primjer 5: Pristup elementima vektora

# zadan je vektor aa <- c(2, 4, 6)a[1] # ispisuje vrijednost 2a[2] <- 5 # element na 2. mjestu postaje 5a[5] <- 7 # na 5. mjesto dodaje se 7, a "rupa" se popunjava s NAa

## [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 indeksiranje elemenata u drugim programskim jezicima. Razlog ove specifičnosti je jednostavan – R se primarno smatra jezikom za analizu podataka, osobito u tabličnom obliku, a u praksi je puno lakše brojati retke ili stupce redoslijedom kojim se pojavljuju u podatkovnom skupu nego raditi “posmak za 1”.

Rekli smo da je vektor “jednodimenzionalna” podatkovna struktura – ako gledamo vektor od n numeričkih elemenata, on zapravo predstavlja pravac u n-dimenzionalnom prostoru. R nudi i podatkovne strukture za pohranu podataka u dvije, ali i više dimenzija. Ove strukture zovu se matrice i polja.

Matrice i polja

Matrice i polja su, jednostavno rečeno, višedimenzionalni vektori. Matrica (engl. matrix) je tako vektor s dvije dimenzije, tj. vektor koji elemente smješta u “retke” i “stupce”. Polje (engl. array) je vektor s 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.

Postoji nekoliko načina stvaranja nove matrice:

· uz pomoć funkcije matrix kojoj prosljeđujemo jednodimenzionalni vektor i željeni broj redaka i stupaca kroz parametre nrow i ncol

· “ručnim” postavljanjem dimenzija jednodimenzionalnoga vektora uz pomoć funkcije dim i pridruživanja dvoelementnoga numeričkog vektora s dimenzijama matrice

· “lijepljenjem” jednodimenzionalnih vektora koji predstavljaju retke ili stupce nove matrice uz pomoć funkcija rbind (engl. row bind) i cbind (engl. column bind).

Vježba 3: Funkcija matrix

# zadan je vektor xx <- 1:12

# uz pomoć funkcije `matrix` i vektora x stvorite matricu s 3 retka i 4 stupca# ispišite rezultat na zaslon

# ponovite postupak, ali pozivu funkcije dodajte parametar `byrow = T`# ispišite rezultat na zaslon i usporedite s prethodnim rezultatom

Vježba 4: Funkcije rbind i cbind

# zadani su vektori a, b i ca <- 1:4b <- 5:8c <- c(0,0)

# stvorite matricu m u kojoj će vektori a i b biti stupci

# dodajte novi redak na vrh matrice m s elementima vektora c# ispišite matricu m

Elementima matrice također pristupamo preko indeksnog operatora [. Razlika je samo u tome što ćemo kod indeksiranja matričnih elemenata (uglavnom) koristiti dvodimenzionalnu referencu – indeks retka i indeks stupca, odvojene zarezom. Ako želimo cijeli redak ili cijeli stupac, jednostavno ispustimo indeks te dimenzije (npr. m[2,] znači “drugi redak”).

Vježba 5: Indeksiranje matričnih elemenata

# zadana je matrica mm <- matrix(1:12, nrow = 3, ncol = 4, byrow = T)

# ispišite:# element u 2. retku, 3. stupcu

# cijeli 3. redak

# cijeli 4. stupac

Polja (engl. array) su zapravo višedimenzionalno proširenje koncepta matrice. U praksi ih relativno rijetko susrećemo osim u specifičnim slučajevima gdje ciljano radimo s višedimenzionalnim strukturama, na primjer modeliranje neke trodimenzionalne podatkovne strukture ili rješavanje višedimenzionalnih problema iz linearne algebre. Zbog ove činjenice, ovdje ih nećemo posebno obrađivati, no ako se pojavi potreba za njihovim korištenjem, dovoljno se prisjetiti da se njima upravlja analogno matricama (npr. indeksiranje svake dimenzije zasebno, odvojeno zarezom).

Liste

Lista je element programskoga 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 kojoj dodajemo niz parova naziva elemenata i njihovih sadržaja. Ovi elementi mogu biti bilo što, pa čak i druge liste.

Primjer 6: Funkcija list

mojaLista <- list(a = 1, b = 2:100, c = list(x = 1, y = 2))

Pokušajmo stvoriti vlastitu listu u sljedećoj vježbi.

Vježba 6: Stvaranje liste

# stvorite novu listu naziva `svastara` koja će imati sljedeće elemente# element naziva `brojevi` s cijelim brojevima od 1 do 3# element naziva `slova` sa slovima "A" i "B"# bezimeni element s logičkim vektorom `c(T,F)`# element naziva `imena` s imenima "Ivo" i "Ana"

# ispišite listu `svastara`

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.

Vježba 7: Struktura liste

# ispišite strukturu liste `svastara`

Elementima liste uglavnom pristupamo na dva načina:

· uz pomoć operatora [[, tj. operatora “dvostruke uglate zagrade”

· uz pomoć operatora $ (tzv. “string” operator).

Vježba 8: Operator [[

# ispišite prvi element liste svastara korištenjem operatora [[

# provjerite njegovu klasu

Navedeni operator najčešće koristimo kako bismo dohvatili odabrani element liste koji definiramo brojem ili (ako ima ime) nazivom elementa. Kod ovakvog dohvata moramo koristiti kombinaciju simbola lista[["ime_elementa"]] koja je ponešto nespretna za tipkanje. Zbog toga R nudi alternativni način pristupa elementima liste prema nazivu korištenjem operatora $, tj. lista$ime_elementa.

Vježba 9: Operator $

# ispišite element naziva `slova` liste svastara# korištenjem operatora [[

# ispišite isti element korištenjem operatora $

Za kraj, naučimo dodati element u listu. Ovo je najjednostavnije učiniti korištenjem već spomenutog operatora $, kao na primjer, lista$noviElement <- noviElement. Element brišemo tako da mu dodijelimo vrijednost NULL.

Vježba 10: Dodavanje i brisanje elemenata liste

# 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`

Podatkovni okviri (Data frames)

Podatkovni okvir predstavlja vjerojatno najpopularniju i najčešće korištenu podatkovnu strukturu jezika R, što proizlazi iz činjenice da se radi o objektu koji reprezentira podatkovni skup koji namjeravamo analizirati. Drugim riječima, podatkovni okvir je objekt slične funkcije kao tablica u Microsoft Excelu ili relacijskoj bazi podataka. Gotovo svaka “sesija” u R-u svodi se na manipuliranje podatkovnim okvirima – no, dok u Excelu tablicom upravljamo uz pomoć grafičkoga sučelja, a u bazi uz pomoć upitnoga jezika SQL, u R-u podatkovni okvirima upravljamo gotovo isključivo programski.

Uzmimo za primjer sljedeću tablicu:

pbr

nazivMjesta

prosjPlacaKn

brojStanovnika

prirez

10000

Zagreb

6359.00

790017

18

51000

Rijeka

5418.00

128384

15

21000

Split

5170.00

167121

10

31000

Osijek

4892.00

84104

13

20000

Dubrovnik

5348.00

28434

10

Ovdje se radi o podatkovnom skupu koji sadržava određene (ne nužno ažurne) parametre vezane za gradove u Republici Hrvatskoj.

Podatkovni okvir najčešće stvaramo na jedan od dva načina:

· programski, uz eksplicitno navođenje sadržaja

· čitanjem iz vanjske datoteke uz pomoć funkcija read.table / read.csv / read.csv2.

U nastavku ćemo isprobati korištenje obaju načina.

Vježba 11: Programsko stvaranje podatkovnog okvira

#zadani su sljedeći vektoripbr = c(10000, 51000, 21000, 31000, 2000)nazivMjesta = c("Zagreb", "Rijeka", "Split", "Osijek", "Dubrovnik")prosjPlacaKn = c(6359., 5418., 5170., 4892., 5348.)brojStanovnika = c(790017, 128384, 167121, 84104, 28434)prirez = c(18, 15, 10, 13, 10)

# uz pomoć funkcije `data.frame` i gornjih vektora stvorite podatkovni okvir `mjesto`# koristite istu sintaksu kao kod stvaranja liste# elemente liste nazovite imenima varijabli

# ispišite podatkovni okvir `mjesto`

Naziv CSV znači “comma separated values”, tj. “podaci odvojeni zarezom”. Prvi redak datoteke obično sadrži nazive stupaca. Ovako oblikovane podatke lako čitamo uz pomoć funkcije read.csv kojoj dajemo sljedeće parametre:

· stazu do CSV datoteke

· parametar stringsAsFactors = FALSE.

Potonji parametar bitan je za sprečavanje takozvanoga “faktoriziranja”, tj. kategoriziranja znakovnih stupaca, što je postupak koji je bolje obaviti “ručno” (nakon učitavanja u podatkovni okvir) kako bismo izbjegli eventualne greške.

NAPOMENA: Problem CSV datoteka jest taj što one koriste zarez kao razdvojnik (engl. delimiter) elemenata zapisa, što predstavlja problem na govornim područjima koje kao standard, umjesto decimalne točke, koriste upravo “decimalni zarez”. Zbog ove činjenice postoji i “alternativni” CSV standard koji kao razdvojnik koristi “točku-zarez”. Budući da smo stanovnici takvoga govornog područja, velike su šanse da ćemo se susresti s takvom “alternativnom” CSV datotekom; u tom slučaju bismo umjesto funkcije read.csv trebali koristiti funkciju read.csv2, ili read.table koja je općenitija funkcija za čitanje podataka iz datoteke i omogućuje fino podešavanje niza parametara (zapravo su read.csv i read.csv2 izvedenice funkcije read.table).

Za sljedeću vježbu bit će potrebno u radnoj mapi stvoriti CSV datoteku sljedećega sadržaja:

pbr,nazivMjesta,prosjPlacaKn,brojStanovnika,prirez10000,Zagreb,6359.00,790017,18 51000,Rijeka,5418.00,128384,1521000,Split,5170.00,167121,1031000,Osijek,4892.00,84104,13 20000,Dubrovnik,5348.00,28434,10

Vježba 12: Čitanje podataka iz CSV datoteke

# učitajte podatke iz datoteke `mjesto.csv` # podatke spremite u podatkovni okvir `mjesto2`

# ispišite podatkovni okvir `mjesto2`

Jedna od zgodnih stvari kod podatkovnih okvira jest ta što se oni ponašaju i kao matrice (dvodimenzionalno indeksiranje) i kao liste. Ovo proizlazi iz činjenice da su podatkovni okviri zapravo ništa drugo nego liste, samo s ograničenjem da svi elementi moraju imati istu duljinu. Ovo ograničenje zapravo diktira “tabličnu” strukturu podatkovnog okvira, što nam daje opciju da isti vizualiziramo kao dvodimenzionalnu tablicu te da s njom upravljamo kroz koncepte “redaka” i “stupaca”.

Upravljanje podatkovnim okvirima predstavlja temelj rada s jezikom R i najvažniju stepenicu za njegovo jednostavnije i učinkovito korištenje. Zbog toga je vrlo važno uložiti trud i vrijeme za svladavanje nekih osnovnih principa koji će rad s podatkovnim okvirima (ali i ostalim podatkovnim strukturama) učiniti što transparentnijim i razumljivijim, bez pojave “neobičnoga” ponašanja i “neočekivanih” rezultata. Konkretno, to su principi vektorizacije i recikliranja te konstrukcije indeksnih vektora, a koje ćemo naučiti u nastavku.

Vektorizacija i indeksni vektoriPrincipi vektorizacije i recikliranja“Sve je vektor”

U prethodnom dijelu upoznali smo osnovne tipove podataka jezika R te složene podatkovne strukture. Pokušajmo sada odgovoriti koja je od varijabli u sljedećem primjeru “vektor”.

Primjer 7: Vektori

a <- 4b <- "Ivana"c <- c(1, 2, 3, 4, 5)d <- list(a = 1:10, b = LETTERS, c = c(T,F)) # Što je LETTERS? Ispišimo to!e <- matrix(1:9, 3, 3)

Pokušajte prvo intuicijom odgovoriti na ovo pitanje, a potom provjerite ispravnost pretpostavke uz pomoć funkcije is.vector.

Došli smo do jednog vrlo važnoga, i možda ponešto neočekivanoga zaključka – sve gornje varijable su vektori, osim varijable e (iako je i ona zapravo vektor, usprkos tomu što ju funkcija is.vector ne priznaje kao takvu, što je zapravo rezultat činjenice da ta funkcija ne provjerava da li je nešto vektor, već ima li dodatne atribute pored names, tako da ju zapravo ne bismo ni trebali koristiti).

Dakle, čak i broj 4 te niz znakova "Ivana" nisu ništa drugo nego jednočlane “kolekcije” elemenata. Što ovo konkretno znači? Postoji jedan često spominjani princip jezika R, koji glasi “sve je vektor”. Iako ovo baš nije doslovno točna izjava, ona otkriva jednu vrlo važnu činjenicu, a to je da je većina varijabli kojima ćemo se koristiti u jeziku R zapravo vektor, što znači da će imati slično ponašanje i koristiti slične obrasce upravljanja!

Matrica kao vektor

Iako smo rekli da funkcija is.vector neće prepoznati matricu kao vektor, to ne mijenja činjenicu da matrica nije ništa drugo nego jednodimenzionalni vektor s dodanim atributom dim koji programeru omogućuje da ga koristi kao dvodimenzionalnu strukturu. Drugim riječima, dvodimenzionalna struktura matrice je samo prividna, ona je tu strogo zbog praktičnosti i lakšega korištenja jezika R za potrebe matričnih izračuna. Ovo je zapravo uobičajena pojava u programskim jezicima – višedimenzionalne strukture se zapravo “raspliću” kako bi se uopće mogle pohraniti u jednodimenzionalnu memoriju računala, a višedimenzionalno indeksiranje se uz pomoć poznate dimenzionalnosti “ispod haube” preračunava u jednodimenzionalni indeks.

U ovo se možemo uvjeriti u sljedećoj vježbi.

Vježba 13: Matrica kao vektor

# zadana je matrica mm <- matrix(1:12, nrow = 3, ncol = 4)

# ispišite matricu m

# ispišite 2. element u 4. stupcu

# ispišite 11. element (jednodimenzionalno indeksiranje!)

Zašto nam je ovo korisno? Poznavanje interne strukture podataka nam može olakšati rad s istima – ako znamo da je matrica zapravo pohranjena “po stupcima” onda možemo i razumjeti zašto može doći do izrazitih usporavanja u radu s velikim matricama ako podatke obrađujemo “po retcima”. Isto tako, kao što ćemo vidjeti u nastavku, podatkovni okviri se u izvjesnoj mjeri ponašaju kao matrice tako da i za njih vrijedi princip “virtualne dimenzionalnosti” – to što mi kao programeri vidimo podatke organizirane u retke, to ne znači da su oni zapravo bliski jedni drugima na mjestu gdje su pohranjeni.

Ovo sve naravno ne znači da u radu s matricama (i podatkovnim okvirima) ne bismo trebali koristiti pogodnosti koje nam donosi prividna dvodimenzionalnost, već samo da bismo istu morali imati u vidu u trenucima kada dođe do eventualnih problema ili uspijemo pronaći zgodan način kako jednodimenzionalnu prirodu iskoristiti za lakše upravljanje takvim podacima.

Lista kao vektor

Opravdano je postaviti pitanje – što je s listom? Kod same definicije liste rekli smo da ona “sadržava različite skupove podataka”, što se naizgled kosi s definicijom vektora kao “jednodimenzionalne uređene kolekcije elemenata istoga tipa”. Objašnjenje je zapravo jednostavno – definicija nije narušena zato što lista u biti sadržava “niz jednoelementnih listi”. Svaki element liste je lista, što objašnjava činjenicu zašto kod indeksiranja liste ne koristimo operator [ već [[. Budući da je svaki element liste opet lista, jednostruki operator bi nam vratio listu, i kod dohvaćanja prvog elementa te liste opet bismo dobili listu, i tako u nedogled. Operator [[ (a tako i operator $ koji je zapravo izveden iz prethodno navedenog) zapravo znači “dohvati i otpakiraj” element liste.

Isprobajmo ovo uz pomoć iduće vježbe:

Vježba 14: Lista kao vektor

# zadana je lista `svastara`svastara <- list(brojevi = c(1,2,3), slova = c("A", "B"), c(T,F), imena = c("Ivo", "Ana"))

# provjerite klasu drugog elementa liste `svastara`# koristite operator [# provjerite klasu drugog elementa liste `svastara`# koristite operator [[

Opet, zašto nam je uopće bitno znati da je lista zapravo vektor? Zato što to znači da u suštini nema bitne razlike između upravljanja vektorom i upravljanja listom; i vektor i lista se ponašaju jednako s gledišta “kolekcije elemenata” te ako razumijemo kako upravljati vektorom koji sadrži, na primjer, jednostavne numeričke vrijednosti, onda iste principe možemo koristiti za upravljanje listama, iako su njezini elementi često složenije strukture od “običnih” brojeva.

Podatkovni okvir kao vektor

Već smo spomenuli da je podatkovni okvir zapravo lista, samo s elementima jednake duljine. To znači da je sve spomenuto za percepciju liste kao vektora primjenjivo i na podatkovni okvir, pa čak i rasprava o operatorima [, [[ i $ – iako ne smijemo zaboraviti činjenicu da R omogućuje dvodimenzionalno indeksiranje podatkovnih okvira tako da kod njih ipak nećemo koristiti operator [[ već operator [ sa zarezom za razdvajanje dimenzija (što je kod “običnih” lista besmisleno i neprimjenjivo).

Pokažimo ovo u idućoj vježbi.

Vježba 15: Podatkovni okvir kao vektor

# # učitajte podatke iz datoteke `mjesto.csv` # podatke spremite u okvir `mjesto`

# ispišite drugi stupac (nazivMjesta) na sljedeće načine:

# koristite operator [ i jednodimenzionalno indeksiranje

# koristite operator [ i dvodimenzionalno indeksiranje

# koristite operator [[

# koristite operator $

Ako ste pažljivo proučili rješenje, mogli ste uočiti jasnu poveznicu između liste i podatkovnih okvira – operator [ uz jednodimenzionalno indeksiranje vratio nam je (jednostupčani) podatkovni okvir, dok su svi ostali načini “otpakirali” informaciju i pretvorili je u običan znakovni vektor. Ova činjenica nam se nekad može pokazati korisnom, pogotovo ako pišemo funkcije koje obrađuju podatkovne okvire i želimo da nam rezultat operacije bude okvir, a ne vektor.

NAPOMENA: Razlog zašto kod operatora [ i dvodimenzionalnog indeksiranja nismo dobili podatkovni okvir već vektor zapravo nije “otpakiravanje” elemenata liste (to operator [ ni ne radi!), već činjenica da jezik R često “pojednostavljuje” rezultat. Ukoliko to ne želimo, možemo kod indeksiranja dodati parametar drop = FALSE, npr. mjesto[, 2, drop = FALSE].

Možemo ponovo postaviti pitanje – zašto nam je bitno razumjeti internu strukturu podatkovnog okvira? Ako znamo da je okvir zapravo lista, to znači da se funkcije za obradu listi mogu bez problema primijeniti na podatkovne okvire (s time da moramo voditi računa o tome da su “elementi liste” zapravo stupci okvira). Isto tako, znajući da je lista zapravo vektor, opet se vraćamo na činjenicu da funkcije za obradu vektora element po element postaju direktno primjenjive na liste, a time i na okvire. Konačno, već spomenuta činjenica kod matrica vrijedi i za okvire – obrada po stupcima je puno učinkovitija od obrade po redcima, budući da se podatkovni okvir interno “raspliće” po stupcima.

Prihvaćanjem principa “sve je vektor” već smo učinili bitan pomak u razumijevanju principa vektorizacije i kako ga primijeniti u jeziku R. Učinimo sad korak dalje.

Vektorizirane operacije i princip recikliranja

Pojam vektorizacije ili bolje rečeno vektoriziranih operacija i funkcija jednostavno znači da se operacije rade nad više elemenata odjednom. Ovo je temeljni princip kako R obrađuje podatke i ključna prepreka za razumijevanje ovoga jezika. Budući da je u R-u “sve vektor”, to znači da su operacije koje radimo nad našim varijablama prirodno vektorizirane, ili – jednostavnim jezikom rečeno – R-ov uobičajeni način rada jest da radi nad cijelim skupom odjednom, umjesto sa svakim elementom posebno.

Dakle, 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).

Vježba 16: Princip vektorizacije

# zadani su vektori x, a i bx <- seq(-5, 5, 1)a <- 1:3b <- 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

Pažljivo razmotrite rezultate prethodnoga zadatka. Ako je potrebno, skicirajte vektore a i b na papiru s vertikalno poslaganim elementima i uočite kako radi paralelno “uparivanje” elemenata. Primijetite da ovdje ne govorimo 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 nikakvoga “množenja vektora” kako ga uobičajeno interpretiramo u linearnoj algebri (“skalarni produkt”), 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 duljega vektora. Najčešći scenarij korištenja ovoga 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 “velikoga” vektora. Ono što bismo trebali izbjegavati jest scenarij recikliranja gdje duljina “velikoga” vektora nije višekratnik duljine “maloga” – R će i dalje reciklirati kraći vektor, samo će ga na kraju morati “odrezati” što će rezultirati odgovarajućim upozorenjem.

Vježba 17: Princip recikliranja

# zadani su vektori a, b i ca <- 1:4b <- 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

Ovdje možemo razjasniti razliku između “skalarnih” i “vektorskih” logičkih operatora u jeziku R. Podsjetimo se, za razliku od drugih programskih jezika, R nudi dvije inačice logičkih operatora “i” i “ili”:

· skalarni: && i ||

· vektorski: & i |

Razlika je u sljedećem:

· Skalarni logički operatori namijenjeni su korištenju s jednoelementnim vektorima, vraćaju jedinstvenu vrijednosti TRUE ili FALSE 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 s logičkim vektorima i kao rezultat daju logički vektor.

Vježba 18: Skalarni i vektorski logički operatori

# zadani su vektori a i ba <- c(T, F, F)b <- c(T, T, F)

# primijenite skalarnu i vektorsku inačicu logičkog operatora "ili"# nad vektorima a i b i ispišite rezultat

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 opravdanoga 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 s usporednim operatorima će možda inicijalno izgledati trivijalan, no potrebno je obratiti posebnu pažnju na rezultate koje ćemo dobiti jer će oni imati vrlo važnu primjenu u nastavku lekcije. Dakle, pogledajmo što se događa kod vektorizacije usporednih operatora.

Vježba 19: Vektorizacija usporednih operatora

# zadani su vektori x i yx <- 1:5y <- 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

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 je indeksima 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 takozvanoga logičkog indeksiranja, što je jedna od metoda indeksiranja koje ćemo upoznati u nastavku.

Indeksni vektoriDefinicija indeksnoga vektora

Do sada smo usvojili dva vrlo važna principa za razumijevanje jezika R:

· “sve je vektor”

· “sve operacije su vektorizirane”.

Jedan od razloga inicijalno strme krivulje učenja ovoga jezika jest i taj da su programeri često naviknuti na intenzivno korištenje programskih petlji te pojedinačnu obradu elemenata kolekcija tako da im princip vektoriziranih operacija često predstavlja dramatičan pomak paradigme. No, ukoliko se ova problematika sagleda sa suprotne strane, može se uočiti da se zapravo radi o bitnom pojednostavljenju sintakse – umjesto da u programu objašnjavamo “kako” nešto učiniti, mi mu samo govorimo “što” želimo i puštamo računalo da se brine o niskorazinskim stvarima kao što je “šetanje po kolekciji”.

Dakle, jezik R jednostavno voli raditi “više stvari odjednom”. No ovo ne znači da smo u praksi ograničeni na pristup “sve ili ništa”, tj. da moramo uvijek sve podatke nekoga skupa obrađivati istodobno. Jezik R nam omogućuje da točno specificiramo nad kojim elementima skupa želimo raditi, ali bez napuštanja principa “sve odjednom” – u tome nam pomažu takozvani indeksni vektori.

Indeksni vektor nije ništa drugo nego običan, elementaran vektor koji predstavlja svojevrsnu specifikaciju elemenata (indekasa) s kojima želimo raditi, bilo da ih želimo dohvatiti ili izmijeniti. Postoje tri osnovna načina indeksiranja (i pripadajućih indeksnih vektora):

· lokacijsko indeksiranje (engl. integer- or location-based indexing), numerički indeksni vektor

· uvjetno indeksiranje (engl. conditional or boolean-based indexing), logički indeksni vektor

· imensko indeksiranje (engl. label-based indexing), znakovni indeksni vektor.

Koje ćemo indeksiranje odabrati ovisi o tome želimo li elementima pristupati ovisno o njihovoj lokaciji, imenu ili prema zadanom uvjetu, a svaki tip indeksiranja u suštini se svodi na korištenje navedenoga tipa vektora kao parametra za operator indeksiranja [.

Upoznajmo detaljno svaki od tipova indeksiranja. U vježbama koje slijede uglavnom ćemo indeksirati jednostavne vektore, no principi koje upoznamo direktno su primjenjivi i na ostale složene strukture (matrice, liste, podatkovne okvire), s obzirom na činjenice koje smo dosad naučili.

Lokacijsko indeksiranje

Lokacijsko indeksiranje je poopćenje već upoznatoga principa indeksiranja 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 indeksiranja.

Vježba 20: Lokacijsko indeksiranje

# zadan je vektor xx <- 1:10

# ispišite prvi element vektora x

# ispišite prva tri elementa vektora x

# ispišite prvi, peti i sedmi element vektora x

Dakle, lokacijski indeksni vektor nije ništa drugo nego običan numerički vektor koji koristimo zajedno s indeksnim operatorom da bismo odredili koje elemente nekoga drugog vektora želimo “zadržati”.

Pogledajmo još neke značajke lokacijskih indeksnih vektora:

Vježba 21: Lokacijsko indeksiranje (2)

# zadan je vektor xx <- 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?

# što vraća negativni indeks izvan granica duljine vektora?

Indeksiranje se ne koristi samo za dohvaćanje elemenata. Kombinacijom operatora indeksiranja i operatora pridruživanja možemo mijenjati elemente vektora (i to također po principu “više elemenata odjednom”):

Vježba 22: Lokacijsko indeksiranje i pridruživanje

# zadan je vektor aa <- 1:10

# postavite sve elemente vektora a od drugog do osmog mjesta na nulu # ispišite vektor a

# nakon toga izvršite sljedeće naredbe:b <- 1:20b[2 * 1:5] <- 0

# razmislite o tome kako izgleda vektor b nakon gornje naredbe# ispišite vektor b i objasnite rezultat

Uvjetno indeksiranje

Ako smo pažljivo razmotrili rezultate dobivene kod primjera s vektoriziranim usporednim operatorima, onda smo mogli vrlo dobro naslutiti kako radi uvjetno indeksiranje. Princip je jednostavan – za indeksni vektor postavljamo logički vektor iste duljine kao i vektor čije elemente želimo dohvatiti. Elementi logičkoga vektora određuju koje elemente zadržavamo (pozicije na kojima se nalazi vrijednost TRUE) a koje odbacujemo (pozicije na kojima se nalazi vrijednost FALSE).

Vježba 23: Uvjetno indeksiranje

# zadan je vektor xx <- 1:10

# napravite logički vektor y duljine 10 s 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

Naredba x[x <= 5], naoko jednostavna, zapravo je 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.

Vježba 24: Uvjetno indeksiranje (2)

# zadani su vektor y i `studenti`y <- seq(1, 100, 7)studenti <- c("Ivo", "Petra", "Marijana", "Ana", "Tomislav", "Tin")

# ispišite 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`)

Ukoliko koncept uvjetnog indeksiranja 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 indeksiranja koji radi na principu dohvaćanja elemenata vektora ovisno o njihovu imenu.

Imensko indeksiranje

Imensko indeksiranje radi na principu eksplicitnog imenovanja elemenata koje želimo “zadržati”. Da bismo mogli koristiti ovakav tip indeksiranja, 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 znakovnoga vektora s odabranim imenima. Moramo voditi računa o tome da vektor imena bude jednake duljine kao originalni vektor!

Vježba 25: Imensko indeksiranje

# zadan je vektor `visine` s postavljenim imenima elemenatavisine <- 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

Vidimo da se imensko indeksiranje očekivano svodi na prosljeđivanje odgovarajućega znakovnog vektora kao parametra indeksiranja.

(NAPOMENA: Pažljiviji čitatelj uočit će jednu neobičnu činjenicu u gornjem programskom kodu – poziv funkcije se koristi kao lvalue (objekt koji nastavlja postojati i nakon obavljanja jedne operacije, za razliku od rvalue koji ne egzistira nakon naredbe u kojoj je korišten)! Odgovor na pitanje zašto je ovakva pohrana moguća 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).

Ako iz nekog razloga poželimo obrisati imena elemenata vektora, jednostavno pozivu funkcije names proslijedimo NULL.

names(visine) <- NULL

Indeksni vektori i matrice

Pokušajmo sada primijeniti naučeno i na složenije podatkovne strukture.

Vježba 26: Indeksni vektori i matrice

# zadana je matrica m` s postavljenim imenima stupacam <- 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 indeksiranja dodajte parametar `drop = FALSE`# parametar odvojite zarezom (kao da se radi o "trećoj" dimenziji indeksiranja)

Indeksni vektori i podatkovni okviri

Sljedeća vježba koristi podatkovni okvir mjesto koji smo već koristili u prethodnim primjerima i vježbama.

Vježba 27: Indeksni vektori i podatkovni okviri

# ispišite tablicu `mjesto` (za referencu)

# ispišite prva tri retka, drugi i peti stupac

# ispišite stupac "prirez"

# ispišite poštanske brojeve i nazive svih mjesta koja # imaju prirez veći od 12 % i broj stanovnika veći od 100.000

Rješavanjem ovoga zadatka mogli smo uočiti uobičajeni obrazac “rezanja” podatkovnog okvira – logički indeksni vektor za retke, znakovni (ili numerički) za stupce. Poznavatelji SQL-a će uočit će sličnost između te naredbe i SQL upita:

SELECT pbr, nazivMjestaFROM mjestoWHERE mjesto.prirez > 12 AND mjesto.brojStanovnika > 100000

Odabir stupaca i redaka nije težak ako se dobro koristimo znanjem o indeksnim vektorima, no kao što se vidi u zadnjem primjeru, sintaksa često nije previše čitljiva (u usporedbi s npr. SQL-ovom sintaksom koja obavlja isti posao). Zbog toga postoje različita proširenja R-a koja ovaj posao uvelike olakšavaju, a koja ćemo detaljno obraditi u jednoj od budućih lekcija koja će se baviti upravljanjem podatkovnim skupovima.

Što ako NE želimo određene elemente u rezultatu, na primjer, želimo sve stupce OSIM stupaca nazivMjesta i prirez? Sjetimo se da negativni numerički indeks znači “izbaci element”. No, kako nam ovo koristi ako stupac identificiramo znakovnim indeksnim vektorom? Jedna od opcija jest da provjerimo koji su po redu ti stupci i onda se koristimo numeričkim indeksnim vektorom, no recimo da trebamo generičko rješenje koje će raditi neovisno o položaju stupaca. Ovdje nam u pomoć nam stiže funkcija which koja pretvara logički vektor u numerički. Procedura je sljedeća:

· dohvatimo vektor imena stupaca (funkcija names)

· stvorimo logički indeksni vektor koji označava koja imena se nalaze u odabranom skupu imena (operator %in%)

· uz pomoć funkcije which pretvorimo ovaj logički indeksni vektor u numerički

· iskoristimo taj numerički vektor uz operator - s ciljem izbacivanja odabranih stupaca.

Primjer 8: Izbacivanje stupaca po imenu

mjesto[, -which(names(mjesto) %in% c("nazivMjesta", "prirez"))]

## pbr prosjPlacaKn brojStanovnika## 1 10000 6359 790017## 2 51000 5418 128384## 3 21000 5170 167121## 4 31000 4892 84104## 5 20000 5348 28434

Možemo uočiti nekoliko problema sa zadnjim primjerom. Prvo, procedura je relativno složena, pogotovo za početnika. Drugo, postoji izvjesna vjerojatnost jedne relativno nezgodne pogreške – ako ni jedan stupac ne odgovara onima navedenim u skupu imena, umjesto da dobijemo sve stupce, mi nećemo dobiti ni jedan (rezultat će biti “prazan” numerički vektor, na koji operator - ne djeluje, tako da mi zapravo doslovno tražimo “nijedan” stupac). Ovo sve pokazuje da se R-ova sintaksa za upravljanje podatkovnim okvirima u određenim slučajevima može pokazati relativno nezgrapnom i nepotrebno kompleksnom. Upravo zbog toga R zajednica nudi niz paketa koji umnogome olakšavaju rad s podatkovnim okvirima te omogućuju pisanje jasnijega, preglednijega programskog kôda s puno manjom vjerojatnosti pogreške. Vjerojatno najpopularniji takav paket jest dplyr, o kojem će biti nešto više riječi u jednoj od nastupajućih lekcija.

Dodatni zadaci za vježbu

1. Stvorite sljedeće vektore:

· (11, 12, 13…, 99)

· (0, 0, 0, 0… , 0) (100 nula)

· (0, 0.1, 0.2…, 1.0).

1. Kolika je suma svih brojeva od 101 do 1001, preskočimo li sve brojeve djeljive s 10? Koristite se funkcijom sum.

1. Stvorite matricu 3 × 3 s brojevima izvođenjem sljedećih naredbi (funkciju sample ćemo pobliže upoznati u jednoj od sljedećih lekcija):

# stvaramo matricu 3 × 3 nasumično odabranih elemenata iz skupa od 1 do 100set.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 daje li umnožak originalne i inverzne matrice jediničnu matricu (za množenje matrica koristite se operatorom %*%).

1. 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 nalazi li se broj 4 u prvom elementu liste

· na zadnje mjesto liste dodajte novu listu s tri vektora a, b i c koji svi sadrže elemente (1,2,3).

1. Učitajte podatkovni okvir mtcars (naredba data(mtcars)). Napravite sljedeće:

· ispišite sve parne retke i stupce s tri ili više slova u nazivu

· stupac mpg pokazuje potrošnju u obliku “milja po galonu goriva”. Dodajte stupac l100km koji će sadržavati potrošnju u litrama na 100 km. Koristite se formulom litara na 100km = 378.5 / milja po galonu

· izbrišite sve retke gdje je broj cilindara (stupac cyl) jednak broju 4.

R i programske paradigmeŠto su programske paradigme?Općenito o programskim paradigmama

Pojam “programske paradigme” predstavlja način na koji programski jezik ili programer dizajnira, tj. oblikuje svoje programe. Postoji niz programskih paradigmi kod kojih ima dosta preklapanja, a programski jezici često podržavaju više istih tako da se pitanje “odabira” programske paradigme često svodi na preuzimanje određenih načela i smjernica koje odgovaraju kako cilju koji programer želi postići tako i svojstvima odabranoga programskog jezika.

U kontekstu ovoga tečaja bavit ćemo se programskim paradigmama, osobito u kontekstu boljega razumijevanja jezika R. Zbog toga nećemo raditi iscrpan pregled postojećih paradigmi kao ni ulaziti u detalje oko svake, već ćemo odabrati tri najčešće spominjane paradigme koje direktno utječu na pristup učenju jezika R: imperativna, objektna i funkcijska paradigma.

Imperativna programska paradigma

Imperativna programska paradigma predstavlja uobičajen pogled na programiranje kao pisanje naredbi koje mogu na određeni način utjecati na stanje programa. Program je u ovom slučaju zapravo zapis nekog algoritma u odabranom programskom jeziku, a čije slijedno izvođenje dovodi do traženog cilja. Program ima definirani tok od početka do kraja, a naredbama kontrole toka utječemo na uvjetno ili ponavljano izvođenje nekih njegovih segmenata (tzv. if-else naredbe i programske petlje).

Objektna programska paradigma

Objektna programska paradigma temelji se na stvaranju i upravljanju takozvanim “objektima”, tj. programskim strukturama koje enkapsuliraju podatke (tzv. “atributi”) i metode (funkcije koje obavljaju neki posao, često vezan za atribute). Programiranje se svodi na dizajn ovakvih objekata čije metode omogućuju međusobnu komunikaciju, a čije izvođenje često dovodi do promjene internih stanja objekata. Objektno programiranje uvodi pojmove kao što su “klase i instance” (predlošci objekata te sami objekti), nasljeđivanje (izvođenje novih objekata proširenjem predložaka postojećih), polimorfizam (jedinstveno sučelje nad različitim objektima) i sl.

Funkcijska programska paradigma

Osnovni princip funkcijske programske paradigme jest da je fokus na poslu koji se obavlja, ne na objektima nad kojima se obavlja posao. Drugim riječima, ako se objektno orijentirano programiranje usredotočuje na “imenice”, funkcijsko se usredotočuje na “glagole”. Programiranje se svodi na stvaranje i provođenje funkcija, a izbjegava se bilo kakvo “pamćenje stanja”. Poziv funkcije s istim ulazima uvijek mora rezultirati identičnim izlazima. Ovakav način programiranja stavlja visoku važnost na reproducibilnost i predvidljivost rezultata programskoga kôda, čineći razumijevanje i korištenje razvijenih programa jednostavnijim. Također, jedna od karakteristika funkcijske programske paradigme jest ta da se funkcije tretiraju kao “građani prvoga reda” – one nisu nešto “zamotano” u objekte, one su samostalne, imaju svoju referencu, mogu se slati u druge funkcije kao parametri ili vraćati kao rezultat funkcije. Funkcijska programska paradigma često se smatra malo težom za učenje od imperativne i (donekle) objektne, ali neupitna je njezina učinkovitost kod određenih skupina problema.

Programski jezici i programske paradigmeOdabir jezika = odabir paradigme?

Već smo rekli da programski jezici često podržavaju više programskih paradigmi te omogućavaju programerima prilagodbu na paradigmu koja najviše odgovara njihovim osobnim preferencama ili cilju koji žele obaviti. Usprkos tomu, određeni jezici “naginju” određenoj paradigmi, tako se npr. C++ i Java često smatraju “primarno” objektnim jezicima, Erlang i Haskell funkcionalnima itd. Postoje jezici, kao na primjer Python, za koje je teško jasno klasificirati naginju li određenoj paradigmi ili ne.

R i programske paradigme

Kod programskog jezika R dosta je teško reći radi li se o “univerzalnom” jeziku (s gledišta paradigmi) ili ga se može svrstati u jezike koji su snažno naklonjeni određenoj paradigmi.

Neupitno je da je R domenski orijentirani jezik, tj. da je njegova primarna funkcija analiza podataka. R uredno podržava sve elemente imperativnih jezika – u R-u možemo pisati naredbe koje mijenjaju stanje programa, a koje je reprezentirano kroz varijable. Isto tako, gledajući način na koji je R dizajniran, uočava se da on sadržava jasno definirane elemente objektne programske paradigme – entiteti kojima upravljamo programirajući u R-u su objekti, a R podržava sve nužne objektno-orijentirane principe kao što su enkapsulacija, polimorfizam i nasljeđivanje. No, učenjem R-a lako ćemo spoznati da R radi s objektima na dosta neobičan način, zaobilazeći neke ustaljene konvencije prisutne u drugim jezicima, te da pisanje R programskoga kôda vrlo često naginje funkcionalnom programiranju – funkcije su “punokrvni” objekti, prirodno je slati ih kao parametre ili primati kao rezultat drugih funkcija, a usredotočenost na “radnju”, tj. “posao” koji moramo izvesti vrlo je sukladno procesu analize podataka gdje se naše programiranje zapravo svodi na razvoj metode koja će “obraditi” podatke na određeni način i koja bi za isti ulazni skup morala uvijek davati iste rezultate.

Zaključak koji se nameće je sljedeći – kod učenja R-a treba naučiti i njegove imperativne i objektne elemente, ali R ne bismo trebali tretirati kao “univerzalni” programski jezik jer je cilj koji njegovim korištenjem želimo postići u jasnom i očitom skladu s funkcijskom programskom paradigmom. Shodno tome, iako poznavanje detalja oko funkcijske programske paradigme nije ključno za svladavanje R-a, prihvaćanje R-a kao funkcionalnoga jezika te ulaganje truda u svladavanje barem nekih principa ove paradigme može se višestruko isplatiti.

U nastavku ćemo se baviti elementima jezika R koji odgovaraju različitim paradigmama. Naučit ćemo naredbe kontrole toka tipične za imperativni pristup, spominjat ćemo i objekte, ali fokus će ipak biti na funkcijama: kako R “vidi” funkcije, kakve tipove funkcija podržava R, kako pisati vlastite funkcije te kako ih koristiti kao “prvoklasne” objekte.

Upravljanje programskim tokomUvjetno izvođenje naredbiNaredba if - else

Ako program gledamo kao niz naredbi, onda nam uvjetno izvođenje omogućuje da se određene naredbe izvedu samo ako su ispunjeni određeni preduvjeti (ili samo ako nisu ispunjeni). Ovo je u gotovo svakom programskom jeziku poznato kao klasični if - else konstrukt, a izgleda ovako:

if (izraz) {blok} else {blok}

gdje se pod izraz misli na logički izraz s jednoelementnim logičkim rezultatom.

Uvjetno izvođenje ne koristimo često u interaktivnom radu, ali je vrlo bitno ako razvijamo vlastite programske funkcije ili skripte. Korištenje if - else konstrukta je samo po sebi jasno i trivijalno, no moramo pripaziti da ne učinimo jednu pomalo teško uočljivu grešku. Pokušajmo tu grešku uočiti i ispraviti u sklopu sljedećega zadatka.

Vježba 28: Naredba if-else

# izvršite sljedeću naredbu uvjetnog izvođenjaif (2 > 1) print("Uspjeh!")

# preinačite sljedeće naredbe tako da ne rezultiraju greškomif (1 > 2) print("Uspjeh!")else print("Nuspjeh!")

Dakle, potencijalni problem može nam stvarati else dio naredbe ako ga stavimo u novi red nakon što je naredba if formalno “završila” s gledišta interpretera. Nakon prvog retka interpreter će doći do vrijednosti FALSE, ispis neće biti izvršen te će interpreter nastaviti s ‘novom’ naredbom koja to u ovom slučaju nije pa će javiti grešku. Postavljanjem naredbe u blok koji ne zatvaramo u istom retku dajemo interpreteru na znanje da “ima još”, tako da onda uredno možemo ubaciti i željeni else dio.

Naredba ifelse

Na ovom mjestu nije loše naučiti i funkciju ifelse koja predstavlja svojevrsnu “vektoriziranu” inačicu naredbe if - else. Ona, strogo gledajući, nema veze s uvjetnim izvođenjem naredbi, ali je vrlo korisna u radu s programskim okvirima kada želimo dodati stupac – binarnu “zastavicu” koja opisuje je li neki uvjet ispunjen ili ne.

Rad ove funkcije najlakše možemo prikazati uz pomoć primjera.

Primjer 9: Funkcija ifelse

a <- 1:3b <- c(0, 2, 4)

x <- ifelse(a < b, 2, 5)x

## [1] 5 5 2

Programske petljeR i programske petlje

U programskom jeziku R imamo tri tipa petlji:

· repeat – beskonačna petlja

· while – petlja s provjerom uvjeta na početku

· for – iteratorska petlja (“petlja s poznatim brojem ponavljanja”).

Petlja repeat

Petlja repeat je najjednostavnija petlja. Ona ima sljedeću sintaksu:

repeat {blok}

Ovdje se radi o “beskonačnoj” petlji gdje se nakon završetka bloka on ponovo izvršava i tako unedogled. Jedini način izlaska iz ovakve petlje jest korištenje naredbe break. Pored ove naredbe imamo i naredbu next koja će preskočiti ostatak bloka, ali neće izaći iz petlje već će nastaviti izvršavati blok od početka.

Pogledajmo kako radi ova petlja u sljedećoj vježbi.

Vježba 29: Petlja repeat

# prije izvršavanja sljedećeg bloka odgovorite na pitanja:# - hoće li se petlja izvršavati beskonačno?# - što će se ispisati na zaslonu?

i <- 1repeat { i <- i + 1 if (i %% 2 == 0) next print(i) if (i > 10) break}

## [1] 3## [1] 5## [1] 7## [1] 9## [1] 11

Često unaprijed znamo uvjet izlaska iz petlje te bismo ga htjeli staviti na jasno vidljivo mjesto tako da nije “skriven” u tijelu petlje. Za to nam pomaže takozvana while petlja.

Petlja while

Petlja while predstavlja “najčišći” oblik programske petlje čija sintaksa doslovno glasi “dok je uvjet ispunjen, ponavljaj navedeni kôd”:

while (uvjet) {blok}

Vježba 30: Petlja while

# dodajte uvjet petlje tako da se ona izvrši# točno 7 puta

i <- 1

while() { print(i) i <- i+1}

Kod ove petlje moramo paziti da se u određenoj iteraciji moraju stvoriti uvjeti za izlaz, inače ona također postaje “beskonačna” petlja. Usprkos tomu što imamo jasno definiran način izlaska iz petlje, mi i u ovoj petlji možemo slobodno koristiti ključne riječi next i break, koje imaju istu funkciju kao i kod petlje repeat.

Petlja for

Petlja for ili “iteratorska petlja” obično služi za “šetanje” po nekoj podatkovnoj strukturi (najčešće vektoru), uzimajući element po element i nešto radeći s njim. Ona koristi ključnu riječ for, ime nove (iteratorske) varijable, ključnu riječ in te vektor čije se vrijednosti uzimaju jedna po jedna i koriste unutar petlje (uočite da navedeni in nije isto što i operator %in% koji provjerava nalazi li se neki element u nekom skupu!). Sintaksa ove petlje je sljedeća:

for (i in v) {radi nešto s i}

Uočimo da ovdje varijabla i nije “brojač” – u svakoj iteraciji petlje ona postaje vrijednost elementa do kojeg smo došli. Ako baš želimo iterirati po indeksima, a ne po samim elementima, onda možemo koristiti konstrukt for (i in 1:length(a)).

Vježba 31: Petlja for

a <- seq(-10, 10, 4)

# ispišite elemente vektora `a` jedan po jedan # uz pomoć petlje `for`# pristupajte elementima direktno

# ponovite isto, ali iterirajte po indeksima

Uočite da je drugi način bolji ako želite mijenjati elemente vektora ili imati informaciju na kojem se mjestu unutar originalnoga vektora trenutačno nalazite.

Korištenje programskih petlji u jeziku R

Sada kad smo naučili sintaksu petlji važno je naglasiti jednu činjenicu – u programskom jeziku R u pravilu se ne preporučuje korištenje programskih petlji. Iako ovo inicijalno možda djeluje neobično s obzirom na sveprisutnost petlji u drugim programskim jezicima, razlog je jednostavan – R je jezik dizajniran upravo na način da naredbe jezika rade po principu “više stvari odjednom”. Već smo vidjeli da principi vektorizacije i recikliranja učinkovito obavljaju poslove koji bi u drugim programskim jezicima zahtijevali petlju, a u poglavljima koja slijede vidjet ćemo da R nudi i mnoge druge konstrukte koji nude poželjniju alternativu od eksplicitnog korištenja programskih petlji.

Recimo, sljedeći primjer je sintaksno potpuno ispravan:

# primjer nepotrebnoga korištenja petljea <- 1:5b <- 6:10c <- numeric()

for (i in 1:length(a)) c[i] <- a[i] + b[i]

ali vjerojatno radi sporije i puno je nečitljiviji od:

# R-ovska sintaksaa <- 1:5b <- 6:10

c <- a + b

Sve navedeno, naravno, ne znači da petlje u R-u ne smijemo koristiti već samo to da bi njihovo korištenje trebalo biti popraćeno dodatnim razmatranjem stvarne potrebitosti petlje na tom mjestu te postojanja alternativne sintakse (ili funkcije!) koja isti posao obavlja deklarativno (i potencijalno brže jer su mnoge rutine R-a implementirane u jeziku C). Rano prihvaćanje “R-ovskoga” načina razmišljanja rezultirat će dugoročnim pozitivnim efektima koji će se očitovati kompaktnijim, čišćim i često učinkovitijim programskim kôdom.

Funkcije u jeziku RUgrađene funkcijePaketi i staza pretrage

Pod pojmom “ugrađenih funkcija” nekoga programskog jezika često mislimo na funkcije koje su nam odmah dostupne nakon instalacije odabrane distribucije toga jezika. Kod programskoga jezika R vrlo brzo se upoznajemo s takvim funkcijama učeći osnovnu sintaksu jezika te neke njegove temeljne koncepte. Tako smo do sada zasigurno već upoznali razne numeričke funkcije (log, abs, sqrt, round i sl.), funkcije za stvaranje vektora (rep, seq i sl.), funkcije za rad s paketima (install.packages, library i sl.) i tako dalje. Operatori kao što su +, *, :, <- i sl. su također zapravo samo funkcije, izvedene na način da omogućuju korištenje specifične “operatorske” sintakse.

Sve ove funkcije logički su organizirane u zasebne kolekcije zvane “paketi”. Novi paket učitavamo uz pomoć naredbe library(ime_paketa), a R okolina automatski pri pokretanju učitava predefiniranu kolekciju korisnih paketa. Potpuna sintaksa za zvanje funkcije iz nekog paketa jest ime_paketa::ime_funkcije(parametri).

Uzmimo za primjer paket stats koji sadrži bogati skup funkcija vezanih za statističke obrade – ovaj će paket već biti učitan čim pokrenemo distribuciju jezika R. Odaberimo iz ovog paketa jednu od vrlo često korištenih funkcija zvanu rnorm. Ona vraća numerički vektor željene duljine čiji su elementi nasumično odabrani iz normalne distribucije s aritmetičkom sredinom 0 i standardnom devijacijom 1 (ove vrijednosti možemo i promijeniti uz pomoć parametara mean i sd).

Pozovimo ovu funkciju u sljedećoj vježbi.

Vježba 32: Poziv funkcije iz odabranog paketa

# stvorite vektor x koji će imati 10 slučajnih elemenata# izvučenih iz standardne normalne distribucije# koristite puni naziv funkcije `rnorm` iz paketa `stats`

# zaokružite elemente vektora x na dvije decimale# koristite puni naziv funkcije `round` iz paketa `base`

# ispišite vektor x

Iako je ovo sintaksno ispravan način pozivanja funkcije, R nam omogućuje da izuzmemo nazive paketa i jednostavno navedemo samo naziv funkcije.

Vježba 33: Poziv funkcije bez imena paketa

# stvorite vektor y po istom principu kao i vektor x# obavite sve u jednom retku# koristite nazive funkcija bez naziva paketa

# ispišite y

Kako je R znao koju funkciju pozvati ako se one nalaze u različitim paketima? Odgovor na to daje nam koncept “staze pretrage”. Naime, R učitava pakete određenim slijedom koji se zapisuje u spomenutu stazu pretrage (engl. search path) pri čemu se paket koji je zadnje učitan postavlja na drugo mjesto u stazi, odmah iza radne, tj. “globalne” okoline (koja je uvijek na prvom mjestu). Kada pozivamo neku funkciju (ili referenciramo neku varijablu), a ista nije prisutna u radnoj okolini, R će se “prošetati” stazom pretrage i slijedno tražiti tu varijablu ili funkciju u paketima koji se nalaze u njoj. Pretragu će završiti čim nađe prvo “poklapanje”.

Stazu pretrage možemo i sami vidjeti uz pomoć funkcije search.

Vježba 34: Staza pretrage

# ispišite stazu pretrage uz pomoć funkcije `search` (bez parametara)

# učitajte novi paket uz pomoć funkcije `library` (npr. paket `dplyr`)# (ako paket nije instaliran, morate ga prvo dohvatiti uz pomoć funkcije `install.packages`)

# ispišite ponovo stazu pretrage

Uočite dvije bitne stvari. Prvo, globalna okolina uvijek ostaje na prvom mjestu, tako da nijedan novoučitani paket ne može “pregaziti” varijable i funkcije radne okoline. Drugo, kod učitavanja novoga paketa možemo dobiti upozorenje da su sada neki objekti “maskirani”. Ovo se događa kada novi paket sadrži objekte s imenima koja već postoje u stazi pretrage. Ovo ne znači da su maskirani objekti sada nedostupni, već samo da njima sada moramo pristupati preko punog imena (ime_paketa::ime_funkcije). Zbog ovoga moramo biti pažljivi kod izrade vlastitih programskih skripti ili izvještaja, te uvijek koristiti puno ime ako se bojimo da će zbog naknadnog učitavanja nekog paketa neki dio programskoga kôda početi raditi neispravno jer će se unutar njega pozivati “kriva” funkcija.

Dohvat pomoći

Kada koristimo programski jezik R, češće se oslanjamo na već postojeće funkcije ili na funkcije iz dodatnih paketa koje je netko drugi stvorio i dao na uporabu R zajednici. Zbog toga je vrlo važno znati kako pronaći informaciju o tome postoji li već, za neki zadatak koji želimo izvršiti, stvorena funkcija te, ako da, na koji ju način pozvati.

Da bismo riješili prvi problem, utvrđivanje postoji li već neka funkcija i, ako postoji, pronaći gdje se nalazi, potrebno je koristiti dostupnu dokumentaciju, podsjetnike ili internetske tražilice. Alat RStudio nudi odlične podsjetnike za razne aspekte i razine korištenja jezika R, a koji se vrlo lako mogu pronaći upisujući “RStudio Cheat Sheets” u internetsku tražilicu. Isto tako, vrlo često odgovor na pitanje o tome postoji li neka funkcija možemo dobiti postavljanjem jasno sročenoga pitanja u tražilicu na engleskom jeziku, na primjer, “What is a function for adding vector elements in R?”.

Jednom kada znamo koju funkciju odabrati, te po potrebi instaliramo paket u kojem se ona nalazi, idući korak jest pregled dokumentacije o navedenoj funkciji. Za to postoji funkcija help, iako je često lakše staviti znak ? i ime funkcije, na primjer, ?rnorm.

Primjer 10: Traženje pomoći

# isprobajte nazive i nekih drugih funkcija?rnorm

Pozivanje pomoći za neku funkciju često daje informacije i o nekim srodnim funkcijama, a na dnu dokumentacije često se daju i primjeri korištenja. Ako želimo, primjere možemo i odmah zatražiti uz pomoć funkcije examples.

Iako dokumentacija funkcije često može djelovati pomalo nečitljivo ili čak zastrašujuće, potrebno se naviknuti na ovakav pristup traženja pomoći jer je to najbrži i najučinkovitiji način za pisanje kvalitetnoga, ciljno orijentiranoga programskog kôda. Često nije loše ni uz pomoć internetske tražilice potražiti dodatne primjere korištenja tražene funkcije, ako ih se može pronaći, jer programeri često nailaze na slične probleme te je moguće da objašnjenje ili rješenje problema s nekom funkcijom već postoji te je lako dostupno uz postavljanje pravog upita u internetsku tražilicu.

Klasične, primitivne i interne funkcije

R je jezik otvorenoga kôda, u što se vrlo lako može uvjeriti uz pomoć jednostavnog eksperimenta. Pokušajmo pozvati funkciju sd (za traženje standardne devijacije elemenata vektora), ali na način da napišemo samo ime funkcije, bez zagrada i parametara.

Primjer 11: Ispis funkcije sd

sd

## function (x, na.rm = FALSE) ## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), ## na.rm = na.rm))## ##

Možemo uočiti da nam R uredno daje ispis programskoga kôda ove funkcije. Točnije, vidimo programski kôd, memorijsku adresu na kojoj je funkcija pohranjena (ako se radi o unaprijed prevedenoj funkciji, što je često slučaj s ugrađenim funkcijama) te paket u kojem se funkcija nalazi. Uvid u izvorni kôd može nam pomoći kod eventualnih problema s korištenjem funkcije, ako nam čitanje dokumentacije nije već pomoglo, a imamo i opciju kopiranja kôda i stvaranje vlastite inačice funkcije ako želimo.

Možemo zatražiti i samo parametre, tijelo ili okolinu funkcije uz pomoć funkcija formals, body i environment.

Primjer 12: Ispis detalja o funkciji sd

# ulazni parametri funkcije (lista!)formals(sd)

# tijelo funkcijebody(sd)

# okolina funkcije (radna okolina ili paket)environment(sd)

## $x## ## ## $na.rm## [1] FALSE## ## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), ## na.rm = na.rm))##

Kod nekih funkcija nećemo uspjeti dobiti ispis, na primjer, kod funkcije sum za sumiranje elemenata vektora.

Primjer 13: Ispis funkcije sum

sum

## function (..., na.rm = FALSE) .Primitive("sum")

Ovakve “primitivne” funkcije nalazimo samo u osnovnom (base) paketu. One formalno i nisu “prave” R funkcije – ovdje se radi o funkcijama koje su implementirane na niskoj razini (najčešće u jeziku C) koje su zbog toga i vrlo učinkovite. Zbog specifičnost ovakvih funkcija dizajneri jezika R ih u pravilu izbjegavaju tako da se nove primitivne funkcije implementiraju samo ako za to postoje dovoljno jaki razlozi.

Pored primitivnih funkcija imamo i takozvane “interne” funkcije, koje ćemo prepoznati po ispisu .Internal umjesto .Primitive. Ovdje se također radi o niskorazinskim funkcijama koje nisu “prave” R funkcije iako imaju neka dodatna svojstva koje primitivne nemaju, tj. kod njihovoga pozivanja logika izvođenja se provodi sličnije “pravim” R funkcijama.

U praksi nema neke konkretne potrebe za učenjem detalja o primitivnim i internim funkcijama – time se bavi programerski tim koji dizajnira jezik R, a prosječnim korisnicima je dovoljna informacija da se logika ovakvih funkcija odvija na niskoj razini, vrlo učinkovito, ali da nemamo jednostavan uvid u njihovo funkcioniranje pored onoga što nudi dokumentacija (što je često i dovoljno, jer je svrha ovih funkcija često očigledna).

Konačno, za neke funkcije možemo dobiti prilično zbunjujući ispis, npr. za funkciju mean:

Primjer 14: Ispis funkcije mean

mean

## function (x, ...) ## UseMethod("mean")## ##

Vidimo da smo umjesto tijela funkcije dobili poziv funkcije neobičnog imena UseMethod. Ovakav ispis ćemo zapravo često susretati ako pokušamo pogledati kako izgledaju neke popularne ugrađene funkcije, kao na primjer, print, plot ili summary. Razlog tomu je što su ovo zapravo takozvane “generičke” funkcije koje su direktno povezane s načinom na koji R realizira svoj objektni model. Više o njima naučit ćemo u idućoj lekciji.

Korisnički definirane funkcije

Već smo rekli da standardni način korištenja programskoga jezika R uglavnom uključuje primjenu postojećih funkcija, bilo da se radi o funkcijama iz paketa isporučenih s R distribucijom ili onih naknadno dohvaćenih iz CRAN repozitorija. No, tijekom rada u R-u često se pojavljuje potreba za programiranjem vlastite funkcije, bilo da se radi o jednokratnoj, “anonimnoj” funkciji ili implementaciji nekoga složenijeg algoritma ili procesa koji planiramo naknadno koristiti u budućim programima. Shodno tome, svladavanje jezika R neizbježno uključuje i učenje pisanja vlastite, korisnički definirane funkcije.

Sintaksa pisanja funkcije

U općenitom slučaju, definicija nove funkcije izgleda ovako:

ime_funkcije <- function(ulazni argumenti) { tijelo funkcije}

Uočimo da kod definicije funkcije koristimo operator <-. Ovo nije slučajno – definicija funkcije nije ništa drugo nego stvaranje objekta klase function koji onda pridružujemo određenoj varijabli; ime varijable zapravo je “naziv” funkcije.

Funkcija prima nula ili više ulaznih parametara (argumenata). Za razliku od programskih jezika kao što su C++ ili Java, u R-u ne definiramo eksplicitno njihove tipove – ulazni argumenti imaju samo ime te opcionalno nazivnu vrijednost.

Cilj funkcije je vratiti rezultat u pozivajuću funkciju ili program. Funkcija formalno može vratiti samo jednu vrijednost, što nije nužno restrikcija ako želimo vratiti više vrijednosti, jednostavno taj skup vrijednosti uokvirimo u vektor, listu ili objekt. Ključna riječ return je opcionalna – funkcija vraća rezultat zadnjeg izraza u funkciji pa je često dovoljno navesti samo varijablu koja predstavlja povratnu vrijednost kao zadnji red funkcije.

Funkcija je, dakle, jednostavno komad programskoga kôda koji na osnovi ulaznih argumenata i programske logike vraća neki rezultat. Ako želimo povećati robusnost funkcije na način da ćemo odbiti izvođenje posla ako nisu zadovoljeni određeni uvjeti, unutar tijela funkcije možemo koristiti funkciju stopifnot(). Ova funkcija izračunava zadani logički izraz i prekida uokvirujuću funkciju ako navedeni uvjet nije istinit.

Vježba 35: Prva korisnički definirana funkcija

# napišite funkciju `veci` koja prima dva numerička vektora iste duljine # i vraća vektor koji sadrži veći od dva elementa na istim mjestima# ako jedan ili oba vektora nisu numerički ili nisu iste duljine, # funkcija mora izbaciti grešku# u funkciji nemojte koristiti petlje## NAPUTAK: sjetimo se funkcije `ifelse`!

# pozovite funkciju `veci` nad kombinacijama vektora# c(T, F, T) i c(1, 2, 3)# c(1, 2, 3, 4) i c(5, 6, 7)# c(1, 2, 3) i c(0, 4, 2)

# (preporuka – drugi dio zadatka isprobati direktno u konzoli!)

Kod poziva funkcije možemo, ali ne moramo, navesti imena parametara, a R dozvoljava miješanje imenovanih i neimenovanih parametara (iako to nije nešto što bismo trebali često koristiti u praksi). Kada R bude povezivao poslane vrijednosti s formalnim parametrima, imenovani parametri imat će prioritet te će se prvi razriješiti, a potom će se redom razrješivati neimenovani parametri.

U ovo se možemo uvjeriti u sljedećoj vježbi. Ova vježba usput demonstrira i korištenje jedne vrlo korisne funkcije – paste, koja konkatenira znakovne nizove uz automatsko dodavanje razmaka (za spajanje bez razmaka postoji alternativna funkcija paste0).

Vježba 36: Parametri funkcije

ispisiABC <- function(a, b, c) { print(paste("A:", a, "B:", b, "C:", c)) }

# razmislite – što ispisuje sljedeći poziv funkcije? ispisiABC(1, a = 2, 3)

U praksi bismo se trebali držati konvencije da prvo koristimo neimenovane parametre, a potom imenovane. Uobičajeno je da postavljamo samo one imenovane parametre čija nam nazivna vrijednost ne odgovara, pri čemu strogi raspored nije bitan (iako će praćenje rasporeda zadanoga potpisom funkcije povećati čitljivost našega kôda).

Ako želimo napisati funkciju koja prima proizvoljan broj argumenata, koristimo se elementom ..., tj. trotočkom. Primjer ovakve funkcije jest gore prikazana ugrađena funkcija paste koja može primiti proizvoljan broj znakovnih nizova. Ako koristimo trotočku u našoj funkciji, u potpisu je u pravilu stavljamo na kraj liste argumenata, a unutar same funkcije ju potom jednostavno pretvorimo u listu te pristupamo njezinim parametrima na način koji nam odgovara.

Vježba 37: Funkcija s proizvoljnim brojem parametara

ispisiParametre <- function(...) { parametri <- list(...) for (p in parametri) print(p)}

# pozovite gornju funkciju s proizvoljnim parametrima

Princip kopiranja-kod-izmjene (engl. copy-on-change)

Jedno od češćih pitanja koje se postavlja kod učenja novih programskih jezika jest rade li funkcije na način “poziva preko vrijednosti” (engl. call-by-value) ili “poziva preko reference” (engl. call-by-reference). Razlika se svodi na sposobnost funkcije da mijenja vrijednosti varijabli koje su poslane na mjestu formalnih argumenata funkcije; kod call-by-value principa u funkciju se šalju samo “vrijednosti” parametara, tj. “kopije” originalnih argumenata. S druge strane, kod call-by-reference principa funkcija prima “reference” originalnih varijabli, tj. ponaša se kao da su originalne varijable proslijeđene funkciji i sve izmjene nad njima odraziti će se u pozivajućem programu.

Jezik R koristi hibridni princip poznat po nazivom “kopiranje kod izmjene” (engl. copy-on-modify). Kod ovog principa u funkciju se prosljeđuju reference argumenata, što nam omogućuje da prenosimo i “velike” varijable bez straha da će doći do nepotrebnog kopiranja. No ovo vrijedi samo ako funkcija ne mijenja vrijednost dobivenih varijabli – u trenutku kada funkcija pokuša provesti bilo kakvu izmjenu, provodi se kopiranje varijable i funkcija dalje nastavlja rad na kopiji. Zbog ovoga se kaže da R kao takav ne podržava call-by-reference (usputno – jedan razlog uvođenja objekata tipa “reference classes”, tj. RC objekata u jezik R upravo je uvođenje ovoga principa).

Pogledajmo što se događa kada naivno pokušamo izmijeniti varijablu iz pozivajuće okoline.

Primjer 15: Pokušaj izmjene varijable iz pozivajuće okoline