4 Podatkovni okviri i faktori
4.1 Podatkovni okviri
Kao što je već rečeno, podatkovni okvir je daleko najpopularniji element programskog jezika R. Jezik R predviđen je primarno analizi podataka, a podatkovni okvir zapravo predstavlja objektnu reprezentaciju podatkovnog skupa kojeg namjeravamo analizirati. Drugim riječima, podatkovni okvir je objekt slične funkcije kao tablica u Microsoft Excel-u ili relacijskoj bazi podataka. Gotovo svaka “sesija” u R-u svodi se na manipuliranje podatkovnim okvirima - no dok u Excel-u tablicom upravljamo uz pomoć grafičkog sučelja, a u bazi uz pomoć upitnog jezika SQL, u R-u podatkovnim 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 parametre vezane uz gradove u Republici Hrvatskoj (navedene vrijednosti ne odgovaraju nužno trenutnom stanju već ih koristimo samo za demonstraciju). Lako možemo zamisliti kako ove podatke zapisujemo u Excel ili stvaramo relacijsku tablicu naziva npr. MJESTO u koju onda pohranjujemo navedene podatke. Pokažimo sada kako bi sa istim podacima manipulirati u sklopu jezika R, tj. pokušajmo stvoriti podatkovni okvir koji će sadržavati ove podatke.
U prošloj lekciji smo napomenuli da je lista kao složeni tip zapravo svojevrsni “predložak” uz pomoć kojeg možemo raditi nove objekte. Podatkovni okvir tako zapravo nije ništa drugo nego lista – tj. “spremnik” koji može sadržavati u sebi druge spremnike podataka različitog tipa. No dok je “lista“ zapravo univerzalni spremnik, tj. nemamo ograničenja što”trpamo" u nju, podatkovni okvir ima određene restrikcije.
Najvažnije ograničenje koje podatkovni okvir nameće jest da svaki element unutar podatkovnog okvira mora imati isti broj elemenata. Zašto je tome tako? Zamislimo listu u kojoj svaki element ima isti broj podelemenata. Ako svaki element skiciramo vertikalno, sa podelementima napisanim jedan ispod drugog, onda će ti podelementi biti “poravnati” po retcima čime smo postigli klasičnu organizaciju podataka u stupce (elemente liste) i retke (poravnati podelementi). Dakle, ova restrikcija zapravo direktno nameće “tabličnu” ili “matričnu” strukturu liste sa jasno definiranim retcima i stupcima, što nam zapravo omogućuje da podatkovnim okvirom upravljamo i uz pomoć metoda vezanih uz liste, ali i uz pomoć metoda primarno namijenjenih matricama.
Postoji više načina stvaranja podatkovnih okvira, a mi ćemo prikazati dva u praksi najčešće susretana scenarija:
- programsko stvaranje uz pomoć funkcije
data.frame
- učitavanje podataka iz vanjskog izvora uz pomoć funkcije
read.csv
Prikažimo oba slučaja. Prvo ćemo stvoriti podatkovni okvir programski.
Zadatak 4.1 - programsko stvaranje podatkovnog okvira
mjesto <- data.frame(pbr = 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))
# ispišite podatkovni okvir `mjesto`
mjesto <- data.frame(pbr = 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))
# ispišite podatkovni okvir `mjesto`
mjesto
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359 790017 18
## 2 51000 Rijeka 5418 128384 15
## 3 21000 Split 5170 167121 10
## 4 31000 Osijek 4892 84104 13
## 5 2000 Dubrovnik 5348 28434 10
Ukoliko želite, pokušajte ponovo stvoriti gornji podatkovni okvir ali uz različite brojeve elemenata vektora koji čine stupce. Ova operacija rezultirati će greškom uz prikladnu poruku a podatkovni okvir neće biti stvoren - R se trudi da matrična priroda okvira uvijek bude očuvana.
Mala napomena glede terminologije: u nastavku ćemo zbog jednostavnosti “podatkovni okvir” često zvati jednostavno “okvir” ili “tablica”. Isto tako, često ćemo za elemente podatkovnog okvira jednakopravno koristiti izraze “stupac”, “varijabla” ili “atribut”, dok ćemo paralelne podelemente elemenata okvira nazivati “retcima” ili “obzervacijama”. Ovi termini u skladu su sa standardnim načinom referenciranja elemenata tablice te statističkim terminima koji se odnose na tablične podatkovne skupove. Ukoliko iz konteksta postoji šansa za dvosmislenost, koristiti će se onaj termin koji jasno opisuje element koji se referencira.
Pokušajmo sada učitati tablicu iz vanjskog izvora. Iako R dopušta različite oblike “vanjskih” podataka, mi ćemo pretpostaviti da podatke dobivamo u tzv. “CSV obliku” (engl. CSV - comma-separated values). Ovaj oblik jedan je od najpopularnijih načina pohrane podataka u čistom tekstualnom obliku koji ima prednosti da se lako izrađuje ručno, a većina alata za upravljanje podacima implementira i logiku za izvoz podataka u obliku CSV datoteke.
U nastavku možemo vidjeti primjer CSV datoteke koja odgovara podatkovnom okviru izrađenom u prethodnom primjeru. Pretpostavimo da se datoteka zove mjesto.csv
. Podaci su odvojeni zarezom (bez razmaknice!), svaka obzervacija u svojem retku, a opcionalni prvi redak predstavlja nazive stupaca.
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
Jedan od potencijalnih problema sa CSV datotekama jest taj što one koriste zarez kao razdvojnik (delimiter) elemenata zapisa, a na određenim govornim područjima kao standard se umjesto decimalne točke koristi upravo “decimalni zarez”. Zbog ove činjenice postoji i “alternativni” CSV standard koji kao razdvojnik koristi “točku-zarez”, tako da bi naša CSV datoteka u tom slučaju izgledala ovako (nazovimo ju mjestoAlt.csv
):
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
Obzirom da je “decimalni zarez” propisani standard i na području Republike Hrvatske, u radu sa CSV datotekama moramo biti oprezni koji od dva standarda zapisa se koristi. Na sreću, jezik R nudi funkcije za podršku oba standarda, tako da ne moramo posebno prilagođavati ulazne datoteke, tek biti oprezni koju funkciju ćemo odabrati.
Pretpostavimo da u radnoj mapi imamo ove dvije datoteke:
mjesto.csv
mjestoAlt.csv
Ukoliko nemamo dostupne ove datoteke lako ih možemo samostalno napraviti uz pomoć običnog uređivača teksta (npr. Notepad ili gedit) i kopiranja gore navedenih redaka.
Za stvaranje podatkovnih okvira iz CSV datoteka koristimo funkcije:
- read.csv
- za “normalne” CSV datoteke sa zarezom kao razdvojnikom
- read.csv2
- za alternativni CSV standard koji koristi točku-zarez
Osnovni parametar ovih funkcija je staza do CSV datoteke koju učitavamo. Funkcije imaju i bogati niz dodatnih parametara koje omogućuju prilagodbu raznim scenarijima, a ukoliko smo dobili neki od “egzotičnijih” oblika CSV datoteke, isplati se pogledati i funkciju read.table
koja je vrlo fleksibilna glede broja različitih parametara i postavki kod učitavanja podataka (read.csv
i read.csv2
su zapravo izvedene iz funkcije read.table
fiksiranjem određenih parametara na standardne CSV značajke).
Neke od parametara i pripadajućih vrijednosti funkcija read.csv
(ili read.table
) koje je korisno znati su:
header = FALSE
- za datoteke bez zaglavljasep = "#"
- za datoteke koje koriste “egzotični” razdvojnik, u ovom slučaju#
na.strings = "NULL"
- naznaka koji standard podaci koriste za reprezentaciju nedostajućih vrijednosti a koji će u R-u postatiNA
nrows = 2000
- maksimalan broj redaka koji će se pročitati, u ovom slučaju 2000stringsAsFactors = F
- sprječavanje automatskog stvaranja faktorskih stupaca (o kojima ćemo učiti u nastavku ove lekcije)encoding = "UTF-8"
- za standarde kodiranja teksta koji nisu ASCII (osobito bitno ako radimo sa podacima sa hrvatskog govornog područja koji koriste dijakritičke znakove)
Pokušajmo sada učitati podatke iz dostupnih CSV datoteka. Ovi podaci neće zahtijevati posebne parametre te će se moći učitati samo pružanjem staze do pripadnih datoteka (jedne koja koristi zarez i druge koja koristi točku-zarez kao razdvojnik).
Zadatak 4.2 - čitanje podataka iz CSV datoteke
# učitajte podatke iz datoteka `mjesto.csv` i `mjestoAlt.csv`
# podatke spremite u okvire `mjesto2` i `mjesto3`
# ispišite okvire `mjesto2` i `mjesto3`
# učitajte podatke iz datoteka `mjesto.csv` i `mjestoAlt.csv`
# podatke spremite u okvire `mjesto2` i `mjesto3`
mjesto2 <- read.csv("mjesto.csv")
mjesto3 <- read.csv2("mjestoAlt.csv")
# ispišite okvire `mjesto2` i `mjesto3`
mjesto2
cat("-----------\n")
mjesto3
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359 790017 18
## 2 51000 Rijeka 5418 128384 15
## 3 21000 Split 5170 167121 10
## 4 31000 Osijek 4892 84104 13
## 5 20000 Dubrovnik 5348 28434 10
## -----------
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359 790017 18
## 2 51000 Rijeka 5418 128384 15
## 3 21000 Split 5170 167121 10
## 4 31000 Osijek 4892 84104 13
## 5 20000 Dubrovnik 5348 28434 10
Pogledajmo sada neke korisne funkcije za rad sa podatkovnim okvirima tj. tablicama. Dobar dio njih će nam već otprije biti poznat iz iskustva u radu sa listama i matricama:
nrow
- broj redakancol
ililength
- broj stupaca (budući da se okvir ponaša i kao matrica i kao lista)dim
- dimenzije tablicenames
- imena stupacahead
- ispis nekoliko redaka s početka tablicetail
- ispis nekoliko redaka s kraja tablicestr
- ispis strukture tablicesummary
- sažete statističke informacije o stupcima tablice
Isprobajmo neke od ovih funkcija:
Zadatak 4.3 - funkcije za rad sa podatkovnim okvirima
# ispišite dimenzije tablice `mjesto`
# ispišite strukturu tablice `mjesto`
# ispišite prvih nekoliko redaka tablice `mjesto`
# ispišite sažete statističke informacije o stupcima tablice `mjesto`
# ispišite dimenzije tablice `mjesto`
dim(mjesto)
cat("-----------\n")
# ispišite strukturu tablice `mjesto`
str(mjesto)
cat("-----------\n")
# ispišite prvih nekoliko redaka tablice `mjesto`
head(mjesto)
cat("-----------\n")
# ispišite sažete statističke informacije o stupcima tablice `mjesto`
summary(mjesto)
## [1] 5 5
## -----------
## 'data.frame': 5 obs. of 5 variables:
## $ pbr : num 10000 51000 21000 31000 2000
## $ nazivMjesta : Factor w/ 5 levels "Dubrovnik","Osijek",..: 5 3 4 2 1
## $ prosjPlacaKn : num 6359 5418 5170 4892 5348
## $ brojStanovnika: num 790017 128384 167121 84104 28434
## $ prirez : num 18 15 10 13 10
## -----------
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359 790017 18
## 2 51000 Rijeka 5418 128384 15
## 3 21000 Split 5170 167121 10
## 4 31000 Osijek 4892 84104 13
## 5 2000 Dubrovnik 5348 28434 10
## -----------
## pbr nazivMjesta prosjPlacaKn brojStanovnika
## Min. : 2000 Dubrovnik:1 Min. :4892 Min. : 28434
## 1st Qu.:10000 Osijek :1 1st Qu.:5170 1st Qu.: 84104
## Median :21000 Rijeka :1 Median :5348 Median :128384
## Mean :23000 Split :1 Mean :5437 Mean :239612
## 3rd Qu.:31000 Zagreb :1 3rd Qu.:5418 3rd Qu.:167121
## Max. :51000 Max. :6359 Max. :790017
## prirez
## Min. :10.0
## 1st Qu.:10.0
## Median :13.0
## Mean :13.2
## 3rd Qu.:15.0
## Max. :18.0
4.2 Odabir redaka i stupaca podatkovnih okvira
Već smo rekli da se podatkovni okviri ponašaju i kao matrice i kao liste, što je svojstvo kojim se posebno često služimo kod odabira redaka i stupaca podatkovnih okvira. Konkretno, za “rezanje” okvira najčešće koristimo:
- dvodimenzionalno referenciranje uz pomoć indeksnih vektora
- odabir pojedinog stupca uz pomoć operatora
$
Ovdje smo zapravo dosta fleksibilni - možemo npr. prvo “izrezati” određene retke matrice uz pomoć lokacijskog referenciranja i potom izdvojiti samo jedan stupac uz pomoć operatora $
. U praksi je jedna od najčešćih kombinacija uvjetni odabir redaka uz imenski odabir stupaca (poznavatelji SQL-a prepoznati će ovo kao standardnu kombinaciju WHERE uvjeta i SELECT liste).
Pokušajmo primijeniti naše znanje o indeksnim vektorima, matricama i listama na rezanje podatkovnih okvira.
Zadatak 4.4 - rezanje podatkovnih okvira
# ispišite tablicu `mjesto` (za referencu)
# ispišite prva tri retka, treći 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
#ispišite tablicu `mjesto` (za referencu)
mjesto
cat("-----------\n")
#ispišite prva tri retka, treći i peti stupac
mjesto[1:3, c(3,5)]
cat("-----------\n")
#ispišite stupac "prirez"
mjesto$prirez
cat("-----------\n")
#ispišite poštanske brojeve i nazive svih mjesta koji imaju
# prirez veći od 12% i broj stanovnika veći od 100,000
mjesto[mjesto$prirez > 12 & mjesto$brojStanovnika > 100000, c("pbr", "nazivMjesta")]
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359 790017 18
## 2 51000 Rijeka 5418 128384 15
## 3 21000 Split 5170 167121 10
## 4 31000 Osijek 4892 84104 13
## 5 2000 Dubrovnik 5348 28434 10
## -----------
## prosjPlacaKn prirez
## 1 6359 18
## 2 5418 15
## 3 5170 10
## -----------
## [1] 18 15 10 13 10
## -----------
## pbr nazivMjesta
## 1 10000 Zagreb
## 2 51000 Rijeka
Uočite sličnost između zadnjeg izraza i SQL upita: `
Odabir stupaca i redaka nije težak ako dobro baratamo znanjem o indeksnim vektorima, no kao što se vidi u zadnjem primjeru sintaksa često nije previše čitljiva (u usporedbi sa npr. SQL-ovom sintaksom koja obavlja isti posao). Zbog toga postoje različita proširenja R-a koji ovaj posao uvelike olakšavaju, a koja ćemo detaljno obraditi u jednoj od budućih lekcija koja će se baviti upravljanjem podatkovnim skupovima.
4.3 Dodavanje i brisanje redaka i stupaca
Za dodavanje i brisanje redaka i stupaca opet se dovoljno sjetiti da je podatkovni okvir svojevrsni hibrid matrice i liste tj. ako znamo dodavati retke i stupce u matricu ili nove elemente u listu onda ekvivalentnim načinom možemo podatke dodavati i u podatkovni okvir. U radu sa podatkovnim okvirima nešto češće dodajemo nove stupce (obično kao transformacije postojećih stupaca) nego retke tako da možemo primjere započeti sa dodavanjem stupaca.
Kao što je rečeno, stupce u podatkovni okvir dodajemo na isti način kao što dodajemo elemente liste - uz pažnju da dodani stupac ima isti broj elemenata kao i ostali stupci. Novi stupci često su izvedenice postojećih stupaca koje predstavljaju binarne indikatore, rezultate aritmetičkih izraza podataka u drugim stupcima i sl.
Zadatak 4.5 - dodavanje novih stupaca u tablicu
# tablici `mjesto` dodajte logički stupac `visokPrirez`
# koji će pokazivati da li je prirez veći od 12%
# pretpostavimo sljedeći (fiktivni!) način izračuna prireza
# - mjesta imaju oko 60% radne populacije
# - svaki radnik plaća porez koji je otprilike jednak 10% neto plaće
# - prirez kojeg radnik plaća računamo kao (stopa prireza)*(iznos poreza)
#
# dodajte stupac `mjesecniPrihod` koji će uz pomoć prosječne plaće, prireza
# i broja stanovnika procijeniti koliki prihod pojedino mjesto dobija od prireza
# (izraženo u milijunima Kn)
# iznos zaokružite uz pomoć funkcije round ( primjer: round(100.12345, 2) ==> 100.12 )
# ispišite tablicu mjesto
# tablici `mjesto` dodajte logički stupac `visokPrirez`
# koji će pokazivati da li je prirez veći od 12%
mjesto$visokPrirez <- mjesto$prirez > 12
# pretpostavimo sljedeći (fiktivni!) način izračuna prireza
# - mjesta imaju oko 60% radne populacije
# - svaki radnik plaća porez koji je otprilike jednak 10% neto plaće
# - prirez kojeg radnik plaća računati ćemo (stopa prireza)*(iznos poreza)
#
# dodajte stupac `mjesecniPrihod` koji će uz pomoć prosječne plaće, prireza
# i broja stanovnika procijeniti koliki prihod pojedino mjesto dobija od prireza
# (izraženo u milijunima Kn)
# iznos zaokružite uz pomoć funkcije round ( primjer: round(100.12345, 2) ==> 100.12 )
mjesto$mjesecniPrihod <- round(0.6 * mjesto$brojStanovnika * 0.1 *
mjesto$prosjPlacaKn * 0.01 * mjesto$prirez / 1e6 , 2)
# ispišite tablicu mjesto
mjesto
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez visokPrirez
## 1 10000 Zagreb 6359 790017 18 TRUE
## 2 51000 Rijeka 5418 128384 15 TRUE
## 3 21000 Split 5170 167121 10 FALSE
## 4 31000 Osijek 4892 84104 13 TRUE
## 5 2000 Dubrovnik 5348 28434 10 FALSE
## mjesecniPrihod
## 1 54.26
## 2 6.26
## 3 5.18
## 4 3.21
## 5 0.91
Retke i stupce smo također mogli dodati slično dodavanju redaka i stupaca u matricu - uz pomoć funkcija rbind
i cbind
. Kod funkcije rbind
obično dodajemo novi podatkovni okvir sa retcima koji imaju odgovarajući redoslijed i vrstu elemenata, dok kod funkcije cbind
možemo dodati i obični vektor no moramo paziti da broj elemenata odgovara broju redaka originalnog okvira.
Isprobajmo ove funkcije na malim “umjetnim” podatkovnim okvirima kako bi lakše predočili njihovu funkcionalnost.
Zadatak 4.6 - funkcije rbind
/cbind
i podatkovni okviri
df1 <- data.frame(a = c(1,2,3), b = c("A", "B", "C"), c = c(T, F, T))
df2 <- data.frame(a = 1, b = "A", c = 3)
#spojite df1 i df2 u podatkovni okvir df12 uz pomoć funkcije `rbind`
# okviru df12 dodajte stupac `imena` sa imenima Ivo, Ana, Pero i Stipe
# koristite funkciju `cbind`
# ispišite okvir df12
df1 <- data.frame(a = c(1,2,3), b = c("A", "B", "C"), c = c(T, F, T))
df2 <- data.frame(a = 1, b = "A", c = 3)
#spojite df1 i df2 u podatkovni okvir df12 uz pomoć funkcije `rbind`
df12 <- rbind(df1, df2)
# okviru df12 dodajte stupac `imena` sa imenima Ivo, Ana, Pero i Stipe
# koristite funkciju `cbind`
df12 <- cbind(df12, imena = c("Ivo", "Ana", "Pero", "Stipe"))
# ispišite okvir df12
df12
## a b c imena
## 1 1 A 1 Ivo
## 2 2 B 0 Ana
## 3 3 C 1 Pero
## 4 1 A 3 Stipe
Za brisanje redaka i stupaca također se možemo koristiti istim metodama za upravljanje matricama i listama. Konkretno:
- brisanje redaka i stupaca možemo obaviti dvodimenzionalnim referenciranjem redaka i stupaca koje želimo “zadržati”
- brisanje stupaca možemo obaviti pridjeljivanjem vrijednosti
NULL
odabranom stupcu
Isprobajmo ovo na primjeru.
Zadatak 4.7 - brisanje redaka i stupaca
# obrišite prvi redak i drugi stupac iz df12 metodom 2d referenciranja
# obrišite stupac `imena` uz pomoć pridjeljivanja `NULL` vrijednosti
# ispišite df12
# obrišite prvi redak i drugi stupac iz df12 metodom 2d referenciranja
df12 <- df12[-1, -2]
# obrišite stupac `imena` uz pomoć pridjeljivanja `NULL` vrijednosti
df12$imena <- NULL
# ispišite df12
df12
## a c
## 2 2 0
## 3 3 1
## 4 1 3
Podatkovnim okvirima ćemo se nastaviti baviti u poglavlju o upravljanju podatkovnim skupovima, gdje ćemo naučiti kako raditi sa okvirima sa daleko više podataka od primjera koje smo koristili u ovoj lekciji, te kako raditi sa dodatnim paketima koji značajno olakšavaju česte radnje nad podacima u podatkovnim okvirima. U nastavku ćemo se pozabaviti još jednim novim (i ponešto kontroverznim) tipom podatkovne strukture.
4.4 Faktori
Faktor u R-u je zapravo tip podataka koji predstavlja ono onoga što se u statistici naziva nominalnom ili kategorijskom varijablom. Naime, atribut neke obzervacije često poprima neku vrijednost iz skupa otprije poznatih kategorija (npr. varijabla spola, dobne kategorije, obrazovanja, mjesta rođenja, stranačke preferencije i sl.). Kategorije se često identificiraju jedinstvenim nizom znakova, a u procesu analize uz pomoć njih često provodimo razna agregiranja i grupacije (npr. u nekoj utrci možemo gledati prosječno vrijeme ovisno o spolu ili dobnoj kategoriji) ili pak dijelimo skup podataka ovisno o kategorijskoj pripadnosti.
Faktori u R-u su često i predmet rasprava budući da se radi o konstruktu koji može olakšati rad nad podacima, ali i uzrokovati brojne probleme, pogotovo ako nismo svjesni da u nekom trenutku radimo sa faktorom (ovaj scenarij se zapravo vrlo lako izbjegava, što ćemo objasniti na kraju ovog poglavlja).
Za početak ćemo predočiti što su zapravo faktori uz pomoć jednostavnog primjera. Zamislimo da sljedeći znakovni vektor opisuje razinu krvnog tlaka kod deset pacijenata:
tlak <- c("nizak", "visok", "visok", "normalan", "normalan",
"nizak", "visok", "nizak", "normalan", "normalan")
Ovo je očito “kategorijska” varijabla budući da može poprimiti jednu od tri diskretne vrijednosti - "nizak"
, "normalan"
i "visok"
. Prema tome, ovaj vektor je tipičan kandidat za “faktoriziranje”, tj. za pretvorbu u objekt klase factor
. Faktorizaciju znakovnog vektora provodimo uz pomoć funkcije factor
kojoj prosljeđujemo (u pravilu) znakovni vektor kao parametar.
Zadatak 4.8 - faktoriziranje znakovnog vektora
tlak <- c("nizak", "visok", "visok", "normalan", "normalan",
"nizak", "visok", "nizak", "normalan", "normalan")
# ispišite varijablu `tlak`
# ispišite klasu varijable `tlak`
# stvorite varijablu `tlak.f` koja će biti faktorizirana
# inačica varijable `tlak`
# ispišite varijablu `tlak.f`
# ispišite klasu varijable tlak.f
tlak <- c("nizak", "visok", "visok", "normalan", "normalan",
"nizak", "visok", "nizak", "normalan", "normalan")
# ispišite varijablu `tlak`
tlak
# ispišite klasu varijable `tlak`
class(tlak)
# stvorite varijablu `tlak.f` koja će biti faktorizirana
# inačica varijable `tlak`
tlak.f <- factor(tlak)
cat("-----------\n")
# ispišite varijablu `tlak.f`
tlak.f
# ispišite klasu varijable tlak.f
class(tlak.f)
## [1] "nizak" "visok" "visok" "normalan" "normalan" "nizak"
## [7] "visok" "nizak" "normalan" "normalan"
## [1] "character"
## -----------
## [1] nizak visok visok normalan normalan nizak visok
## [8] nizak normalan normalan
## Levels: nizak normalan visok
## [1] "factor"
Vidimo da je ispis faktora dobio dodatni atribut Levels
. To znači da se sada ovdje radi o “pravoj” kategorijskoj varijabli sa točno definiranim kategorijama koje smije poprimiti. Ako pokušamo dodati novu vrijednost u faktor koja nije zastupljena u trenutnim kategorijama (npr. “prenizak”) dobiti ćemo upozorenje, a umjesto kategorije koju smo naveli nova stavka imati će vrijednost NA
.
Ovo ponekad nije scenarij kojeg priželjkujemo. Ukoliko unaprijed znamo da znakovni vektor kojeg kategoriziramo ne sadrži sve moguće kategorije koje se općenito mogu pojaviti, imamo opciju dodavanja parametra levels
u kojem ćemo uz pomoć znakovnog vektora eksplicitno navesti niz “mogućih” kategorija.
Zadatak 4.9 - nezastupljene kategorije i parametar levels
# dodajte 11. element u vektor `tlak.f` sa sadržajem "prenizak"
# ispišite `tlak.f`
# napravite varijablu `tlak.f2` uz pomoć varijable `tlak`
# kategorijske razine navedite eksplicitno tako da sadrže
# i kategorije "prenizak" i "previsok"
# dodajte 11. element u vektor `tlak.f2` sa sadržajem "prenizak"
# ispišite `tlak.f2`
## Warning in `[<-.factor`(`*tmp*`, 11, value = "prenizak"): invalid factor
## level, NA generated
# ispišite `tlak.f`
tlak.f
cat("-----------\n")
# napravite varijablu `tlak.f2` uz pomoć varijable `tlak`
# kategorijske razine navedite eksplicitno tako da sadrže
# i kategorije "prenizak" i "previsok"
tlak.f2 <- factor(tlak, levels = c("prenizak", "nizak", "normalan",
"visok", "previsok"))
# dodajte 11. element u vektor `tlak.f2` sa sadržajem "prenizak"
tlak.f2[11] <- "prenizak"
# ispišite `tlak.f2`
tlak.f2
## [1] nizak visok visok normalan normalan nizak visok
## [8] nizak normalan normalan <NA>
## Levels: nizak normalan visok
## -----------
## [1] nizak visok visok normalan normalan nizak visok
## [8] nizak normalan normalan prenizak
## Levels: prenizak nizak normalan visok previsok
Koja je prednost faktora? Zašto varijable ne bismo ostavili u originalnom, “znakovnom” obliku? Razlog potrebe za faktoriziranjem kategorijskih stupaca tj. varijabli je poglavito u tome što određene statističke i vizualizacijske funkcije “znaju” na ispravan način interpretirati i koristiti faktore te ih tretiraju drugačije od običnih “znakovnih” stupaca. Zbog toga je vrlo dobra dugoročna strategija već u početku učenja R-a naviknuti se da kod rada sa podatkovnim okvirima faktoriziramo stupce koji su zaista kategorijske varijable (ali također i pripazimo da nemamo faktorizirane stupce koji nisu kategorijske varijable, što se može događati ako nismo pažljivi).
Jedno od pitanja koje se često pitamo vezano uz kategorijske varijable jest - kolika je zastupljenost pojedinih kategorija? Odgovor na ovo pitanje daje nam funkcija table
kojoj prosljeđujemo odabrani faktor.
Zadatak 4.10 - funkcija table
## tlak.f2
## prenizak nizak normalan visok previsok
## 1 3 4 3 0
Funkcija table
ne zahtijeva nužno faktor i uredno će raditi čak i sa znakovnim vektorom. No u tom slučaju ne bismo dobili informaciju o kategorijama koje nisu uopće zastupljene.
Kategorijska varijabla iz naših primjera zapravo ima prirodu tzv. ordinalne kategorijske varijable, što znači da kategorije imaju prirodni poredak (nizak tlak je “manji” od normalnog koji je “manji” od visokog). Ukoliko želimo, ovu činjenicu možemo “ugraditi” u faktor kod njegove inicijalizacije, jednostavnim dodavanjem parametra ordered
postavljenog na TRUE
. Prednost ordinalnog faktora jest ta što nam omogućuje usporedbu vrijednosti faktora uz pomoć usporednih operatora.
Zadatak 4.11 - ordinalni faktor
# napravite varijablu `tlak.f3` na isti način kao i `tlak.f2`
# ali uz dodatni parametar `ordered = TRUE`
# pripazite da poredak kategorija odgovara ordinalnom rasporedu
# ispišite `tlak.f3`
# provjerite radi li se uistinu o ordinalnom faktoru
# uz pomoć funkcije `is.ordered`
# provjerite da li je tlak prvog pacijenta
# (okvirno) niži od tlaka trećeg pacijenta
# napravite varijablu `tlak.f3` na isti način kao i `tlak.f2`
# ali uz dodatni parametar `ordered = TRUE`
# pripazite da poredak kategorija odgovara ordinalnom rasporedu
tlak.f3 <- factor(tlak, levels = c("prenizak", "nizak", "normalan", "visok", "previsok"), ordered = TRUE)
# ispišite `tlak.f3`
tlak.f3
# provjerite radi li se uistinu o ordinalnom faktoru
# uz pomoć funkcije `is.ordered`
is.ordered(tlak.f3)
# provjerite da li je tlak prvog pacijenta
# (okvirno) niži od tlaka trećeg pacijenta
tlak.f3[1] < tlak.f3[3]
## [1] nizak visok visok normalan normalan nizak visok
## [8] nizak normalan normalan
## Levels: prenizak < nizak < normalan < visok < previsok
## [1] TRUE
## [1] TRUE
Već smo uvidjeli da je u R-u zapravo “sve vektor” - brojevi su jednodimenzionalni numerički vektori, matrice su vektori sa dodanim parametrom dimenzionalnosti, liste su vektori malih listi, podatkovni okviri su liste za dodanom restrikcijom. Možemo se zapitati - što su zapravo faktori?
Implementacijski, faktor je zapravo kodirani ili enumerirani skup vrijednosti inicijalno definiranih znakovnih nizova, uz pridruženu kodnu tablicu istih. Jednostavnije rečeno, faktorizacija znakovnog vektora uključuje:
- “popisivanje” svih uočenih kategorija (ili preuzimanje eksplicitnog popisa iz parametra
levels
) - pridjeljivanje numeričkih vrijednosti redom svakoj kategoriji (npr:
"nizak"
->1
,"normalan"
->2
itd.) - “pakiranje” novostvorenog numeričkog vektora i pripadajuće “kodne tablice”
Iako ove korake R radi automatski, u internu strukturu faktora možemo se uvjeriti ako faktor probamo pretvoriti u čisti numerički, odnosno čisti znakovni tip.
Zadatak 4.12 - interna struktura faktora
# ispišite vrijednost varijable tlak.f3 pretvorene u znakovni tip
# ispišite vrijednost varijable tlak.f3 pretvorene u numerički tip
# ispišite vrijednost varijable tlak.f3 pretvorene u znakovni tip
as.character(tlak.f3)
# ispišite vrijednost varijable tlak.f3 pretvorene u numerički tip
as.numeric(tlak.f3)
## [1] "nizak" "visok" "visok" "normalan" "normalan" "nizak"
## [7] "visok" "nizak" "normalan" "normalan"
## [1] 2 4 4 3 3 2 4 2 3 3
Pretvaranjem faktora u znakovni tip zapravo radimo operaciju inverznu faktoriziranju, tj. dobijamo originalni znakovni vektor. S druge strane pretvaranjem faktora u numerički tip zapravo dobivamo “prekodirane” brojke koje faktor interno koristi za reprezentaciju kategorija. Možemo se zapitati - zašto bi nam ovo bilo korisno? Odgovor je - ne, od ovog u praksi nema neke direktne koristi. Zapravo je ovo nešto što nam može stvoriti vrlo velike probleme ako to unaprijed ne očekujemo.
Ovdje se krije već prije navedena “kontroverza” oko faktora kao tipa podataka. Ona zapravo uopće nije povezana sa samim faktorima, već sa nekim nazivnim postavkama R-a i njegovim funkcijama učitavanja podataka iz vanjske datoteke. Navedimo sada detaljne korake koji se odvijaju tijekom stvaranja podatkovnog okvira iz datoteke i pokušajmo zaključiti gdje nastaje potencijalni problem:
- R otvara CSV datoteku
- na osnovu pročitanih podataka R pokušava zaključiti kojeg je tipa koji stupac
- svi znakovni stupci se automatski faktoriziraju osim ako nije naveden parametar
stringsAsFactors - FALSE
- Formira se konačni podatkovni okvir
Vidimo li mogući problem? Pretpostavimo sljedeći scenarij: u jednom numeričkom stupcu potkrala se nenumerička vrijednost (npr. niz znakova NULL
zbog nedostajućih vrijednosti iz baze). Ovo može biti samo jedna vrijednost od više milijuna redaka, no to je dovoljno da R klasificira stupac kao “znakovni”. Budući da R automatski faktorizira znakovne stupce, u konačnom podatkovnom okviru taj numerički stupac postaje kategorijski (iako su nazivi kategorija zapravo brojevi). Nepažljivi analitičar (ili automatska skripta) ne uočava da se radi o faktoru već provodi konverziju navedenog stupca u numerički tip - i dobija potpuno semantički besmisleni niz “prekodiranih” cijelih brojeva koji, ako nisu pravovremeno uočeni, mogu biti korišteni kao ulazni podaci za daljnje analize.
Zadatak 4.13 - važnost parametra stringsAsFactors = F
# uz pomoć funkcije `read.csv` učitajte podatke iz datoteke `mjestoNULL.csv`
# u varijablu `mjesto4`
# nemojte koristiti parametar `stringsAsFactors` niti `na.strings`
# ispišite sadržaj okvira `mjesto4` na zaslon
# ispisite stupac `prosjPlaca`
# primjenite funkciju `as.numeric` na stupac `prosjPlaca` i komentirajte rezultat
# primjenite kombinaciju funkcija `as.character` i `as.numeric` na stupac `prosjPlaca`
# i komentirajte rezultat
# učitajte podatke iz datoteka `mjesto.csv` i `mjestoAlt.csv`
# podatke spremite u okvire `mjesto2` i `mjesto3`
mjesto4 <- read.csv("mjestoNULL.csv")
# ispišite sadržaj okvira `mjesto4` na zaslon
mjesto4
## pbr nazivMjesta prosjPlacaKn brojStanovnika prirez
## 1 10000 Zagreb 6359.00 790017 18
## 2 51000 Rijeka NULL 128384 15
## 3 21000 Split 5170.00 167121 10
## 4 31000 Osijek 4892.00 84104 13
## 5 20000 Dubrovnik 5348.00 28434 10
# ispisite stupac `prosjPlacaKn`
mjesto4$prosjPlacaKn
cat("----------------------\n")
# primjenite funkciju `as.numeric` na stupac `prosjPlacaKn` i komentirajte rezultat
as.numeric(mjesto4$prosjPlacaKn)
cat("----------------------\n")
# primjenite kombinaciju funkcija `as.character` i `as.numeric` na stupac `prosjPlacaKn`
# i komentirajte rezultat
# pazite na redoslijed poziva funkcija!
as.numeric(as.character(mjesto4$prosjPlacaKn))
## [1] 6359.00 NULL 5170.00 4892.00 5348.00
## Levels: 4892.00 5170.00 5348.00 6359.00 NULL
## ----------------------
## [1] 4 5 2 1 3
## ----------------------
## [1] 6359 NA 5170 4892 5348
Ponovimo - kako izbjeći ovaj scenarij? Procedura je vrlo jednostavna:
- kod korištenja funkcija
read.csv
iliread.table
uvijek koristiti parametarstringsAsFactors = FALSE
- pažljivo pregledati tipove podataka stupaca učitanog okvira
- provesti odgovarajuće konverzije stupaca
- provjeriti dobivene rezultate
Ako se pridržavamo ovih koraka gotovo nikada nećemo doći u situaciju da nam faktori prave probleme. Postoje i alternativne procedure (npr. eksplicitno navođenje tipova stupaca kod čitanja iz datoteke uz pomoć parametara colClasses
, ili korištenje sintagme as.numeric(as.character())
za pretvaranje stupaca u numerički tip koja će provesti ispravnu konverziju neovisno o tome radi li se o znakovnom stupcu ili “prikrivenom” faktoru), no gore navedeni koraci bi u većini slučajeva trebali biti potpuno dovoljni. Najvažnija stvar za zapamtiti jest ključna uloga parametra stringsAsFactors = FALSE
(koja se u literaturi zna navoditi kao obavezni parametar, bez dodatnih objašnjenja).
Zadaci za vježbu
- U mapi zajedno sa ovom bilježnicom pronađite datoteku
mjestoNOHEADER.csv
koja predstavlja datoteku istovjetnu datotecimjesto.csv
osim sljedećih značajki:
- nedostaju imena stupaca
- korišten je razmak kao razdvojnik
Pokušajte uz pomoć dokumentacije učitati podatke iz ove datoteke u varijablu mjestoNH
koja će biti istovjetna varijabli mjesto
korištenoj u lekciji.
- U mapi zajedno sa ovom bilježnicom pronađite datoteku
racun.csv
i učitajte ju u varijabluracun
. Pripazite da nizovi znakova nisu automatski pretvoreni u faktore. Ispišite na zaslon:
- broj redaka ove tablice
- broj stupaca tablice
- imena stupaca tablice
- Za tablicu
racun
napravite sljedeće:
- faktorizirajte stupac
katArtikl
- ispišite šifru, naziv i cijenu svih artikala kategorije “slatkisi i grickalice” jeftinijih od 12 Kn
- ispišite koliko proizvoda koje kategorije se nalazi u računu
- dodajte stupac ukupno koji će sadržavati ukupnu cijenu pojedine stavke uračunavajući i cijenu i količinu
- izračunajte ukupni iznos računa
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/