11 Prilagodba podatkovnih skupova uz paket dplyr

Paket dplyr jedan je od novijih paketa jezika R čija je glavna funkcija učinkovito i pojednostavljeno upravljanje podatkovnim okvirima uz pomoć skupa intuitivno dizajniranih funkcija. U trenutku pisanja ove lekcije nalazi se na vrhu liste najpopularnijih paketa usprkos jakoj “konkurenciji” u ovom području koju čine iznimno popularni paketi kao što su plyr, data.table ali i osnovne, tradicionalne metode za rad sa podatkovnim okvirima. Bilo koja od ovih opcija predstavlja dobar izbor, a iako paket dplyr nije najučinkovitiji glede samih performansi (daleko najbolje performanse trenutno nudi paket data.table), prednost ovog paketa je njegova intuitivna, čitljiva sintaksa koja programski rad sa podatkovnim okvirima čini brzim i jednostavnim te je odličan izbor za programere koji se upoznaju sa R-om ili se jednostavno žele usredotočiti na rad sa podacima kroz čitljiv, lako održiv programski kod.

Konkretne prednosti koje donosi paket dplyr su sljedeće:

  • jednostavna sintaksa (slična SQL-u, ali proceduralna) koja koristi pet glavnih “glagola” za manipulaciju podatkovnim okvirima i kao takva definira svojevrsni samostalni jezik unutar jezika R
  • veća učinkovitost od metoda koje nudi osnovni paket (inicijalno je dplyr dizajniran za veću učinkovitost programiranja, ne nužno za bolje performanse, no u međuvremenu implementacija određenih rutina u C-u omogućila je i poboljšanje i performansi izvođenja)
  • integracija sa relacijskim bazama podataka (funkcije paketa dplyr mogu se koristiti direktno nad tablicama u bazi uz pomoć automatskog prevođenja funkcija paketa dplyr u SQL naredbe)

Spomenutih osnovnih “pet glagola” koje nudi paket dplyr su sljedeći:

  • filter - za filtriranje podatkovnog skupa po retcima
  • select - za odabir pojedinih stupaca
  • arrange - za promjenu redoslijeda redaka
  • mutate - za stvaranje novih stupaca iz postojećih
  • summarise - za agregiranje podataka

Pored pet osnovnih glagola vrlo često koristimo i:

  • group_by za grupiranje podataka unutar podatkovnog skupa
  • porodicu join funkcija za spajanje podatkovnih okvira

Poznavatelji jezika SQL lako će uočiti paralele između tog jezika i navedenih funkcionalnosti paketa dplyr. Najveća razlika jest u tome što SQL radi “deklarativno”, tj. moramo pratiti pravila izgradnje SQL naredbe koja “sve radi odjednom”, dok u R-u uz pomoć funkcija paketa dplyr i već ranije upoznatog operatora %>% radnje nad podatkovnim skupovima možemo izvoditi proceduralno, s jasnim tijekom obrade podataka slijeva na desno.

Prije detaljnog pregleda funkcionalnosti paketa dplyr pogledajmo ogledni podatkovni skup kojeg ćemo koristiti tijekom ove lekcije.

11.1 Ogledni podatkovni skup: Titanic

Odaberimo za početak jedan često korišteni podatkovni skup – “Titanic Passenger Survival Dataset”. Ovaj skup pruža informacije o sudbinama putnika prekoocenaskog putničkog broda Titanic koji je potonuo 14. travnja 1912. godine pri čemu je od 2223 putnika i članova posade preživjelo samo njih 706. Podatkovni skup između ostalog sadrži imena putnika, spol, godište u trenutku potonuća, putničku klasu i sl. Postoji inačica ovog podatkovnog skupa koja dolazi sa samom R distribucijom, no mi ćemo koristiti njegovu proširenu inačicu sa Kaggle natjecanja “Titanic: Machine Learning From Disaster” o kojem se više detalja može saznati na ovoj poveznici.

Učitajmo ovaj podatkovni skup uz pomoć funkcije read_csv, funkcije paketa readr koja “nadograđuje” funkciju read.csv. Isto tako, umjesto funkcije str, pokušajmo koristiti njezin ekvivalent glimpse kojeg nudi paket dplyr.


Zadatak 11.1 - podatkovni skup Titanic

## Parsed with column specification:
## cols(
##   PassengerId = col_integer(),
##   Survived = col_integer(),
##   Pclass = col_integer(),
##   Name = col_character(),
##   Sex = col_character(),
##   Age = col_double(),
##   SibSp = col_integer(),
##   Parch = col_integer(),
##   Ticket = col_character(),
##   Fare = col_double(),
##   Cabin = col_character(),
##   Embarked = col_character()
## )
## Observations: 891
## Variables: 12
## $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,...
## $ Survived    <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,...
## $ Pclass      <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3,...
## $ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bra...
## $ Sex         <chr> "male", "female", "female", "female", "male", "mal...
## $ Age         <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, ...
## $ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4,...
## $ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1,...
## $ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "1138...
## $ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, ...
## $ Cabin       <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, ...
## $ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", ...
## # A tibble: 6 x 12
##   PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##         <int>    <int>  <int> <chr> <chr> <dbl> <int> <int> <chr>  <dbl>
## 1           1        0      3 Brau~ male     22     1     0 A/5 2~  7.25
## 2           2        1      1 Cumi~ fema~    38     1     0 PC 17~ 71.3 
## 3           3        1      3 Heik~ fema~    26     0     0 STON/~  7.92
## 4           4        1      1 Futr~ fema~    35     1     0 113803 53.1 
## 5           5        0      3 Alle~ male     35     0     0 373450  8.05
## 6           6        0      3 Mora~ male     NA     0     0 330877  8.46
## # ... with 2 more variables: Cabin <chr>, Embarked <chr>

Zašto koristiti funkciju read_csv umjesto funkcije sličnog imena read.csv? Postoji nekoliko razloga:

  • veća autonomija, tj. kvalitetnije ugrađene vrijednosti nazivnih parametara
  • veća brzina
  • ispis statusnih informacija radi lakšeg uočavanja grešaka
  • nema automatske kategorizacije
  • učitani objekt se automatski pretvara u “tibble

Svi ovi razlozi su jasni, osim možda zadnjeg. Što je “tibble”? To je kolokvijalni naziv za objekt klase tbl_df, koja predstavlja svojevrsnu nadogradnju klase data.frame, tj. osnovnog podatkovnog okvira. Najveća prednost ovog tipa objekta jest što će se kod pokušaja ispisa cijelog okvira na zaslon (što se najčešće događa omaškom i što rezultira “smrzavanjem” R konzole) pozvati posebna funkcija koja će ispisati samo dio okvira i moći će se odmah nastaviti s radom.

Ako želimo neki postojeći podatkovni okvir “nadograditi” u tibble, to možemo učiniti uz pomoć naredbe as_tibble (uočite kako se funkcije iz kolekcije tidyverse često oslanjaju na podcrtu tj. “underscore” kako bi ih lakše identificirali kao nadogradnje osnovnih funkcija koje imaju slične nazive ali koriste točku).

