9 Operator cjevovoda i uredni podaci
9.1 Operator cjevovoda
Pogledajmo sljedeći primjer: zamislimo da u jeziku R želimo stvoriti 100 nasumičnih realnih varijabli u rasponu [0,100], zaokružiti ih na dvije decimale, iz ovog skupa odabrati uzorak od 10 varijabli, izračunati aritmetičku sredinu uzorka i ispisati ga na zaslon. Jedno od mogućih programskih rješenja moglo bi biti sljedeće:
set.seed(1234) # (zbog ponovljivosti)
# rješenje gornjeg primjera
rez <- runif(100, 0, 100) # 100 nasumičnih varijabli iz uniformne razdiobe od 0 do 100
rez <- round(rez,2)
rez <- sample(rez, 10)
rez <- mean(rez)
rez
## [1] 51.123
Ovakav kod ima dosta nepotrebnog ponavljanja - u svakom retku koristimo varijablu rez
koja čuva međurezultate i operator pridruživanja pomoću kojeg pridružujemo nove rezultate varijabli rez
. Alternativno, mogli smo sve obaviti u jednom retku.
Zadatak 9.1 - učahurene funkcije
## [1] 51.123
Ovdje vidimo jedan tipičan primjer “kodnog sendviča” koji nije problem samo u R-u, već se pojavljuje u većini programskih jezika - rezultat jedne funkcije postaje ulaz u drugu te ukoliko želimo sve obaviti bez eksplicitnog čuvanja međurezultata kao rezultat ćemo dobiti programski kod koji je podložan greškama kod pisanja te je vrlo teško čitljiv.
Prirodan način interpretacije ovakvog primjera bio bi “slijeva na desno”; kad obavimo jedan posao, rezultat postaje ulaz u drugi posao i tako sve do završetka procesa. Ako bi postojao način kada bi mogli ovakvu intuitivnu interpretaciju predočiti programskim kodom, pojednostavili bismo si ne samo pisanje koda, već bi takav kod postao daleko čitljiviji i lakši za održavanje i eventualnu naknadnu prilagodbu. Upravo ovo bila je motivacija za razvoj tzv. “pipeline” operatora kojeg nudi paket magrittr
(Bache and Wickham 2014).
Paket čudnog imena zapravo je inspiriran imenom apstraktnog slikara Renea Magrittea, točnije njegovom slavnom slikom “La trahison des images” koja prikazuje lulu ispod koje su riječi “Ceci n’est pas une pipe”. Na isti način paket magrittr
donosi “pipeline” ili “pipe” operator %>%
koji “nije lula”. Štogod mislili o navedenom slikarskom djelu ili igri riječi koja je inspirirala ovaj paket, ono što je neporecivo jest činjenica da “pipeline” operator programski kod čini daleko čitljivijim te da je postao izrazito omiljen u R zajednici, pogotovo kod procedura koje uključuju intenzivno “ulančavanje” funkcija.
Kako radi %>%
operator? Vrlo jednostavno - postavimo ga nakon poziva neke funkcije i iza njega navedemo poziv druge funkcije u kojem mjesto rezultata prve naznačimo točkom. Ovo možemo raditi koliko god puta želimo, tj. ovisno koliko poziva “ulančavamo”.
Ako je rezultat prethodne funkcije na prvom mjestu sljedeće funkcije, onda se točka (štoviše, cijeli taj argument) može izbaciti, tako da je sintaksa još kraća:
Ukoliko ne želimo koristiti točku, moramo samo voditi računa da su pozivi funkcija u lancu zapravo formalno nepravilni, jer imaju “nevidljivi” prvi argument. Usprkos tome, mnogi R programeri vole ovakvu sintaksu jer zahtijeva manje tipkanja i nešto je preglednija, a spomenuta nepravilnost ne smeta dok god je programer upoznat sa postojanjem “nevidljivog” argumenta.
Probajmo sada preoblikovati naš prvi primjer uz pomoć %>%
operatora.
Zadatak 9.2 - operator %>%
Uočite kako čitanjem gornjeg programskog koda vrlo lagano interpretiramo smisao te linije programskog koda, pogotovo u usporedbi sa istom naredbom napisanom u obliku “sendviča”.
Krajnji rezultat našeg “lanca” funkcija možemo pohraniti uobičajenim načinom:
ali je možda vizualno konzistentnije koristiti “obrnuti” operator pridruživanja: ->
.
Uočite da u situacijama kada je rezultat prethodne funkcije jedini parametar sljedeće možemo izbaciti zagrade u potpunosti (dakle u gornjim primjerima sum
, sum()
ili sum(.)
bi svi radili jednako).
Pokušajmo sada kombinirati %>%
operator i lapply
na primjeru već viđenom u poglavlju o funkcijama iz porodice apply
.
Zadatak 9.3 - funkcija lapply
i operator %>%
l <- list(a = 1:10, b = 10:20, c = 100:200)
# stvorite matricu koja će sadržavati prvi i zadnji element svakog elementa liste
# elementi moraju biti poredani po retcima
# koristite funkcije lapply, unlist i matrix te %>% operator
# rezultat spremite u varijablu `rez`
# ispišite `rez`
l <- list(a = 1:10, b = 10:20, c = 100:200)
l %>% lapply(function(x) c(x[1], x[length(x)])) %>% unlist %>% matrix(ncol = 2, byrow = T) -> rez
rez
## [,1] [,2]
## [1,] 1 10
## [2,] 10 20
## [3,] 100 200
Operator cjevovoda vrlo je pogodan u sprezi s “klasičnim” funkcijama, no možemo naići na problem kada ga želimo kombinirati s drugim operatorima. Uzrok problema jest sintaksa - operator cjevovoda svoju učinkovitost postiže upravo nametanjem nove, “slijedne” sintakse, koja nije kompatibilna sa sintaksom koju nameću drugi operatori, kao npr. +
, %%
ili [
.
Ukoliko nam je zaista bitno da u našem programskom kodu imamo “neprekinuti” lanac poziva funkcija koji će sadržavati ne samo funkcije, nego i druge operatore, onda je jedno od rješenja koristiti operatore kao “obične” funkcije. Naime, svaki operator je zapravo funkcija koja dijeli ime s operatorom (uz korištenje backtick navodnika kako bi se mogli koristiti simbolima), tako da su sljedeći parovi izraza zapravo ekvivalentni:
Primjer - operatori kao funkcije
# svaki par naredbi jest ekvivalentan
2 + 3
`+`(2, 3)
1 : 5
`:`(1, 5)
x <- c(1, 2, 3)
`<-`("x", c(1,2,3))
x[1]
`[`(x, 1)
## [1] 5
## [1] 5
## [1] 1 2 3 4 5
## [1] 1 2 3 4 5
## [1] 1
## [1] 1
Pokušajmo ovaj princip iskoristiti u sljedećoj vježbi.
Zadatak 9.4 - složenija uporaba operatora cjevovoda
set.seed(1234)
# "uredite" sljedeću naredbu uz pomoć operatora cjevovoda
matrix(table(sample(round(sqrt(sample(1:10000, 10000, replace = T))), 100))[1:9], 3, 3)
## [,1] [,2] [,3]
## [1,] 1 2 1
## [2,] 1 3 1
## [3,] 1 1 2
set.seed(1234)
# "uredite" sljedeću naredbu uz pomoć operatora cjevovoda
1:10000 %>% sample(10000, replace = T) %>% sqrt %>% round %>%
sample(100) %>% table %>% `[`(1:9) %>% matrix(3, 3)
## [,1] [,2] [,3]
## [1,] 1 2 1
## [2,] 1 3 1
## [3,] 1 1 2
%>%
operator je posebno pogodan za upravljanje podatkovnim skupovima, pogotovo u scenarijima kada imamo definiranu proceduru transformacije podataka (npr. filtriramo neke retke, potom odaberemo stupce, zatim grupiramo podatke ovisno o nekoj kategorijskoj varijabli). Uz pomoć ovog operatora dobivamo preglednu reprezentaciju našeg procesa prilagodbe podataka koju kasnije lako prilagođavamo i po potrebi proširujemo. Primjeri u nastavku će često prema potrebi koristiti ovaj operator, te preporučujemo njegovo svladavanje prije nastavka sa lekcijama koje slijede.
9.2 Uredni podaci
U literaturi možemo naći činjenicu kako je u procesu analize priprema podataka često vremenski najzahtjevniji segment procesa - u knjizi “Exploratory Data Mining and Data Cleaning” spominje se da se na pripremu često troši od 50% do 80% ukupnog vremena. Isto tako, kako navodi Hadley Wickham u svojem članku “Tidy Data”, priprema podataka često nije samo prvi korak već je proces koji se ponavlja kako se otkrivaju nova saznanja ili prikupljaju novi podaci.
Hadley Wickham je uveo termin “urednih podataka” koji se odnosi na organizaciju podatkovnog skupa na način da u što većoj mjeri olakša njihovu daljnju obradu i analizu. Činjenica je da ulazni podaci često nisu originalno namijenjeni za potrebe analize te kao takvi nisu organizirani na način koji bi omogućio njihovo lako korištenje u analitičkom procesu. “Uredni podaci” zapravo predstavljaju princip kako - prema potrebi - “presložiti” podatke tako da njihova struktura odgovara standardnom, očekivanom metapredlošku.
Principi urednih podataka imaju sličnosti sa relacijskim modelom podataka no definirani su na način koji više odgovara statističarima i programerima. Ugrubo te principe možemo popisati na sljedeći način:
- podaci su organizirani u tablicu
- svaki redak predstavlja obzervaciju
- svaki stupac predstavlja svojstvo ili varijablu te obzervacije
Budući da ovo možda zvuči previše trivijalno, pogledajmo koja svojstva Hadley navodi kao tipična za “neuredne” podatke:
- imena stupaca nisu nazivi varijabli, već njihove vrijednosti
- više različitih varijabli spremljeno je u isti stupac
- varijable su spremljene u retke
- više tipova različitih obzervacija spremljeno je u istu tablicu
- jedan tip obzervacije spremljen je u više tablica
U nastavku ćemo dati nekoliko primjera tablica koje ne odgovaraju u potpunosti definiciji urednih podataka te prikazati kako ih na jednostavan način preoblikovati, tj. “urediti”. Za taj posao koristiti ćemo metode paketa tidyr
.
9.2.1 Funkcije gather
i spread
U radnoj mapi trebala bi se nalaziti datoteka studenti.csv
. Učitajmo ju u radnu okolinu. Budući da je datoteka pohranjena uz pomoć UTF-8 kodiranja (budući da sadrži hrvatska slova), naredbi read.csv
možete dodati i parametar fileEncoding = "UTF-8"
kako bi dobili korektni ispis posebnih znakova.
Zadatak 9.5 - podatkovni skup studenti
# učitajte podatke iz datoteke `studenti.csv` u varijablu `studenti`
# ne zaboravite na parametar `stringsAsFactors`!
# upoznajte se sa podacima uz pomoć standardnih funkcija za tu svrhu
# (names, sapply - class, str, head, summary ...)
# u daljnim primjerima za ovaj postupak koristiti ćemo se izrazom "proučite okvir.."
studenti <- read.csv("studenti.csv", fileEncoding="UTF-8", stringsAsFactors = F)
str(studenti)
head(studenti)
## 'data.frame': 27 obs. of 10 variables:
## $ JMBAG : int 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 ...
## $ Prezime : chr "Anic" "Babic" "Crnoja" "Crnjac" ...
## $ Ime : chr "Iva" "Josip" "Petra" "Lucija" ...
## $ Matematika.1 : chr "2" "5" "4" "2" ...
## $ Fizika.1 : chr "2" "3" "3" "5" ...
## $ Programiranje : chr "NULL" "4" "4" "2" ...
## $ Osnove.elektrotehnike: chr "NULL" "3" "2" "2" ...
## $ Digitalna.logika : chr "4" "NULL" "3" "3" ...
## $ Matematika.2 : chr "2" "5" "4" "3" ...
## $ Algoritmi.1 : chr "2" "5" "3" "4" ...
## JMBAG Prezime Ime Matematika.1 Fizika.1 Programiranje
## 1 1341 Anic Iva 2 2 NULL
## 2 1342 Babic Josip 5 3 4
## 3 1343 Crnoja Petra 4 3 4
## 4 1344 Crnjac Lucija 2 5 2
## 5 1345 Dizla Stipe NULL 4 3
## 6 1346 Ermic Igor NULL 3 NULL
## Osnove.elektrotehnike Digitalna.logika Matematika.2 Algoritmi.1
## 1 NULL 4 2 2
## 2 3 NULL 5 5
## 3 2 3 4 3
## 4 2 3 3 4
## 5 5 2 2 2
## 6 5 5 5 5
Uočite da ovaj podatkovni skup ima dosta nedostajućih vrijednosti koje su zapisane kao NULL
. Budući da R ne prepoznaje ovo kao nedostajuću vrijednost on je podatke učitao kao znakovne nizove (ili kao faktore, ako smo zaboravili na parametar stringsAsFactors
). Budući da su stupci koji se odnose na ocjene očito numerički, možemo ih lako pretvoriti u takve uz pomoć naredbe as.numeric()
(ili as.numeric(as.character())
ako su faktori!). No, postoji jednostavniji način - ako znamo na koji način je nedostajuća vrijednost reprezentirana u podatkovnom skupu, možemo to direktno ugraditi u naredbu read.csv
uz pomoć parametra na.strings
.
Zadatak 9.6 - prilagodba parametara učitavanja podatkovnog skupa
# ponovo učitajte podatke iz datoteke `studenti.csv` u varijablu `studenti`
# naredbi `read.csv` dodajte parametar `na.strings` sa znakovnim nizom koji predstavlja NA
# proučite okvir `studenti`
studenti <- read.csv("studenti.csv", fileEncoding="UTF-8", stringsAsFactors = F, na.strings = "NULL")
str(studenti)
head(studenti)
## 'data.frame': 27 obs. of 10 variables:
## $ JMBAG : int 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 ...
## $ Prezime : chr "Anic" "Babic" "Crnoja" "Crnjac" ...
## $ Ime : chr "Iva" "Josip" "Petra" "Lucija" ...
## $ Matematika.1 : int 2 5 4 2 NA NA NA 3 3 4 ...
## $ Fizika.1 : int 2 3 3 5 4 3 3 4 2 2 ...
## $ Programiranje : int NA 4 4 2 3 NA 3 3 3 4 ...
## $ Osnove.elektrotehnike: int NA 3 2 2 5 5 3 4 2 2 ...
## $ Digitalna.logika : int 4 NA 3 3 2 5 2 4 3 5 ...
## $ Matematika.2 : int 2 5 4 3 2 5 2 5 NA 4 ...
## $ Algoritmi.1 : int 2 5 3 4 2 5 5 4 3 2 ...
## JMBAG Prezime Ime Matematika.1 Fizika.1 Programiranje
## 1 1341 Anic Iva 2 2 NA
## 2 1342 Babic Josip 5 3 4
## 3 1343 Crnoja Petra 4 3 4
## 4 1344 Crnjac Lucija 2 5 2
## 5 1345 Dizla Stipe NA 4 3
## 6 1346 Ermic Igor NA 3 NA
## Osnove.elektrotehnike Digitalna.logika Matematika.2 Algoritmi.1
## 1 NA 4 2 2
## 2 3 NA 5 5
## 3 2 3 4 3
## 4 2 3 3 4
## 5 5 2 2 2
## 6 5 5 5 5
Vidimo da podaci sada imaju odgovarajući tip - no očito ne odgovaraju u potpunosti definiciji “urednih podataka”. Imena stupaca su zapravo kategorije varijable Predmet
a “obzervacija” u ovoj tablici reprezentirana je studentom. Dodavanje nove ocjene iz nekog predmeta moguće je jedino dodavanjem novog stupca, pri čemu bismo morali voditi računa da se na taj način dodaju ocjene za sve student, tj. da bi tablica morala imati puno NA
vrijednosti za sve kombinacije studenata i predmeta koje su neprimjenjive jer ocjene trenutno nema (i možda je neće ni biti ukoliko student niti ne sluša taj predmet).
Budući da su podaci u tablici zapravo skup ocjena, bilo bi pogodno preoblikovati tablicu tako da svaki redak upravo bude “ocjena koju je dobio neki student na nekom predmetu”. Razmislite koje korake bi trebalo poduzeti da stvorimo takvu tablicu. Trebamo:
- stvoriti kategorijsku varijablu
Predmet
koja bi kao razine imala nazive predmeta koji su trenutno stupci - stvoriti sve pripadne kombinacije student-predmet
- popuniti kombinacije pripadajućom vrijednosti ocjene
Ovaj postupak nije nemoguć ali zahtjeva dosta truda oko preoblikovanja podatkovnog okvira. Kako bi se ovaj postupak pojednostavio, možemo koristiti funkciju gather
iz paketa tidyr
koja obavlja upravo gore opisani postupak: ona “prikuplja” stupce u jedinstvenu varijablu i onda popunjava vrijednosti te varijable uz pomoć postojećih kombinacija naziv stupca / redak. Potpis funkcije izgleda ovako:
Detaljni opis funkcije možete dobiti pozivom naredbe ?gather
, a ovdje ćemo samo ukratko objasniti parametre:
data
očito predstavlja naš podatkovni okvirkey
predstavlja naziv novog stupca - kategorijske varijable kojeg stvaramo a (u našem slučajuPredmet
); kako bi se olakšao posao programeru, ova funkcija ne zahtijeva da naziv stupca stavljamo u navodnikevalue
predstavlja naziv novog stupca - varijable sa vrijednostima (u našem slučajuOcjena
)...
predstavlja skup stupaca koje prikupljamo; možemo navesti samo nazive stupaca odvojene zarezima (navodnici također nisu potrebni) ili se koristiti skraćenom sintaksomprvi_stupac:zadnji_stupac
(možemo i naznačiti samo “izbačene” stupce sa predznakom-
)na.rm
opisuje da li želimo izostaviti stupce saNA
convert
će obaviti konverziju podataka ukoliko to smatramo potrebnimfactor_key
nas pita želimo li faktorizirati novu varijablu koju stvaramo
Obavimo ovu funkciju nad našim podatkovnim okvirom.
Zadatak 9.7 - funkcija gather
# stvorite podatkovni okvir `ocjene` uz pomoć funkcije `gather` i okvira `studenti`
# proučite okvir `ocjene`
ocjene <- gather(studenti, Predmet, Ocjena, Matematika.1:Algoritmi.1, na.rm = T, factor_key = T )
str(ocjene)
head(ocjene)
## 'data.frame': 168 obs. of 5 variables:
## $ JMBAG : int 1341 1342 1343 1344 1348 1349 1350 1351 1352 1353 ...
## $ Prezime: chr "Anic" "Babic" "Crnoja" "Crnjac" ...
## $ Ime : chr "Iva" "Josip" "Petra" "Lucija" ...
## $ Predmet: Factor w/ 7 levels "Matematika.1",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ Ocjena : int 2 5 4 2 3 3 4 2 4 5 ...
## JMBAG Prezime Ime Predmet Ocjena
## 1 1341 Anic Iva Matematika.1 2
## 2 1342 Babic Josip Matematika.1 5
## 3 1343 Crnoja Petra Matematika.1 4
## 4 1344 Crnjac Lucija Matematika.1 2
## 8 1348 Grubiša Ivan Matematika.1 3
## 9 1349 Gobac Davor Matematika.1 3
Funkcija koja radi inverzan posao od gather
jest funkcija spread
. Ona će podatke iz kombinacije kategorijskog stupca i vrijednosti “raširiti” tako da kategorije postaju nazivi stupaca a vrijednosti se “raspršuju” po odgovarajućim stupcima.
Potpis funkcije izgleda ovako:
Dokumentaciju ove funkcije lako dohvaćamo naredbom ?spread
a neke elemente već možemo lako prepoznati korištenjem znanja kako radi funkcija gather
. Parametri koje možda treba dodatno pojasniti su:
fill
koji opisuje koju vrijednost staviti kod “nepostojećih” kombinacija nakon “širenja”drop
koji opisuje da li treba raditi stupce za nepostojeće kategorije ako je stupac kojeg širimo faktoriziransep
koji nam omogućuje da naziv stupca ne bude samo vrijednost kategorije već kombinacija naziva postojećeg kategorijskog stupca i vrijednosti (uz definirani separator)
Pokušajmo uz pomoć ove naredbe “rekonstruirati” originalni podatkovni okvir studenti
.
Zadatak 9.8 - funkcija spread
# "raširite" podatkovni okvir `ocjene` uz pomoć naredbe `spread`
# rezultat pohranite u okvir `studenti2`
# proučite okvire `studenti` i `studenti2`
studenti2 <- spread(ocjene, Predmet, Ocjena)
head(studenti)
head(studenti2)
str(studenti)
str(studenti2)
## JMBAG Prezime Ime Matematika.1 Fizika.1 Programiranje
## 1 1341 Anic Iva 2 2 NA
## 2 1342 Babic Josip 5 3 4
## 3 1343 Crnoja Petra 4 3 4
## 4 1344 Crnjac Lucija 2 5 2
## 5 1345 Dizla Stipe NA 4 3
## 6 1346 Ermic Igor NA 3 NA
## Osnove.elektrotehnike Digitalna.logika Matematika.2 Algoritmi.1
## 1 NA 4 2 2
## 2 3 NA 5 5
## 3 2 3 4 3
## 4 2 3 3 4
## 5 5 2 2 2
## 6 5 5 5 5
## JMBAG Prezime Ime Matematika.1 Fizika.1 Programiranje
## 1 1341 Anic Iva 2 2 NA
## 2 1342 Babic Josip 5 3 4
## 3 1343 Crnoja Petra 4 3 4
## 4 1344 Crnjac Lucija 2 5 2
## 5 1345 Dizla Stipe NA 4 3
## 6 1346 Ermic Igor NA 3 NA
## Osnove.elektrotehnike Digitalna.logika Matematika.2 Algoritmi.1
## 1 NA 4 2 2
## 2 3 NA 5 5
## 3 2 3 4 3
## 4 2 3 3 4
## 5 5 2 2 2
## 6 5 5 5 5
## 'data.frame': 27 obs. of 10 variables:
## $ JMBAG : int 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 ...
## $ Prezime : chr "Anic" "Babic" "Crnoja" "Crnjac" ...
## $ Ime : chr "Iva" "Josip" "Petra" "Lucija" ...
## $ Matematika.1 : int 2 5 4 2 NA NA NA 3 3 4 ...
## $ Fizika.1 : int 2 3 3 5 4 3 3 4 2 2 ...
## $ Programiranje : int NA 4 4 2 3 NA 3 3 3 4 ...
## $ Osnove.elektrotehnike: int NA 3 2 2 5 5 3 4 2 2 ...
## $ Digitalna.logika : int 4 NA 3 3 2 5 2 4 3 5 ...
## $ Matematika.2 : int 2 5 4 3 2 5 2 5 NA 4 ...
## $ Algoritmi.1 : int 2 5 3 4 2 5 5 4 3 2 ...
## 'data.frame': 27 obs. of 10 variables:
## $ JMBAG : int 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 ...
## $ Prezime : chr "Anic" "Babic" "Crnoja" "Crnjac" ...
## $ Ime : chr "Iva" "Josip" "Petra" "Lucija" ...
## $ Matematika.1 : int 2 5 4 2 NA NA NA 3 3 4 ...
## $ Fizika.1 : int 2 3 3 5 4 3 3 4 2 2 ...
## $ Programiranje : int NA 4 4 2 3 NA 3 3 3 4 ...
## $ Osnove.elektrotehnike: int NA 3 2 2 5 5 3 4 2 2 ...
## $ Digitalna.logika : int 4 NA 3 3 2 5 2 4 3 5 ...
## $ Matematika.2 : int 2 5 4 3 2 5 2 5 NA 4 ...
## $ Algoritmi.1 : int 2 5 3 4 2 5 5 4 3 2 ...
U prethodnom primjeru demonstrirali smo inverznu funkcionalnost funkcija gather
i spread
ali narednom spread
nismo postigli uredne podatke, samo smo se vratili na originalni okvir. Pogledajmo sada primjer gdje upravo naredbom spread
“sređujemo” podatke.
Učitajmo podatke iz datoteke auti.csv
koja pohranjuje tehničke karakteristike određenih automobila.
Zadatak 9.9 - podatkovni skup auti
## 'data.frame': 18 obs. of 3 variables:
## $ Model.auta : chr "Opel Astra" "Opel Astra" "Opel Astra" "Opel Astra" ...
## $ Tehnicka.karakteristika: chr "Cilindara" "KS" "Dužina m" "Masa kg" ...
## $ Vrijednost : num 4 125 4.27 1285 4 ...
## Model.auta Tehnicka.karakteristika Vrijednost
## 1 Opel Astra Cilindara 4.000
## 2 Opel Astra KS 125.000
## 3 Opel Astra Dužina m 4.267
## 4 Opel Astra Masa kg 1285.000
## 5 Audi A4 Cilindara 4.000
## 6 Audi A4 KS 136.000
U ovoj tablici očito je narušen princip urednih podataka da u jedan stupac treba biti pohranjen samo jedan tip varijable - tehničke karakteristike automobila smještene su u jedinstveni stupac naziva Tehnicka.karakteristika
a u stupcu Vrijednost
nalaze se vrijednosti vrlo različitih tipova (masa u kg, dužina u m i sl.).
Pokušajte urediti ovaj okvir uz pomoć naredbe spread
.
Zadatak 9.10 - funkcija spread
(2)
## 'data.frame': 5 obs. of 6 variables:
## $ Model.auta: chr "Audi A4" "Citroen C6" "Fiat 500L" "Opel Astra" ...
## $ Cilindara : num 4 6 2 4 4
## $ Dužina m : num 4.7 NA NA 4.27 4.56
## $ KS : num 136 215 103 125 110
## $ Masa kg : num 1470 1816 1260 1285 NA
## $ Ventila : num NA NA NA NA 16
## Model.auta Cilindara Dužina m KS Masa kg Ventila
## 1 Audi A4 4 4.703 136 1470 NA
## 2 Citroen C6 6 NA 215 1816 NA
## 3 Fiat 500L 2 NA 103 1260 NA
## 4 Opel Astra 4 4.267 125 1285 NA
## 5 Renault Grand Scenic 4 4.560 110 NA 16
Naredbe gather
i spread
ne koriste se samo za “neuredne” podatke. One mogu biti vrlo korisne kod pretvorbe tzv. “širokih” podatka (wide data) u “dugačke” (long data). Prikažimo ovo na primjeru tzv. podataka o potrošačkim košaricama.
Potrošačka košarica predstavlja zapis artikala koje je kupac kupio tijekom jednog dolaska u trgovinu (bilo da se radi o virtualnoj trgovini ili stvarnom prodajnom mjestu). Ako podatke o potrošačkoj košarici zapisujemo u “širokom” formatu, onda podatke organiziramo tako da stupci predstavljaju pojedine artikle a retci jednu kupnju (ili račun). Vrijednost 1
znači da se artikl našao u košarici, 0
da nije bio prisutan. Ovakav prikaz pogodan je za različite tipove analiza, ali nije ekonomičan - podaci će često imati jako puno “nula”. S druge strane, “dugački” format jednostavno u svaki redak stavlja kombinaciju identifikatora košarice (ili broja računa) i naziv (ili šifru) kupljenog artikla. Ovakav zapis imati će znatno više redaka, ali je znatno pogodniji u slučajevima kada je broj artikala u asortimanu daleko veći od broja artikala u prosječnoj košarici.
Zadatak 9.11 - podatkovni skup potrosackaKosarica
# učitajte podatke iz datoteke `potrosackaKosarica.csv` u podatkovni okvir imena `racuni`
# proučite okvir `racuni`
racuni <- read.csv("potrosackaKosarica.csv", stringsAsFactors = F, encoding = "UTF-8")
str(racuni)
head(racuni)
## 'data.frame': 104 obs. of 21 variables:
## $ racunID : int 15671 15672 15673 15674 15675 15676 15677 15678 15679 15680 ...
## $ Coca.cola.2l : int 0 1 1 0 1 0 0 0 0 0 ...
## $ Cipi.Cips : int 0 0 1 0 0 0 0 1 0 0 ...
## $ Nutella.400.g : int 0 1 0 0 1 0 0 0 0 0 ...
## $ Karlovacko.pivo: int 0 1 0 0 0 0 0 0 0 1 ...
## $ Ožujsko.pivo : int 1 0 0 1 0 0 0 0 0 1 ...
## $ Omekšivac.1.5l : int 0 0 0 0 0 0 0 1 0 1 ...
## $ Voda.2l : int 0 0 1 0 0 0 0 0 1 1 ...
## $ Narance : int 0 0 0 1 1 0 0 0 1 1 ...
## $ Jabuke : int 1 0 0 1 0 0 0 0 0 0 ...
## $ Mandarine : int 0 0 1 0 0 0 0 1 1 1 ...
## $ Salvete : int 0 0 0 0 1 1 0 0 0 0 ...
## $ Ajvar : int 0 0 1 1 0 0 0 0 0 1 ...
## $ Ketchup : int 1 0 0 1 0 0 0 0 0 1 ...
## $ Senf : int 1 0 0 0 0 0 0 1 0 0 ...
## $ Mlijeko.0.5l : int 0 1 0 1 1 0 0 0 0 0 ...
## $ Kiselo.vrhnje : int 0 1 0 0 0 0 1 0 0 1 ...
## $ Feta.sir : int 0 0 0 1 0 0 0 0 0 0 ...
## $ Sardine : int 0 0 0 0 0 0 0 0 0 1 ...
## $ Tuna.pašteta : int 1 1 1 0 0 1 0 0 0 0 ...
## $ Nescaffe : int 0 0 1 0 0 0 0 0 1 0 ...
## racunID Coca.cola.2l Cipi.Cips Nutella.400.g Karlovacko.pivo
## 1 15671 0 0 0 0
## 2 15672 1 0 1 1
## 3 15673 1 1 0 0
## 4 15674 0 0 0 0
## 5 15675 1 0 1 0
## 6 15676 0 0 0 0
## Ožujsko.pivo Omekšivac.1.5l Voda.2l Narance Jabuke Mandarine Salvete
## 1 1 0 0 0 1 0 0
## 2 0 0 0 0 0 0 0
## 3 0 0 1 0 0 1 0
## 4 1 0 0 1 1 0 0
## 5 0 0 0 1 0 0 1
## 6 0 0 0 0 0 0 1
## Ajvar Ketchup Senf Mlijeko.0.5l Kiselo.vrhnje Feta.sir Sardine
## 1 0 1 1 0 0 0 0
## 2 0 0 0 1 1 0 0
## 3 1 0 0 0 0 0 0
## 4 1 1 0 1 0 1 0
## 5 0 0 0 1 0 0 0
## 6 0 0 0 0 0 0 0
## Tuna.pašteta Nescaffe
## 1 1 0
## 2 1 0
## 3 1 1
## 4 0 0
## 5 0 0
## 6 1 0
Zadatak 9.12 - pretvorba okvira u ‘dugi’ format
# pretvorite podatke okvira `racuni` iz "širokog" formata u "dugi"
# novi okvir nazovite `racuniDugi`
racuni <- read.csv("potrosackaKosarica.csv", encoding = "UTF-8")
head(racuni)
racuniDugi <- gather(racuni, artikl, vrijednost, -racunID)
racuniDugi <- racuniDugi [racuniDugi$vrijednost != 0, 1:2]
racuniDugi <- racuniDugi[order(racuniDugi$racunID), ]
head(racuniDugi)
write.csv(racuniDugi, file = 'potrosackaKosaricaDugiFormat.csv', row.names = F)
## racunID Coca.cola.2l Cipi.Cips Nutella.400.g Karlovacko.pivo
## 1 15671 0 0 0 0
## 2 15672 1 0 1 1
## 3 15673 1 1 0 0
## 4 15674 0 0 0 0
## 5 15675 1 0 1 0
## 6 15676 0 0 0 0
## Ožujsko.pivo Omekšivac.1.5l Voda.2l Narance Jabuke Mandarine Salvete
## 1 1 0 0 0 1 0 0
## 2 0 0 0 0 0 0 0
## 3 0 0 1 0 0 1 0
## 4 1 0 0 1 1 0 0
## 5 0 0 0 1 0 0 1
## 6 0 0 0 0 0 0 1
## Ajvar Ketchup Senf Mlijeko.0.5l Kiselo.vrhnje Feta.sir Sardine
## 1 0 1 1 0 0 0 0
## 2 0 0 0 1 1 0 0
## 3 1 0 0 0 0 0 0
## 4 1 1 0 1 0 1 0
## 5 0 0 0 1 0 0 0
## 6 0 0 0 0 0 0 0
## Tuna.pašteta Nescaffe
## 1 1 0
## 2 1 0
## 3 1 1
## 4 0 0
## 5 0 0
## 6 1 0
## racunID artikl
## 417 15671 Ožujsko.pivo
## 833 15671 Jabuke
## 1249 15671 Ketchup
## 1353 15671 Senf
## 1873 15671 Tuna.pašteta
## 2 15672 Coca.cola.2l
Zadatak 9.13 - pretvorba okvira u ‘široki’ format
# pokušajte "dugi" format oblikovati natrag u "široki"
# pohranite rezultat u datoteku `potrosackaKosaricaSirokiFormat.csv`
racuniSiroki <- racuniDugi
racuniSiroki$Vrijednost <- 1
racuniSiroki <- spread(racuniSiroki, artikl, Vrijednost, fill = 0)
head(racuniSiroki)
write.csv(racuniSiroki, file = 'potrosackaKosaricaSirokiFormat.csv', row.names = F)
## racunID Ajvar Cipi.Cips Coca.cola.2l Feta.sir Jabuke Karlovacko.pivo
## 1 15671 0 0 0 0 1 0
## 2 15672 0 0 1 0 0 1
## 3 15673 1 1 1 0 0 0
## 4 15674 1 0 0 1 1 0
## 5 15675 0 0 1 0 0 0
## 6 15676 0 0 0 0 0 0
## Ketchup Kiselo.vrhnje Mandarine Mlijeko.0.5l Narance Nescaffe
## 1 1 0 0 0 0 0
## 2 0 1 0 1 0 0
## 3 0 0 1 0 0 1
## 4 1 0 0 1 1 0
## 5 0 0 0 1 1 0
## 6 0 0 0 0 0 0
## Nutella.400.g Omekšivac.1.5l Ožujsko.pivo Salvete Sardine Senf
## 1 0 0 1 0 0 1
## 2 1 0 0 0 0 0
## 3 0 0 0 0 0 0
## 4 0 0 1 0 0 0
## 5 1 0 0 1 0 0
## 6 0 0 0 1 0 0
## Tuna.pašteta Voda.2l
## 1 1 0
## 2 1 0
## 3 1 1
## 4 0 0
## 5 0 0
## 6 1 0
9.2.2 Funkcije separate
i unite
Paket tidyr
ima još niz korisnih funkcija namijenjenih “uređivanju” podataka a mi ćemo ovdje obratiti još dvije koje se relativno često koriste - separate
i unite
.
Funkcija separate
je korisna kada neki stupac ima “složene” vrijednosti koje želimo rastaviti u dva ili više stupaca.
Zadatak 9.14 - podatkovni skup odjeli
## 'data.frame': 28 obs. of 4 variables:
## $ Odjel : chr "A" "A" "A" "A" ...
## $ Kvartal : chr "Q1-2015" "Q2-2015" "Q3-2015" "Q4-2015" ...
## $ PrihodKn: num 12416 224290 10644 191229 258697 ...
## $ RashodKn: num 23101 63886 35468 12249 61515 ...
## Odjel Kvartal PrihodKn RashodKn
## 1 A Q1-2015 12416.2 23100.5
## 2 A Q2-2015 224290.1 63886.1
## 3 A Q3-2015 10643.7 35467.8
## 4 A Q4-2015 191229.3 12249.1
## 5 A Q1-2016 258697.4 61514.6
## 6 A Q2-2016 121865.3 46092.6
Ova tablica prikazuje prihode i rashode odjela neke tvrtke po kvartalima. Kvartali su trenutno pohranjeni u složenu varijablu Kvartal
koja se sastoji od identifikatora godišnjeg kvartala (Q1, Q2, Q3 ili Q4) i godine. Za potrebe analize vjerojatno bi bilo zgodno ovo rastaviti u dva stupca - Kvartal
(koji bi pohranjivao samo identifikator kvartala) i Godina
.
Paket tidyr
za ovakve potrebe nudi funkciju separate
sa sljedećim potpisom:
separate(data, col, into, sep = "[^[:alnum:]]+", remove = TRUE,
convert = FALSE, extra = "warn", fill = "warn", ...)
Potpunu dokumentaciju funkcije možemo pogledati naredbom ?separate
a ovdje ćemo navesti objašnjenje nekih važnijih parametara:
col
- stupac kojeg rastavljamo (ne moramo koristiti navodnike)into
- imena novih stupaca (preporučuje se koristiti znakovni vektor)sep
- separator vrijednosti u originalnom stupcu, default-na vrijednost je zapravo regularni izraz za “nešto što nije alfanumerički znak”remove
- opisuje da li je potrebno ukloniti originalni stupac ili ne
Pokušajmo primijeniti ovu funkciju na tablicu odjeli
. Ponovimo usput princip korištenja pipeline operatora.
Zadatak 9.15 - funkcija separate
# razdvojite stupac `Kvartal` u stupce `Kvartal` i `Godina` uz uklanjanje originalnog stupca
# rezultat pohranite u varijablu `odjeli2`
# sve učinite u sklopu jedne naredbe uz pomoć `%>%` operatora
# proučite okvir `odjeli2`
## 'data.frame': 28 obs. of 5 variables:
## $ Odjel : chr "A" "A" "A" "A" ...
## $ Kvartal : chr "Q1" "Q2" "Q3" "Q4" ...
## $ Godina : chr "2015" "2015" "2015" "2015" ...
## $ PrihodKn: num 12416 224290 10644 191229 258697 ...
## $ RashodKn: num 23101 63886 35468 12249 61515 ...
## Odjel Kvartal Godina PrihodKn RashodKn
## 1 A Q1 2015 12416.2 23100.5
## 2 A Q2 2015 224290.1 63886.1
## 3 A Q3 2015 10643.7 35467.8
## 4 A Q4 2015 191229.3 12249.1
## 5 A Q1 2016 258697.4 61514.6
## 6 A Q2 2016 121865.3 46092.6
Uočite da su stupci Kvartal
i Godina
zapravo kategorijske varijable i da bi ih bilo dobro faktorizirati. Faktoriziranje stupaca je nešto teže izvesti uz pomoć pipeline operatora (iako je izvedivo!) no u narednim poglavljima naučiti ćemo kako to puno lakše napraviti uz pomoć funkcija iz paketa dplyr
.
Funkcija separate
se često koristi za rastavljanje datuma (npr. 2016-10-28
u godinu, mjesec i dan) no u takvim situacijama preporuka je koristiti paket lubridate
koji je stvoren upravo za lakše upravljanje datumima. Ovaj paket upoznati ćemo u jednom od sljedećih poglavlja.
Za kraj naučimo još funkciju unite
koja se nešto rijeđe koristi a zapravo je inverz funkcije separate
. Potpis funkcije unite
je:
I u ovom slučaju dokumentaciju lako dohvaćamo sa ?unite
, a ovdje dajemo opis parametara koji potencijalno zahtijevaju dodatno objašnjenje:
col
- ime novog stupca (nije nužno koristiti navodnike)...
- imena stupaca koje spajamo - ne moramo koristiti navodnike, a ukoliko ima puno stupaca možemo se koristiti sličnom sintaksom za odabir kao i kod funkcijegather
Isprobajmo naredbu na okviru odjeli2
.
Zadatak 9.16 - funkcija unite
# spojite stupce `Kvartal` i `Godina` iz tablice `odjeli2` u jedinstven stupac `Kvartal`
# uklonite stupce `Kvartal` i `Godina`
# koristite `-` kao separator
# spremite rezultat u varijablu `odjeli3`
# sve ovo izvedite u sklopu jedne naredbe uz pomoć `%>%` operatora
# proučite okvire `odjeli` i `odjeli3`
odjeli2 %>% unite(Kvartal, Kvartal, Godina, sep = "-") -> odjeli3
str(odjeli)
str(odjeli3)
head(odjeli)
head(odjeli3)
## 'data.frame': 28 obs. of 4 variables:
## $ Odjel : chr "A" "A" "A" "A" ...
## $ Kvartal : chr "Q1-2015" "Q2-2015" "Q3-2015" "Q4-2015" ...
## $ PrihodKn: num 12416 224290 10644 191229 258697 ...
## $ RashodKn: num 23101 63886 35468 12249 61515 ...
## 'data.frame': 28 obs. of 4 variables:
## $ Odjel : chr "A" "A" "A" "A" ...
## $ Kvartal : chr "Q1-2015" "Q2-2015" "Q3-2015" "Q4-2015" ...
## $ PrihodKn: num 12416 224290 10644 191229 258697 ...
## $ RashodKn: num 23101 63886 35468 12249 61515 ...
## Odjel Kvartal PrihodKn RashodKn
## 1 A Q1-2015 12416.2 23100.5
## 2 A Q2-2015 224290.1 63886.1
## 3 A Q3-2015 10643.7 35467.8
## 4 A Q4-2015 191229.3 12249.1
## 5 A Q1-2016 258697.4 61514.6
## 6 A Q2-2016 121865.3 46092.6
## Odjel Kvartal PrihodKn RashodKn
## 1 A Q1-2015 12416.2 23100.5
## 2 A Q2-2015 224290.1 63886.1
## 3 A Q3-2015 10643.7 35467.8
## 4 A Q4-2015 191229.3 12249.1
## 5 A Q1-2016 258697.4 61514.6
## 6 A Q2-2016 121865.3 46092.6
Zadaci za vježbu
- Inicijalizirajte generator slučajnih brojeva uz pomoć naredbe
set.seed(1234)
. Potom uz pomoć jedne naredbe i%>%
operatora izvedite sljedeće:
- stvorite 100000 nasumičnih brojeva izvučenih iz normalne razdiobe za aritmetičkom sredinom 10000 i standardnom devijacijom 1000
- zaokružite brojeve na prvi veći cijeli broj
- izbacite duplikate iz skupa
- poredajte skup po veličini
- slučajnim odabirom iz skupa izvucite 100 elemenata
- organizirajte tih 100 elemenata u matricu 10 x 10, složenu po retcima
- izračunajte sume redaka matrice
- ispišite prosjek suma redaka na zaslon.
- U datoteci
weather.csv
nalaze se podaci o izmjerenim vremenskim uvjetima od strane meteorološke stanice koja svaki sat vremena mjeri temperaturu, tlak, vlažnost i brzinu vjetra (podaci su preuzeti i prilagođeni iz podatkovnog skupa paketaweatherData
dostupnog na CRAN-u). Izvedite sljedeće:
- učitajte datoteku u podatkovni okvir i proučite učitane podatke (
names
,str
,summary
,head
…) - odgovorite: da li se radi o urednim podacima? Zašto?
- poduzmite odgovarajuće korake kako bi dobili podatkovni okvir koji odgovara principu urednih podataka
- spremite “uređeni” u okvir u datoteku
weatherClean.csv
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/
References
Bache, Stefan Milton, and Hadley Wickham. 2014. Magrittr: A Forward-Pipe Operator for R. https://CRAN.R-project.org/package=magrittr.