Uz pomoć funkcije options i parametara tibble.print_max i tibble.width možemo kontrolirati koliko u općenitom slučaju želimo ispisati stupaca i redaka u općenitom slučaju npr:

a kod pojedinog ispisa broj redaka možemo birati uz pomoć parametra n:


Prije nastavka bilo bi dobro pobliže se upoznati sa podatkovnim skupom kojeg ćemo koristiti, bilo nešto detaljnijim istraživanjem podatkovnog skupa, bilo prikupljanjem dokumentacije o istom. Kraći opis samog podatkovnog skupa preuzet sa službenih stranica Keggle natjecanja slijedi:

U ovom trenutku bi trebalo razmotriti uključuje li podatkovni skup neke kategorijske podatke. Kao što smo naučili, za razliku od funkcije read.csv koja automatski faktorizira sve znakovne stupce (što nije preporučljivo), funkcija read_csv iz paketa readr ne faktorizira ništa, već to ostavlja na odgovornost analitičaru. Iako ovo predstavlja dodatni posao za analitičara, razina kontrole i robusnosti koja se time dobiva je više nego dostatni kompromis.

U podatkovnom skupu titanic uočavamo sljedeće kategorijske varijable:

  • Survival (preživljavanje – 2 kategorije: 0 i 1)
  • Pclass (putnička klasa – 3 kategorije: 1, 2 i 3)
  • Sex (spol – 2 kategorije: “M” i “F”)
  • Embarked (luka ukrcaja – 3 kategorije: “C”, “Q” i “S”)

Faktorizirajmo navedene stupce.

Zadatak 11.2 - Kategorizacija stupaca podatkovnoga skupa Titanic

## Observations: 891
## Variables: 12
## $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,...
## $ Survived    <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,...
## $ Pclass      <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3,...
## $ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bra...
## $ Sex         <fct> male, female, female, female, male, male, male, ma...
## $ Age         <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, ...
## $ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4,...
## $ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1,...
## $ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "1138...
## $ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, ...
## $ Cabin       <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, ...
## $ Embarked    <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q,...

Postoji i sažetija alternativa za faktoriziranje stupaca koja koristi lapply:

Sada kada dobro razumijemo naš podatkovni skup i kada smo obavili inicijalne pripreme u smislu kategorizacije stupaca, možemo krenuti sa upoznavanjem funkcija paketa dplyr.

11.2 Stvaranje podskupa obzervacija uz filter i slice

U poglavlju o podatkovnim okvirima smo već naučili da se “rezanje” podatkovnih okvira može raditi slično rezanju matrica - uz pomoć indeksnih vektora kojima definiramo koje retke/stupce zadržavamo. Isto tako, naučili smo da indeksni vektori mogu biti cjelobrojni (lokacijski), logički i imenski.

Kod definicije podskupa redaka daleko najčešći tip indeksnog vektora je logički - uz pomoć varijabli tj. stupaca definiramo određeni kriterij koji “filtrira” retke. Nažalost, osnovna R-ova sintaksa korištenja logičkih indeksnih redaka za određivanje podskupa redaka je ponešto nespretna, što se može vidjeti iz primjera:

Prvi i očiti problem jest potreba ponavljanja imena podatkovnog okvira (koju možemo eliminirati uz pomoć funkcije attach, što smo rekli da nije idealno rješenje jer donosi niz novih potencijalnih problema). Drugo pitanje jest problem čitljivosti - gornju naredbu nije lako vizualno interpretirati, tj. naknadnim pregledom koda nije lako odmah uočiti da se radi o reduciranju broja redaka.

Funkcija filter eksplicitnom sintaksom odaje da se radi o filtriranju redaka, a također omogućuje korištenje imena stupaca bez potrebe za referenciranjem imena podatkovnog okvira:

Isto tako, dobro je uočiti da je prvi argument funkcije sam podatkovni skup, što nam omogućuje jednostavno ulančavanje. Na ovom principu dizajnirana je većina funkcija paketa dplyr.

Gore navedena funkcija predstavlja najčešći način odabira podskupa redaka (poznavatelji SQL-a uočiti će sličnost sa WHERE segmentom SQL upita). Pored funkcije filter, za određivanje podskupa redaka imamo i sljedeće funkcije, također vrlo intuitivnih imena (radi lakše interpretacije umjesto potpisa funkcija dajemo primjere parametara):

  • distinct(podaci) - za uklanjanje duplikata
  • slice(podaci, 1:10) - za lokacijsko indeksiranje
  • sample_frac(podaci, 0.2) - nasumični odabir dijela skupa po danom omjeru
  • sample_n(podaci, 50) - nasumični odabir zadanog broja redaka
  • top_n(podaci, 10, a) - prvih n redaka, gledano po poretku stupca a

Za poredak redaka u ispisu možemo koristiti:

  • arrange(podaci, a, desc(b)) - poredaj po stupcu a uzlazno pa po b silazno

Isprobajmo ovo na primjerima:


Zadatak 11.3 - odabir redaka

## # A tibble: 14 x 12
##    PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##          <int> <fct>    <fct>  <chr> <fct> <dbl> <int> <int> <chr>  <dbl>
##  1          55 0        1      Ostb~ male     65     0     1 113509  62.0
##  2          97 0        1      Gold~ male     71     0     0 PC 17~  34.7
##  3         171 0        1      Van ~ male     61     0     0 111240  33.5
##  4         253 0        1      Stea~ male     62     0     0 113514  26.6
##  5         276 1        1      Andr~ fema~    63     1     0 13502   78.0
##  6         439 0        1      Fort~ male     64     1     4 19950  263  
##  7         457 0        1      Mill~ male     65     0     0 13509   26.6
##  8         494 0        1      Arta~ male     71     0     0 PC 17~  49.5
##  9         546 0        1      Nich~ male     64     0     0 693     26  
## 10         556 0        1      Wrig~ male     62     0     0 113807  26.6
## 11         626 0        1      Sutt~ male     61     0     0 36963   32.3
## 12         631 1        1      Bark~ male     80     0     0 27042   30  
## 13         746 0        1      Cros~ male     70     1     1 WE/P ~  71  
## 14         830 1        1      Ston~ fema~    62     0     0 113572  80  
## # ... with 2 more variables: Cabin <chr>, Embarked <fct>
## # A tibble: 6 x 12
##   PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##         <int> <fct>    <fct>  <chr> <fct> <dbl> <int> <int> <chr>  <dbl>
## 1         166 1        3      "Gol~ male   9        0     2 363291  20.5
## 2         371 1        1      Hard~ male  25        1     0 11765   55.4
## 3         508 1        1      "Bra~ male  NA        0     0 111427  26.6
## 4         571 1        2      Harr~ male  62        0     0 S.W./~  10.5
## 5         710 1        3      "Mou~ male  NA        1     1 2661    15.2
## 6         832 1        2      Rich~ male   0.83     1     1 29106   18.8
## # ... with 2 more variables: Cabin <chr>, Embarked <fct>
## # A tibble: 5 x 12
##   PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##         <int> <fct>    <fct>  <chr> <fct> <dbl> <int> <int> <chr>  <dbl>
## 1         496 0        3      Yous~ male     NA     0     0 2627   14.5 
## 2         464 0        2      Mill~ male     48     0     0 234360 13   
## 3         869 0        3      van ~ male     NA     0     0 345777  9.5 
## 4          92 0        3      Andr~ male     20     0     0 347466  7.85
## 5          61 0        3      Sira~ male     22     0     0 2669    7.23
## # ... with 2 more variables: Cabin <chr>, Embarked <fct>
## # A tibble: 6 x 12
##   PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##         <int> <fct>    <fct>  <chr> <fct> <dbl> <int> <int> <chr>  <dbl>
## 1          12 1        1      Bonn~ fema~    58     0     0 113783  26.6
## 2         196 1        1      Lure~ fema~    58     0     0 PC 17~ 147. 
## 3         269 1        1      Grah~ fema~    58     0     1 PC 17~ 153. 
## 4         367 1        1      Warr~ fema~    60     1     0 110813  75.2
## 5         830 1        1      Ston~ fema~    62     0     0 113572  80  
## 6         276 1        1      Andr~ fema~    63     1     0 13502   78.0
## # ... with 2 more variables: Cabin <chr>, Embarked <fct>

Uočite da “prvih n” redaka samo odabire podskup redaka, neće ga nužno ispisati po redu. Ukoliko želimo poredani ispis moramo koristiti i funkciju arrange.

11.3 Stvaranje podskupa varijabli uz select

Druga metoda rezanja podatkovnog okvira jest odabir podskupa stupaca. Za razliku od biranja podskupa redaka, gdje se najčešće služimo logičkim indeksiranjem tj. filtriranjem po određenom kriteriju, stupce tj. varijable najčešće referenciramo po njihovom imenu. Sintaksa odabira podskupa stupaca po imenu uz pomoć osnovnog načina indeksiranja u R-u izgleda na primjer ovako:

Ovdje također uočavamo određenu nespretnost i teškoću interpretacije. Nazivi stupaca moraju biti ugrađeni u funkciju stvaranja vektora što smanjuje čitljivost, a naredba nigdje eksplicitno ne iskazuje da se radi o odabiru stupaca već do moramo zaključiti iz položaja indeksnog vektora. Dodatno, nema jednostavnog načina za odabir raspona stupaca po imenu, postojanju nekog podniza ili uzorka unutar imena i sl.

Funkcija select nam omogućuje eksplicitni odabir stupaca uz pomoć sintakse:

Dakle jednostavno navodimo podatkovni okvir i niz stupaca koje želimo odabrati. Ovdje je također lako uočiti sličnost sa SQL-om, konkretno SELECT segmentom SQL upita.

No gornja sintaksa nije sve što ova funkcija nudi - select ima čitav niz pomoćnih funkcija i operatora koji uvelike proširuju njezinu funkcionalnost, kao npr:

  • select(podaci, a:c) - odaberi stupce od a do c
  • select(podaci, -a, -b) - odaberi sve stupce osim a i b
  • select(podaci, starts_with("PO"))) - odaberi stupce koji počinju sa slovima "PO"
  • select(podaci, contains("stup")) - odaberi stupce koji sadrže podniz "stup"
  • select(podaci, matches("[123]{2,3}")) - odaberi stupce koji odgovaraju danom regularnom izrazu

Ovo nisu sve mogućnosti, no dodatne opcije lako je pronaći u službenoj dokumentaciji.

Isprobajmo ovu naredbu, također na podatkovnom skupu Titanic.


Zadatak 11.4 - funkcija select

## # A tibble: 10 x 3
##    Name                                                        Age Survived
##    <chr>                                                     <dbl> <fct>   
##  1 Panula, Mrs. Juha (Maria Emilia Ojala)                    41    0       
##  2 Taussig, Miss. Ruth                                       18    1       
##  3 Sirayanian, Mr. Orsen                                     22    0       
##  4 Lemore, Mrs. (Amelia Milley)                              34    1       
##  5 Hamalainen, Master. Viljo                                  0.67 1       
##  6 Sandstrom, Miss. Marguerite Rut                            4    1       
##  7 Baclini, Miss. Marie Catherine                             5    1       
##  8 Stead, Mr. William Thomas                                 62    0       
##  9 Penasco y Castellana, Mrs. Victor de Satode (Maria Josef~ 17    1       
## 10 Hassan, Mr. Houssein G N                                  11    0
## # A tibble: 11 x 7
##    Name                             Sex     Age SibSp Parch Ticket     Fare
##    <chr>                            <fct> <dbl> <int> <int> <chr>     <dbl>
##  1 Wheadon, Mr. Edward H            male   66       0     0 C.A. 245~ 10.5 
##  2 Ostby, Mr. Engelhart Cornelius   male   65       0     1 113509    62.0 
##  3 Goldschmidt, Mr. George B        male   71       0     0 PC 17754  34.7 
##  4 Connors, Mr. Patrick             male   70.5     0     0 370369     7.75
##  5 Duane, Mr. Frank                 male   65       0     0 336439     7.75
##  6 Millet, Mr. Francis Davis        male   65       0     0 13509     26.6 
##  7 Artagaveytia, Mr. Ramon          male   71       0     0 PC 17609  49.5 
##  8 Barkworth, Mr. Algernon Henry W~ male   80       0     0 27042     30   
##  9 Mitchell, Mr. Henry Michael      male   70       0     0 C.A. 245~ 10.5 
## 10 Crosby, Capt. Edward Gifford     male   70       1     1 WE/P 5735 71   
## 11 Svensson, Mr. Johan              male   74       0     0 347060     7.78
## # A tibble: 9 x 10
##   Survived Pclass Name        Sex     Age SibSp Parch Ticket  Fare Embarked
##   <fct>    <fct>  <chr>       <fct> <dbl> <int> <int> <chr>  <dbl> <fct>   
## 1 0        3      Thorneycro~ male     NA     1     0 376564 16.1  S       
## 2 1        3      Nilsson, M~ fema~    26     0     0 347470  7.85 S       
## 3 1        2      Faunthorpe~ fema~    29     1     0 2926   26    S       
## 4 1        3      Jansson, M~ male     21     0     0 350034  7.80 S       
## 5 0        2      Eitemiller~ male     23     0     0 29751  13    S       
## 6 0        3      Sdycoff, M~ male     NA     0     0 349222  7.90 S       
## 7 1        2      Davies, Ma~ male      8     1     1 C.A. ~ 36.8  S       
## 8 0        3      Reed, Mr. ~ male     NA     0     0 362316  7.25 S       
## 9 1        2      Leitch, Mi~ fema~    NA     0     0 248727 33    S
## # A tibble: 11 x 2
##      Age Embarked
##    <dbl> <fct>   
##  1    14 C       
##  2     4 S       
##  3    58 S       
##  4    20 S       
##  5    39 S       
##  6    14 S       
##  7    55 S       
##  8     2 Q       
##  9    NA S       
## 10    31 S       
## 11    NA C
## # A tibble: 10 x 9
##    Name               Sex      Age SibSp Parch Ticket  Fare Pclass Survived
##    <chr>              <fct>  <dbl> <int> <int> <chr>  <dbl> <fct>  <fct>   
##  1 Boulos, Mr. Hanna  male      NA     0     0 2664    7.22 3      0       
##  2 Klaber, Mr. Herman male      NA     0     0 113028 26.6  1      0       
##  3 Lennon, Mr. Denis  male      NA     1     0 370371 15.5  3      0       
##  4 "O'Dwyer, Miss. E~ female    NA     0     0 330959  7.88 3      1       
##  5 Peter, Miss. Anna  female    NA     1     1 2668   22.4  3      1       
##  6 Peters, Miss. Kat~ female    NA     0     0 330935  8.14 3      0       
##  7 Plotcharsky, Mr. ~ male      NA     0     0 349227  7.90 3      0       
##  8 Razi, Mr. Raihed   male      NA     0     0 2629    7.23 3      0       
##  9 Sage, Mr. Frederi~ male      NA     8     2 CA. 2~ 69.6  3      0       
## 10 Zabour, Miss. Tha~ female    NA     1     0 2665   14.5  3      0

11.4 Stvaranje novih stupaca uz mutate

U radu sa podatkovnim skupovima često se pojavi potreba za stvaranjem dodatnih varijabli uz pomoć informacija pohranjenih u jednoj ili više postojećih varijabli. Novi stupac najčešće stvaramo uz pomoć nekog izraza koji opisuje na koji način transformiramo postojeće podatke; motivacija može biti normalizacija numeričke varijable, stvaranje indikatorske ili kategorijske varijable, sumiranje više varijabli u jednu jedinstvenu varijablu ili bilo koja druga transformacija s ciljem dobivanja nove varijable koja je na neki način potrebna za daljnji proces analize.

Ako pretpostavimo stvaranje novog stupca koji će pohraniti zbroj numeričkih vrijednosti postojećih stupaca, onda bi uz pomoć osnovnih funkcija R-a to proveli na sljedeći način:

Paket dplyr nam nudi alternativu u obliku funkcija mutate i transmute:

Razlika: mutate vraća cijeli originalni podatkovni okvir uz novostvorene stupce, dok transmute zadržava samo one koje smo naveli unutar poziva funkcije. Zbog toga transmute možemo koristiti kao skraćenu kombinaciju mutate i select, npr:

(NAPOMENA: Ovdje se ne moramo bojati degradacije performansi zbog kopiranja okvira, budući da R kod dodavanja stupca radi “plitku kopiju” (shallow copy), tj. novi okvir samo referencira novi stupac dok dijeli reference na preostale stupce sa “originalnim” okvirom. “Duboka kopija” (deep copy) bi značilo da se svi stupci kopiraju u potpuno novi okvir, u kojem slučaju bi ova operacija bila resursno “skupa”)

Uočite da mutate i transmute nisu alternative naredbi UPDATE iz SQL-a, već zapravo odgovaraju SELECT segmentu naredbe u scenarijima kada ne biramo pojedine stupce već ih kombiniramo u sklopu izraza.

Funkcije mutate i transmute koriste uobičajene (vektorizirane) funkcije i operatore, a imamo na raspolaganju i niz dodatnih tzv. “prozorskih” (window) funkcija koje nam omogućuju dodatnu fleksibilnost kod stvaranja novih varijabli, kao npr:

  • ntile, cut - stvaranje kategorijske varijable koja grupira vrijednosti u n ladica; ntile će napraviti kategorije jednake veličine, dok će cut rezati u ovisnosti o veličini intervala numeričkih vrijednosti
  • dense_rank, min_rank - rangiranje obzervacija (razlika je samo u tretiranju obzervacija sa istim vrijednostima)
  • between - opisuje da li je varijabla nekog stupca u intervalu zadanom sa dva druga stupca
  • pmin, pmax - “paralelni minimum i maksimum”, tj. minimum ili maksimum vrijednosti odabranih stupaca gledano po retcima
  • itd.

Popis svih dostupnih funkcija može se pronaći u dokumentaciji.


Zadatak 11.5 - funkcija mutate

## # A tibble: 10 x 3
##    Name                                                Pclass FareToday
##    <chr>                                               <fct>  <chr>    
##  1 "Ford, Miss. Doolina Margaret \"Daisy\""            3      $819.84  
##  2 Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson) 3      $398.3   
##  3 Panula, Mrs. Juha (Maria Emilia Ojala)              3      $946.55  
##  4 Jussila, Miss. Mari Aina                            3      $234.33  
##  5 Stanley, Miss. Amy Zillah Elsie                     3      $180.07  
##  6 Honkanen, Miss. Eliina                              3      $189.01  
##  7 Nilsson, Miss. Helmina Josefina                     3      $187.32  
##  8 Davis, Miss. Mary                                   2      $310.05  
##  9 Leader, Dr. Alice (Farnham)                         1      $618.41  
## 10 Carter, Mrs. William Ernest (Lucile Polk)           1      $2862
## # A tibble: 20 x 4
##    Name                                          Pclass   Fare FareCategory
##    <chr>                                         <fct>   <dbl>        <int>
##  1 Charters, Mr. David                           3        7.73            1
##  2 Connolly, Miss. Kate                          3        7.75            1
##  3 Connaghton, Mr. Michael                       3        7.75            1
##  4 Youseff, Mr. Gerious                          3        7.22            1
##  5 Lemore, Mrs. (Amelia Milley)                  2       10.5             2
##  6 Lievens, Mr. Rene Aime                        3        9.5             2
##  7 Larsson, Mr. August Viktor                    3        9.48            2
##  8 Meyer, Mr. August                             2       13               3
##  9 Lindell, Mr. Edvard Bengtsson                 3       15.6             3
## 10 Jerwan, Mrs. Amin S (Marie Marthe Thuillard)  2       13.8             3
## 11 Chapman, Mr. Charles Henry                    2       13.5             3
## 12 Blackwell, Mr. Stephen Weart                  1       35.5             4
## 13 Moran, Miss. Bertha                           3       24.2             4
## 14 Collyer, Mrs. Harvey (Charlotte Annie Tate)   2       26.2             4
## 15 Chip, Mr. Chang                               3       56.5             5
## 16 Chaffee, Mr. Herbert Fuller                   1       61.2             5
## 17 Bissette, Miss. Amelia                        1      136.              5
## 18 Endres, Miss. Caroline Louise                 1      228.              5
## 19 Allison, Mrs. Hudson J C (Bessie Waldo Danie~ 1      152.              5
## 20 Carter, Master. William Thornton II           1      120               5
## # A tibble: 10 x 14
##    PassengerId Survived Pclass Name  Sex     Age SibSp Parch Ticket  Fare
##          <int> <fct>    <fct>  <chr> <fct> <dbl> <int> <int> <chr>  <dbl>
##  1           1 0        3      Brau~ male     22     1     0 A/5 2~  7.25
##  2           2 1        1      Cumi~ fema~    38     1     0 PC 17~ 71.3 
##  3           3 1        3      Heik~ fema~    26     0     0 STON/~  7.92
##  4           4 1        1      Futr~ fema~    35     1     0 113803 53.1 
##  5           5 0        3      Alle~ male     35     0     0 373450  8.05
##  6           6 0        3      Mora~ male     NA     0     0 330877  8.46
##  7           7 0        1      McCa~ male     54     0     0 17463  51.9 
##  8           8 0        3      Pals~ male      2     3     1 349909 21.1 
##  9           9 1        3      John~ fema~    27     0     2 347742 11.1 
## 10          10 1        2      Nass~ fema~    14     1     0 237736 30.1 
## # ... with 4 more variables: Cabin <chr>, Embarked <fct>,
## #   hadRelativesOnBoard <lgl>, EmbarkationPort <chr>

11.5 Ogledni podatkovni skup: Houston flights

Učitajmo paket dplyr i podatkovni okvir hflights koji se nalazi u paketu istog imena i može ga se dohvatiti iz CRAN repozitorija. Nakon učitavanja paketa podatkovni okvir možemo prebaciti u globalnu okolinu uz pomoć funkcije data.


Zadatak 11.6 - podatkovni skup hflights

## Observations: 227,496
## Variables: 21
## $ Year              <int> 2011, 2011, 2011, 2011, 2011, 2011, 2011, 20...
## $ Month             <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,...
## $ DayofMonth        <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1...
## $ DayOfWeek         <int> 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6,...
## $ DepTime           <int> 1400, 1401, 1352, 1403, 1405, 1359, 1359, 13...
## $ ArrTime           <int> 1500, 1501, 1502, 1513, 1507, 1503, 1509, 14...
## $ UniqueCarrier     <chr> "AA", "AA", "AA", "AA", "AA", "AA", "AA", "A...
## $ FlightNum         <int> 428, 428, 428, 428, 428, 428, 428, 428, 428,...
## $ TailNum           <chr> "N576AA", "N557AA", "N541AA", "N403AA", "N49...
## $ ActualElapsedTime <int> 60, 60, 70, 70, 62, 64, 70, 59, 71, 70, 70, ...
## $ AirTime           <int> 40, 45, 48, 39, 44, 45, 43, 40, 41, 45, 42, ...
## $ ArrDelay          <int> -10, -9, -8, 3, -3, -7, -1, -16, 44, 43, 29,...
## $ DepDelay          <int> 0, 1, -8, 3, 5, -1, -1, -5, 43, 43, 29, 19, ...
## $ Origin            <chr> "IAH", "IAH", "IAH", "IAH", "IAH", "IAH", "I...
## $ Dest              <chr> "DFW", "DFW", "DFW", "DFW", "DFW", "DFW", "D...
## $ Distance          <int> 224, 224, 224, 224, 224, 224, 224, 224, 224,...
## $ TaxiIn            <int> 7, 6, 5, 9, 9, 6, 12, 7, 8, 6, 8, 4, 6, 5, 6...
## $ TaxiOut           <int> 13, 9, 17, 22, 9, 13, 15, 12, 22, 19, 20, 11...
## $ Cancelled         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ CancellationCode  <chr> "", "", "", "", "", "", "", "", "", "", "", ...
## $ Diverted          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## # A tibble: 6 x 21
##    Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum
##   <int> <int>      <int>     <int>   <int>   <int> <chr>             <int>
## 1  2011     1          1         6    1400    1500 AA                  428
## 2  2011     1          2         7    1401    1501 AA                  428
## 3  2011     1          3         1    1352    1502 AA                  428
## 4  2011     1          4         2    1403    1513 AA                  428
## 5  2011     1          5         3    1405    1507 AA                  428
## 6  2011     1          6         4    1359    1503 AA                  428
## # ... with 13 more variables: TailNum <chr>, ActualElapsedTime <int>,
## #   AirTime <int>, ArrDelay <int>, DepDelay <int>, Origin <chr>,
## #   Dest <chr>, Distance <int>, TaxiIn <int>, TaxiOut <int>,
## #   Cancelled <int>, CancellationCode <chr>, Diverted <int>

11.6 Grupiranje i agregacija uz group_by i summarise

U literaturi koja se bavi analizom podataka često ćemo naići na tzv. SAC paradigmu (engl. Split-Apply-Combine). Radi se o strategiji koja se svodi na rastavljanje velikog zadatka na manje dijelove, obavljanje određenog posla na svakom od dijelova te konačno kombiniranje svih rezultata u jedinstvenu cjelinu. Potrebu za ovom paradigmom nailazimo u različitim scenarijima analize - u eksploratornoj analizi podataka htjet ćemo izračunati različite statistike ili stvoriti nove varijable zasebno za različite podskupove podataka (npr. ovisno o nekoj kategorijskoj varijabli); kod obrade iznimno velikih količina podataka često želimo ubrzati proces obrade na način da podatke razbijemo u manje skupove koji će se svaki obrađivati zasebno (poznati princip Map-Reduce).

Poznavatelji SQL-a će ovaj princip lako prepoznati kao grupiranje i agregaciju koji se provode kroz GROUP BY segment SQL upita uz prateće elemente u SELECT dijelu. Paket dplyr nudi vrlo sličnu funkcionalnost (iako na proceduralni način) - prvo provedemo “grupiranje”, tj. stvaranje podskupova redaka nekog okvira, a onda provodimo daljnje obrade paralelno nad svakim podskupom, da bi na kraju sve rezultate sakupili u jedinstveni podatkovni okvir.

Za grupiranje dplyr nudi funkciju group_by kojom tablicu (podatkovni okvir) pretvaramo u “grupiranu tablicu” (grouped_tbl):

Isprobajmo ovu funkciju na našem okviru hflights.


Zadatak 11.7 - grupirana tablica

## Observations: 95
## Variables: 21
## $ Year              <int> 2011, 2011, 2011, 2011, 2011, 2011, 2011, 20...
## $ Month             <int> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,...
## $ DayofMonth        <int> 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, ...
## $ DayOfWeek         <int> 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 7, 6,...
## $ DepTime           <int> 2116, 2109, 2111, 2112, 2124, 2145, 2114, 21...
## $ ArrTime           <int> 2255, 2252, 2255, 2326, 2321, 2327, 2324, 23...
## $ UniqueCarrier     <chr> "CO", "CO", "CO", "CO", "CO", "CO", "CO", "C...
## $ FlightNum         <int> 815, 815, 815, 815, 815, 815, 815, 815, 815,...
## $ TailNum           <chr> "N74856", "N57852", "N78866", "N57863", "N57...
## $ ActualElapsedTime <int> 219, 223, 224, 254, 237, 222, 250, 231, 229,...
## $ AirTime           <int> 194, 191, 190, 197, 198, 200, 192, 187, 186,...
## $ ArrDelay          <int> 12, 6, 9, 40, 35, 41, 38, 18, 19, 10, 29, 33...
## $ DepDelay          <int> 6, -1, 1, 2, 14, 35, 4, 0, 6, 9, 6, 17, 38, ...
## $ Origin            <chr> "IAH", "IAH", "IAH", "IAH", "IAH", "IAH", "I...
## $ Dest              <chr> "LAX", "LAX", "LAX", "LAX", "LAX", "LAX", "L...
## $ Distance          <int> 1379, 1379, 1379, 1379, 1379, 1379, 1379, 13...
## $ TaxiIn            <int> 10, 13, 15, 18, 12, 10, 14, 19, 12, 12, 20, ...
## $ TaxiOut           <int> 15, 19, 19, 39, 27, 12, 44, 25, 31, 13, 39, ...
## $ Cancelled         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ CancellationCode  <chr> "", "", "", "", "", "", "", "", "", "", "", ...
## $ Diverted          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## [1] "------------------------"
## Observations: 95
## Variables: 21
## Groups: Month [6]
## $ Year              <int> 2011, 2011, 2011, 2011, 2011, 2011, 2011, 20...
## $ Month             <int> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,...
## $ DayofMonth        <int> 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, ...
## $ DayOfWeek         <int> 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 7, 6,...
## $ DepTime           <int> 2116, 2109, 2111, 2112, 2124, 2145, 2114, 21...
## $ ArrTime           <int> 2255, 2252, 2255, 2326, 2321, 2327, 2324, 23...
## $ UniqueCarrier     <chr> "CO", "CO", "CO", "CO", "CO", "CO", "CO", "C...
## $ FlightNum         <int> 815, 815, 815, 815, 815, 815, 815, 815, 815,...
## $ TailNum           <chr> "N74856", "N57852", "N78866", "N57863", "N57...
## $ ActualElapsedTime <int> 219, 223, 224, 254, 237, 222, 250, 231, 229,...
## $ AirTime           <int> 194, 191, 190, 197, 198, 200, 192, 187, 186,...
## $ ArrDelay          <int> 12, 6, 9, 40, 35, 41, 38, 18, 19, 10, 29, 33...
## $ DepDelay          <int> 6, -1, 1, 2, 14, 35, 4, 0, 6, 9, 6, 17, 38, ...
## $ Origin            <chr> "IAH", "IAH", "IAH", "IAH", "IAH", "IAH", "I...
## $ Dest              <chr> "LAX", "LAX", "LAX", "LAX", "LAX", "LAX", "L...
## $ Distance          <int> 1379, 1379, 1379, 1379, 1379, 1379, 1379, 13...
## $ TaxiIn            <int> 10, 13, 15, 18, 12, 10, 14, 19, 12, 12, 20, ...
## $ TaxiOut           <int> 15, 19, 19, 39, 27, 12, 44, 25, 31, 13, 39, ...
## $ Cancelled         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ CancellationCode  <chr> "", "", "", "", "", "", "", "", "", "", "", ...
## $ Diverted          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## # A tibble: 6 x 21
##    Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum
##   <int> <int>      <int>     <int>   <int>   <int> <chr>             <int>
## 1  2011     4         30         6    2116    2255 CO                  815
## 2  2011     4         29         5    2109    2252 CO                  815
## 3  2011     4         28         4    2111    2255 CO                  815
## 4  2011     4         27         3    2112    2326 CO                  815
## 5  2011     4         26         2    2124    2321 CO                  815
## 6  2011     4         25         1    2145    2327 CO                  815
## # ... with 13 more variables: TailNum <chr>, ActualElapsedTime <int>,
## #   AirTime <int>, ArrDelay <int>, DepDelay <int>, Origin <chr>,
## #   Dest <chr>, Distance <int>, TaxiIn <int>, TaxiOut <int>,
## #   Cancelled <int>, CancellationCode <chr>, Diverted <int>
## [1] "------------------------"
## # A tibble: 6 x 21
## # Groups:   Month [1]
##    Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum
##   <int> <int>      <int>     <int>   <int>   <int> <chr>             <int>
## 1  2011     4         30         6    2116    2255 CO                  815
## 2  2011     4         29         5    2109    2252 CO                  815
## 3  2011     4         28         4    2111    2255 CO                  815
## 4  2011     4         27         3    2112    2326 CO                  815
## 5  2011     4         26         2    2124    2321 CO                  815
## 6  2011     4         25         1    2145    2327 CO                  815
## # ... with 13 more variables: TailNum <chr>, ActualElapsedTime <int>,
## #   AirTime <int>, ArrDelay <int>, DepDelay <int>, Origin <chr>,
## #   Dest <chr>, Distance <int>, TaxiIn <int>, TaxiOut <int>,
## #   Cancelled <int>, CancellationCode <chr>, Diverted <int>

Vidimo da grupiranjem nismo “izgubili” nikakvu informaciju - grupirani podatkovni okvir i dalje izgleda identično originalnom, “negrupiranom” okviru. Zapravo jedina naznaka da je nešto drugačije jest nova, nasljeđena klasa te redak Groups:.. u ispisu redaka. Ovo znači da je grupiranje okvira samo naznaka da se neke daljnje (najčešće agregacijske) operacije ne izvode nad cijelim okvirom, već nad pojedinim grupama. Isto tako, ukoliko želimo, okvir uvijek možemo lako “odgrupirati” uz pomoć funkcije ungroup.

Za agregaciju se koristimo funkcijom summarise koja prima podatke (grupiranu tablicu) a potom kombinacije agregacijskih funkcija i stupaca nad kojim se iste izvršavaju, npr.:

Za agregacijsku funkciju u pravilu možemo koristiti bilo koju funkciju koja vektor vrijednosti svodi na jednu vrijednost (npr. mean, max, sd itd.), a često korištene funkcije koje nudi sam paket dplyr su:

  • first, last, nth - prvi, zadnji, n-ti element grupe
  • n, n_distinct - broj (jedinstvenih) vrijednosti

Zadatak 11.8 - funkcija summarise

## # A tibble: 1 x 1
##   meanDelay
##       <dbl>
## 1      21.6
## [1] "------------------------"
## # A tibble: 6 x 2
##   Month meanDelay
##   <int>     <dbl>
## 1     4      21.1
## 2     5      20.9
## 3     6      28.0
## 4     8     -27.5
## 5     9      14  
## 6    10     -13

Uočite da je poželjno imenovati agregirane stupce.


U praksi ne čuvamo referencu na grupirane tablice već unutar jedne naredbe provodimo cijeli proces odabira redaka i stupaca, grupiranja i provođenja agregacije. Ako koristimo operator %>%, onda to može izgledati ovako:

Konačno, ponovimo još jednom lako uočljivu sličnost gornjeg izraza sa SQL upitima u relacijskim bazama, uz bitnu razliku da ovdje operacije provodimo proceduralno što uvelike povećava čitljivost te da vrlo lako u bilo kojem trenutku možemo pohraniti i provjeriti međurezultat.

Pokušajmo sada iskoristiti sva dosadašnja saznanja o paketu dplyr i riješiti sljedeće primjere. Svi zadaci vezani su uz cijeli podatkovni skup hflights.


Zadatak 11.9 - složeni upiti

## # A tibble: 12 x 2
##    Month BadWeatherCancellations
##    <int>                   <int>
##  1     1                     149
##  2     2                     929
##  3     3                      50
##  4     4                      40
##  5     5                     106
##  6     6                      49
##  7     7                      29
##  8     8                     108
##  9     9                      38
## 10    10                      46
## 11    11                      13
## 12    12                      95

Zadatak 11.10 - složeni upiti (2)

## Warning: Factor `ArrDelayCat` contains implicit NA, consider using
## `forcats::fct_explicit_na`
## # A tibble: 11 x 5
##    ArrDelayCat totalFlights minArrDelay maxArrDelay mostFreqDest
##    <fct>              <int>       <dbl>       <dbl> <chr>       
##  1 (-71,34.8]        203101         -70          34 DAL         
##  2 (34.8,140]         18551          35         139 DAL         
##  3 (140,244]           1849         140         244 ATL         
##  4 (244,349]            294         245         346 ATL         
##  5 (349,454]             48         350         450 ATL         
##  6 (454,559]             16         458         556 ATL         
##  7 (559,664]              4         579         663 DFW         
##  8 (664,768]              4         685         766 DFW         
##  9 (768,873]              4         775         861 DEN         
## 10 (873,979]              3         918         978 DFW         
## 11 <NA>                3622          NA          NA DAL

11.7 Spajanje podatkovnih okvira uz join funkcije

Spajanje podatkovnih okvira jest operacija poznata svim programerima koji imaju iskustvo u radu sa relacijskim bazama podataka i SQL-om. Kod pohrane u relacijsku bazu podataka tablice se često dekomponiraju u više tablica postupkom koji se naziva “normalizacija”. Svrha normalizacije je poglavito uklanjanje nepotrebne redundancije - svaka tablica zadržava dovoljno podataka kako bi se prema potrebi operacijom spajanja mogli rekonstruirati originalni podaci tj. skupovi obzervacija.

Iako postoje različiti oblici spajanja tablica, daleko najčešće je tzv. “prirodno” spajanje kod koje u jednoj tablici imamo jedinstvene identifikatore vezane uz podatke koji se nalaze u drugoj tablici. Na primjer - zamislimo da imamo tablice Korisnik i Mjesto, gdje tablica korisnika sustava može imati stupac koji pohranjuje poštanski broj mjesta boravišta korisnika, dok se ostali podaci vezani uz mjesta nalaze u drugoj tablici. Pohraniti naziv mjesta u tablicu s korisnicima bilo bi redundantno, budući da poštanski broj kao takav jedinstveno identificira mjesto, a ukoliko želimo kod ispisa korisnika vidjeti i nazive mjesta, obavljamo operaciju prirodnog spajanja dvije tablice po poštanskom broju mjesta koje je tzv. “strani ključ” u tablici Korisnik a istovremeno i “primarni ključ” u tablici Mjesto.

Stvorimo ovakve podatkovne okvire (sa tek nekoliko redaka kako bi koncept bio jasniji).

Ako bi htjeli imati okvir sa stupcima (id, prezime, pbrBoravista, naziv), moramo prirodno spojiti ova dva okvira. Paket dplyr za ovo nudi funkciju inner_join:

gdje su df1 i df2 podatkovni okviri koje spajamo, a nizovi znakova “s1” i “s2” označavaju imena stupaca “lijevog” i “desnog” okvira prema kojima provodimo spajanje (uočite samo jedan znak jednakosti!). Ukoliko stupac kojeg koristimo ima isto ime u obje tablice možemo navesti samo ime tog stupca (ili znakovni vektor više stupaca ako spajamo preko tzv. “kompozitnog” stranog ključa), ili taj parametar potpuno ispustiti (ako su stupci prema kojima provodimo spajanje jedini stupci čiji se nazivi poklapaju).


Zadatak 11.11 - prirodno spajanje

##   id prezime pbrBoravista  naziv
## 1  1    Ivic        10000 Zagreb
## 2  2   Peric        31000 Osijek
## 3  3    Anic        10000 Zagreb

Vodite računa da je spajanje vrlo “skupa” operacija; ako spajate okvire sa vrlo velikim brojem redaka operacija bi mogla trajati vrlo dugo i zauzeti dosta memorije. U slučaju da često dolazi do potrebe za ovakvim “velikim” spajanjima, onda se snažno preporučuje korištenje paketa data.table koji implementira algoritme sa znatno boljim performansama kod operacija spajanja (korištenjem indeksiranja), ili - ukoliko radimo s podacima iz relacijske baze - provesti spajanja na strani baze podataka i onda u R povući rezultat, umjesto da se spajanje ostavlja na odgovornost R-u. U idućem poglavlju prikazati ćemo kako se ovo potencijalno može izvesti i bez potrebe za pisanjem zasebnih SQL naredbi.

Ako pogledamo rezultat gornjeg primjera, možemo vidjeti da smo “izgubili” jedan redak tablice mjesto. Naime, redak (21000, ‘Split’) nije imao pripadajućeg korisnika te je “nestao” iz rezultata. Ovo je potpuno očekivani rezultat prirodnog spajanja, no postoje slučajevi kada želimo zadržati sve retke iz jedne od tablica koje spajamo. U tom slučaju koristimo tzv. “vanjsko spajanje” koje radi identično već viđenom, ali zadržava sve retke iz jedne ili obje od tablica na način da retci koji se ne uspiju spojiti ostaju u rezultatu, ali sa NA vrijednostima sa suprotne strane. Razlikujemo “lijevo”, “desno”, i “puno” vanjsko spajanje, u ovisnosti želimo li zadržati sve retke iz lijeve, desne ili obje tablice. Paket dplyr nudi funkcije koje obavljaju sve ove tipove spajanja, one imaju potpis identičan gore viđenoj funkciji a zovu se left_join, right_join i full_join.


Zadatak 11.12 - vanjsko spajanje

##   id prezime pbrBoravista  naziv
## 1  1    Ivic        10000 Zagreb
## 2  2   Peric        31000 Osijek
## 3  3    Anic        10000 Zagreb
## 4 NA    <NA>        21000  Split

11.8 Integracija sa relacijskim bazama podataka

Relacijske baze česti su izvor podataka za analize koje radimo u jeziku R. Jedna od standardnih procedura pripreme podataka za analitički proces je tzv. “izvoženje” (engl. export) podataka iz baze u CSV datoteku, koja se potom učitava u R. Često se prije stvaranja CSV datoteke prvo podaci iz baze moraju prikupiti i pripremiti na adekvatan način, što znači da cjelokupni proces pripreme podataka incijalno zahtjeva rad u SQL-u nad bazom podataka. Ovo je pogotovo bitno ako radimo sa većim skupovima podataka koji bi nepotrebno opteretili stroj nad kojim radimo analize u R-u, a koji bi se mogli znatno učinkovitije i brže mogli obraditi u bazi podataka.

Gore navedeno ne znači nužno da nam za prikupljanje podataka iz baze treba zaseban alat pomoću kojeg ćemo izvoditi SQL upite. R sadrži niz paketa za direktno spajanje i rad sa popularnijim bazama podataka, kao što su npr. MySQL ili PostgreSQL. Uz pomoć ovih paketa možemo unutar R skripte uspostaviti konekciju sa bazom i izvoditi SQL upite, a sve uz pomoć R naredbi pri čemu kao konačni rezultat dobivamo podatkovni okvir spreman za daljnje korake analize. Npr. rad direktno nad MySQL bazom može izgledati ovako:

Vidimo da prvo moramo učitati paket s podrškom za spajanje na odabranu bazu, potom uspostavljamo konekciju sa bazom (parametre nam obično daje administrator same baze), a onda uz pomoć odgovarajućih funkcija postavljamo SQL upite). Iako ovakav pristup nije kompliciran i relativno se često koristi, on neumitno zahtijeva miješanje programskog koda u SQL-u i programskog koda u R-u.

Paket dplyr može ovdje pomoći na način da rastereti programera od potrebe pisanja SQL upita unutar R skripte. Naime, dplyr sadrži integriranu podršku za automatsku konverziju izraza koji koriste dplyr funkcije direktno u SQL upit, a što je transparentno za korisnika. Drugim riječima, do izvjesne mjere možemo na isti način raditi nad relacijskom bazom podataka kao da se radi o podatkovnim okvirima u memoriji računala. Ovdje trebamo biti oprezni jer ne možemo očekivati da će nam sve funkcionalnosti dplyr-a moći biti automatski prevedene u SQL, ali neke standardne pripremne operacije poput spajanje i filtriranja se na ovaj način mogu raditi direktno u bazi, a kroz čisti R programski kod.

Kako bi mogli ovo isprobati u praksi, potrebna nam je relacijska baza podataka. Budući da nije praktično u ovom trenutku instalirati ili pripremati postojeću relacijsku bazu podataka samo za potrebe nekolicine primjera, učinit ćemo ono što radi većina R programera kada želi razvijati programski kod vezan uz relacijske baze, ali izbjeći potrebu za pristupom konkretnoj bazi podataka - koristiti ćemo se tzv. SQLite bazom. Ovo je jednostavna baza koja ne zahtijeva posebnu instalaciju, ne koristi poslužitelj i sve podatke drži u jednoj datoteci.

U sljedećem programskom odsječku učitavamo paket RSQLite, stvaramo novu bazu kreiranjem nove datoteke (ili se spajamo na postojeću ako datoteka ne postoji) te spremamo okvir mtcars u tablicu AUTI što će nam glumiti “podatke u bazi”.

## [1] "AUTI"

Uobičajeni način prikupljanja podataka iz baze jest ugradnja SQL upita direktno u R programski kod i pohrana rezultata u varijablu.

##    mpg    wt
## 1 21.0 2.620
## 2 21.0 2.875
## 3 21.4 3.215
## 4 18.1 3.460
## 5 19.2 3.440
## 6 17.8 3.440
## 7 19.7 2.770

Ako dobro poznajemo SQL, onda ovakav način prikupljanja podataka vjerojatno ne izgleda pretjerano kompleksno i uistinu, veliki broj programera i analitičara rado kombinira SQL i R kako bi optimizirali proces prikupljanja i pripreme podataka. No ako nismo “jaki” u SQL-u, ili jednostavno želimo “čistiji” kod, možemo se koristiti alatima koje nam nudi paket dplyr.

U idućem primjeru ponoviti ćemo dio gore prikazanog postupka (spojiti ćemo se na SQLite bazu nazvanu moja.baza i ispisati podatke iz tablice AUTI), ali ćemo sve izvesti uz pomoć dplyr funkcija.

## [1] "AUTI"
## # A tibble: 7 x 2
##     mpg    wt
##   <dbl> <dbl>
## 1  21    2.62
## 2  21    2.88
## 3  21.4  3.22
## 4  18.1  3.46
## 5  19.2  3.44
## 6  17.8  3.44
## 7  19.7  2.77

Ključno je znati da dplyr neće ništa izvoditi do trenutka kada mi eksplicitno zatražimo tražene podatke, a i tada će ih dohvaćati “u porcijama” kako nam ne bi zagušio memoriju. Upravo zbog toga smo u gornjem primjeru koristili funkciju collect, koja zapravo znači “dohvati cijeli rezultat i pohrani ga u podatkovni okvir” (u našem slučaju mi smo ga jednostavno ispisali na zaslon). Na ovaj način R programer može znatno rasteretiti svoju R okolinu prebacujući odgovornost “teških” proračuna na bazu podataka, a sve uz korištenje istih funkcija koje koristi za upravljanje podatkovnim skupovima pohranjenim u memoriji računala u obliku podatkovnih okvira.


Zadaci za vježbu

U zadacima za vježbu poslužiti ćemo se proširenom podatkovnim skupom mammals sleep dostupnim u vizualizacijskom paketu ggplot2. Učitajte paket ggplot2 te potom prenesite podatkovni okvir msleep u globalnu okolinu uz pomoć funkcije data.

Prije rješavanja učitajte podatkovni skup i upoznajte se s njim uz pomoć uobičajenih funkcija.

  1. Za 10 biljojeda koji najdulje spavaju ispišite ime, koliko dnevno spavaju i prosječnu tjelesnu, težinu u kg. Ispis poredajte po duljini spavanja silazno.

  2. Ispišite prosječno, najdulje i najkraće vrijeme spavanja životinja ovisno o njihovom tipu prehrane.

  3. Podijelite ukupno vrijeme spavanja u 5 razreda jednoliko po ukupnoj duljini dnevnog spavanja. Za svaki razred ispišite ukupan broj životinja koje pripadaju razredu, a potom posebno ukupan broj pripadnika razreda koji nisu biljojedi. Ispis poredajte od razreda životinja koje najmanje spavaju naviše. Stupce nazovite smisleno i pripazite da konačna tablica nema NA vrijednosti.

  4. Sljedeći okvir sadrži šifre statusa očuvanja životinja i njihove opise:

Dodajte okviru msleep stupac conservationDesc koji će sadržavati pripadajuće opise preuzete iz gornjeg okvira. Pripazite da kod proširenja ne izgubite nijedan redak iz okvira msleep.


Creative Commons License
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/