Do sada smo uglavnom u radu sa bazama podataka predstavljali podatke grafički, manipulisali njima i izvodili zaključke na osnovu gotovih podataka koji se nalaze u bazi. Međutim, često nam se javlja potreba da nekako transformišemo podatke, i sebi olakšamo rad sa njima. Na primer, želimo da izmenimo imena promenljivih, ili da promenimo redosled opservacija kako bi podaci bili lakši za rad. U tome nam može pomoći dplyr paket.

dplyr

Da bismo ilustrovali ključne ideje u radu sa dplyr paketom, koristićemo baze iz paketa nycflights13, a takođe, koristićemo i paket ggplot2 da bismo prikazali podatke i bolje razumeli šta radimo.

Koristićemo bazu flights da pokažemo kako možemo manipulisati podacima koristeći paket dplyr. Ova baza sadrži podatke o svih 336.776 letova iz Njujorka u toku 2013.godine.

nycflights13::flights
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Primećujemo da je malo drugačiji prikaz podataka od onog na koji smo navikli u dosadašnjem radu sa bazama podataka: prikazano je samo prvih nekoliko redova i kolona. (Da bismo videli celu bazu, mozemo pokrenuti View(flights), koji će prikazati bazu u zasebnom prozoru). Prikazuje se drugačije jer je u pitanju takozvani tibl. Tiblovi su takođe baze podataka, ali podešene na malo drugačiji način. No, za sada ne moramo naročito voditi racuna o tim razlikama.

Takođe, možemo primetiti skraćenice od tri (ili četiri) slova, ispod naziva svake kolone. One opisuju tip svake od promenljivih:

Postoje još tri tipa podataka koji se ne koriste u ovoj bazi, ali se koriste često:

Osnovne funkcije

Za početak, naučićemo da koristimo pet ključnih dplyr funkcija (tzv. dplyr glagola), koje nam omogućavaju da rešimo veliku većinu naših problema u manipulaciji podacima.

Sve ove funkcije rade na sličan način:

  1. Prvi argument je baza sa kojom radimo.

  2. Dodatnim argumentima opisujemo šta želimo da radimo sa bazom, koristeći imena promenljivih tj. kolona (bez $).

  3. Rezultat je nova baza podataka.

Zajedno, ova svojstva nam omogućavaju povezivanje više jednostavnih koraka kako bi se postigao složen rezultat.

Takođe, često nam mogu biti korisne i funkcije sample_n() i sample_frac(), koje služe za uzimanje slučajnog uzorka redova: sample_n() koristimo kada zelimo određeni broj redova, a sample_frac() kada zelimo određeni procenat.

sample_n(flights, 10)
## # A tibble: 10 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     4     3     1652           1700        -8     2046
##  2  2013    10     7      653            652         1      928
##  3  2013     6     7      954            959        -5     1204
##  4  2013     7     8     1732           1700        32     2047
##  5  2013     8    21      958           1000        -2     1122
##  6  2013     6     3     1159           1200        -1     1302
##  7  2013    12     6      625            600        25      745
##  8  2013     9    30     1852           1900        -8     2100
##  9  2013     6    19     1404           1415       -11     1657
## 10  2013     9    29      809            815        -6     1007
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
sample_frac(flights, 0.01)
## # A tibble: 3,368 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     7    24     1457           1500        -3     1602
##  2  2013     1     3     2040           2029        11     2226
##  3  2013     9     1     1627           1629        -2     1757
##  4  2013     1    10      855            900        -5     1213
##  5  2013    10    14     1027           1030        -3     1343
##  6  2013    12     5     1924           1830        54     2213
##  7  2013     4    25     1221           1135        46     1509
##  8  2013     9     4      553            600        -7      650
##  9  2013     8    24     1753           1800        -7     1850
## 10  2013    12    26     2053           2105       -12     2151
## # ... with 3,358 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Možemo koristiti replace = TRUE da vadimo Butstrep uzorak.

Ovih pet funkcija nam čine “glagole” jezika za manipilaciju podacima. Na najosnovnijem nivou, možemo da uređujemo jednu bazu podataka na pet korisnih načina: promenimo redosled redova (arrange()), izaberemo opservacije i promenljive koje nas zanimaju (filter() i select()), dodamo nove promenljive koje su funkcije postojecih (mutate()), i računamo neke sumarne vrednosti (summarise()). Ostatak jezika dolazi od primene ovih pet funkcija na različite načine i podatke. Na primer, diskutovaćemo kako ove funkcije rade na grupisanim podacima.

1. Filtriranje redova pomocu funkcije filter()

Funkcija filter() omogućava nam da pravimo podskupove opservacija na osnovu nekih uslova. Prvi argument je ime baze sa kojom radimo. Drugi i naredni argumenti odnose se na promenljive unutar baze podataka: zadajemo izraze i biramo redove gde je vrednost izraza TRUE. Na primer, želimo da izaberemo sve letove 1.januara:

filter(flights, month == 1, day == 1)
## # A tibble: 842 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 832 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Ovo je otprilike ekvivalentno sledećem baznom kodu:

flights[flights$month == 1 & flights$day == 1, ]

Kada pozovemo funkciju filter, dplyr izvrši operaciju filtriranja i vraća novu bazu podataka. Kako dplyr funkcije nikada ne menjaju početnu bazu, ako želimo da sačuvamo rezultat, moramo da koristimo operator dodele:

jan1 = filter(flights, month == 1, day == 1)

Ako želimo i da štampamo i da sačuvamo rezultat, onda smestimo u zagrade.

(jan1 = filter(flights, month == 1, day == 1))
## # A tibble: 842 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 832 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Da bismo efikasno koristili filtriranje, moramo pravilno da odaberemo opservacije koje želimo koristeći operatore poređenja: >,>=,<,<=,!= i ==.

Česta početnička greška u radu u R-u je korišćenje = umesto ==, kada testiramo jednakost. Kada se to desi, dobićemo informaciju o grešci:

filter(flights, month = 1)
#> Error: `month` (`month = 1`) must not be named, do you need `==`?

Postoji još jedan uobičajen problem koji se javlja kada se koristi ==. Ovi rezultati vas mogu iznenaditi!

sqrt(2) ^ 2 == 2
## [1] FALSE
1 / 49 * 49 == 1
## [1] FALSE

Ovo se dešava jer racunari koriste preciznost na konačno mnogo decimala (očigledno, ne mogu da sačuvaju beskonačno mnogo cifara), dakle svaki broj koji vidimo predstavlja aproksimaciju. Umesto oslanjanja na ==, mozemo koristiti near().

near(sqrt(2) ^ 2,  2)
## [1] TRUE
near(1 / 49 * 49, 1)
## [1] TRUE

Ako želimo da izdvojimo redove koji zadovoljavaju više uslova, koristićemo operator &, tako da nam svi uslovi moraju biti ispunjeni da bi red bio uključen u izlaz. Slično, mozemo praviti i druge kombinacije uslova, koristeći razne kombinacije sa logičkim operatorima &(i), |(ili) i !=(negacija).

Sledeći kod pronalazi sve letove koji su bili u novembru ili decembru:

filter(flights, month == 11 | month == 12)
## # A tibble: 55,403 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013    11     1        5           2359         6      352
##  2  2013    11     1       35           2250       105      123
##  3  2013    11     1      455            500        -5      641
##  4  2013    11     1      539            545        -6      856
##  5  2013    11     1      542            545        -3      831
##  6  2013    11     1      549            600       -11      912
##  7  2013    11     1      550            600       -10      705
##  8  2013    11     1      554            600        -6      659
##  9  2013    11     1      554            600        -6      826
## 10  2013    11     1      554            600        -6      749
## # ... with 55,393 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Moramo voditi računa o redosledu operacija! Ne funkcioniše kao govorni jezik! Ne možemo pisati filter(flights, month == 11 | 12), što bi se bukvalno moglo čitati kao: “pronađi sve letove koji su bili u novembru ili decembru”, pa bi nam možda intuitivno palo na pamet. Umesto da izdvoji ono sto treba, pronaći ce sve mesece koji su jednaki sa 11|12, izraza koji ima vrednost TRUE.

Korisna prečica da izbegnemo ovakav problem je korišćenje x%in%y. Ovaj izraz za svaku vrednost iz x gleda da li se nalazi u vektoru y, i ako je tako, na odgovarajućoj poziciji u rezultujućem vektoru se nalazi TRUE, a inače FALSE. Dakle, možemo pisati:

nov_dec = filter(flights, month %in% c(11, 12))

Nekad možemo da pojednostavimo komplikovano filtriranje, ako koristimo De Morganov zakon: !(x & y) je isto što i !x | !y, a !(x | y), je isto što i !x & !y. Na primer, ako želimo da pronađemo letove koji nisu kasnili (pri poletanju i sletanju) za više od dva sata, možemo da koristimo bilo koji od sledeća dva filtera:

filter(flights, !(arr_delay > 120 | dep_delay > 120))
## # A tibble: 316,050 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 316,040 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
filter(flights, arr_delay <= 120, dep_delay <= 120)
## # A tibble: 316,050 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 316,040 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Pored & i |, R ima i && i ||. Međutim, njih ne treba koristiti ovde.

Uvek kada nam se javi potreba za složenim, višetrukim izrazima u funkciji filter(), trebalo bi da razmislimo o pravljenju dodatnih promenljivih. To mnogo olakšava proveru rada, pa ćemo uskoro naučiti i da kreiramo nove promenljive.

NA vrednosti

Jedna od važnih stvari u R-u, koja može da učini poređenje nezgodnim su NA vrednosti. NA predstavlja nedostajuću vrednost, a te nedostajuće vrednosti su “zarazne”: rezultat skoro svake operacije koja uključuje nepoznatu vrednost će i sam biti nepoznata vrednost.

NA > 5
## [1] NA
10 == NA
## [1] NA
NA + 10
## [1] NA
NA/2
## [1] NA

Možda najviše zbunjuje sledeći rezultat:

NA == NA
## [1] NA

Kako najlakše da shvatimo zasto ovo važi:

# Ana ima x godina. Mi ne znamo koliko je Ana stara.
x = NA

# Marija ima y godina. Ne znamo ni koliko je Marija stara.
y = NA

# Da li one imaju isto godina?
x == y
#> [1] NA
# Ne znamo!

Ako želimo da utvrdimo da li nedostaje vrednost, koristimo is.na():

is.na(x)
#> [1] TRUE

Rezultat filtriranja korišćenjem funkcije filter() uključuje samo redove gde je uslov ispunjen (tj. TRUE), a isključuje i FALSE i NA vrednosti. Ako želimo da sačuvamo NA vrednosti, moramo to eksplicitno navesti.

df = tibble(x = c(1, NA, 3))
filter(df, x > 1)
## # A tibble: 1 x 1
##       x
##   <dbl>
## 1     3
filter(df, is.na(x) | x > 1)
## # A tibble: 2 x 1
##       x
##   <dbl>
## 1    NA
## 2     3

2. Promena redosleda redova pomoću funkcije arrange()

Funkcija arrange() radi slično kao filter(), s tim što se umesto filtriranja i izbora redova menja njihov redosled. Kao argumente uzima bazu podataka i skup imena kolona (ili složenijih izraza) po kojima se vrši sortiranje. Ako navedemo više od jednog naziva kolona, sortira se redom - po prvoj navedenoj koloni, pa drugoj, trećoj, itd. Pogledajmo ilustraciju na manjoj bazi:

data("Orange")
attach(Orange)
t = tibble(Tree, age, circumference, u = sample(1:20, 35, rep = TRUE))
arrange(t, u, age, circumference)
## # A tibble: 35 x 4
##    Tree    age circumference     u
##    <ord> <dbl>         <dbl> <int>
##  1 5      1582           177     2
##  2 4      1582           214     2
##  3 2       118            33     3
##  4 5       484            49     3
##  5 3      1231           115     3
##  6 5      1231           142     3
##  7 2       664           111     4
##  8 3      1004           108     4
##  9 2      1004           156     4
## 10 5       118            30     7
## # ... with 25 more rows

Sortiramo po godini, pa po mesecu i na kraju po danu.

arrange(flights, year, month, day)
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Koristimo desc() da uredimo u opadajućem redosledu po koloni dep_delay:

arrange(flights, desc(dep_delay))
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     9      641            900      1301     1242
##  2  2013     6    15     1432           1935      1137     1607
##  3  2013     1    10     1121           1635      1126     1239
##  4  2013     9    20     1139           1845      1014     1457
##  5  2013     7    22      845           1600      1005     1044
##  6  2013     4    10     1100           1900       960     1342
##  7  2013     3    17     2321            810       911      135
##  8  2013     6    27      959           1900       899     1236
##  9  2013     7    22     2257            759       898      121
## 10  2013    12     5      756           1700       896     1058
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

NA vrednosti se uvek nalaze na kraju, bilo da sortiramo rastuće ili opadajuće:

df = tibble(x = c(5, 2, NA))
arrange(df, x)
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     2
## 2     5
## 3    NA
arrange(df, desc(x))
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     5
## 2     2
## 3    NA

3. Selektovanje kolona pomoću funkcije select()

Nije neuobičajeno da baze podataka imaju stotine, ili čak hiljade promenljivih. Međutim, nama je najčesće od značaja samo nekoliko njih. Funkcija select() nam omogućava da se fokusiramo na podskup promenljivih koji nam je potreban, tako što joj prosledimo imena kolona (ili neke složenije izraze) koje nas interesuju.
Funkcija select() nije naročito korisna kod baze flights, jer imamo samo 19 promenljivih, ali se i dalje moze videti opšta ideja:

# Selektujemo kolone navodeci njihova imena
select(flights, year, month, day)
## # A tibble: 336,776 x 3
##     year month   day
##    <int> <int> <int>
##  1  2013     1     1
##  2  2013     1     1
##  3  2013     1     1
##  4  2013     1     1
##  5  2013     1     1
##  6  2013     1     1
##  7  2013     1     1
##  8  2013     1     1
##  9  2013     1     1
## 10  2013     1     1
## # ... with 336,766 more rows
# Selektujemo sve kolone izmedju year i day (ukljucujuci i njih)
select(flights, year:day)
## # A tibble: 336,776 x 3
##     year month   day
##    <int> <int> <int>
##  1  2013     1     1
##  2  2013     1     1
##  3  2013     1     1
##  4  2013     1     1
##  5  2013     1     1
##  6  2013     1     1
##  7  2013     1     1
##  8  2013     1     1
##  9  2013     1     1
## 10  2013     1     1
## # ... with 336,766 more rows
#Selektujemo sve kolone osim onih izmedju year i day (iskljucujuci i njih)
select(flights, -(year:day))
## # A tibble: 336,776 x 16
##    dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay
##       <int>          <int>     <dbl>    <int>          <int>     <dbl>
##  1      517            515         2      830            819        11
##  2      533            529         4      850            830        20
##  3      542            540         2      923            850        33
##  4      544            545        -1     1004           1022       -18
##  5      554            600        -6      812            837       -25
##  6      554            558        -4      740            728        12
##  7      555            600        -5      913            854        19
##  8      557            600        -3      709            723       -14
##  9      557            600        -3      838            846        -8
## 10      558            600        -2      753            745         8
## # ... with 336,766 more rows, and 10 more variables: carrier <chr>,
## #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## #   distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>

Postoji nekoliko pomoćnih funkcija koje se mogu koristiti u okviru funkcije select()(pogledajte ?select za više detalja), na primer: * starts_with("abc"): pronalazi imena kolona koja počinju sa “abc”.

Funkcija select() se može koristiti i za promenu imena kolona, ali je retko korisna, jer “ispušta” sve promenljive koje nisu eksplicitno navedene. Umesto nje, bolje je koristiti funkciju rename(), varijantu funkcije select(), koja čuva sve promenljive koje nisu eksplicitno navedene.

select(flights, deptime = dep_time)
## # A tibble: 336,776 x 1
##    deptime
##      <int>
##  1     517
##  2     533
##  3     542
##  4     544
##  5     554
##  6     554
##  7     555
##  8     557
##  9     557
## 10     558
## # ... with 336,766 more rows
rename(flights, deptime = dep_time)
## # A tibble: 336,776 x 19
##     year month   day deptime sched_dep_time dep_delay arr_time
##    <int> <int> <int>   <int>          <int>     <dbl>    <int>
##  1  2013     1     1     517            515         2      830
##  2  2013     1     1     533            529         4      850
##  3  2013     1     1     542            540         2      923
##  4  2013     1     1     544            545        -1     1004
##  5  2013     1     1     554            600        -6      812
##  6  2013     1     1     554            558        -4      740
##  7  2013     1     1     555            600        -5      913
##  8  2013     1     1     557            600        -3      709
##  9  2013     1     1     557            600        -3      838
## 10  2013     1     1     558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Izvorna baza se ne menja kada koristimo bilo koji glagol, pa ni select() i rename().

flights
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Druga opcija je da koristimo select() u kombinaciji sa everything(). Ovo je naročito korisno kada imamo nekoliko promenljivih koje želimo da premestimo na početak.

select(flights, time_hour, air_time, everything())
## # A tibble: 336,776 x 19
##    time_hour           air_time  year month   day dep_time sched_dep_time
##    <dttm>                 <dbl> <int> <int> <int>    <int>          <int>
##  1 2013-01-01 05:00:00      227  2013     1     1      517            515
##  2 2013-01-01 05:00:00      227  2013     1     1      533            529
##  3 2013-01-01 05:00:00      160  2013     1     1      542            540
##  4 2013-01-01 05:00:00      183  2013     1     1      544            545
##  5 2013-01-01 06:00:00      116  2013     1     1      554            600
##  6 2013-01-01 05:00:00      150  2013     1     1      554            558
##  7 2013-01-01 06:00:00      158  2013     1     1      555            600
##  8 2013-01-01 06:00:00       53  2013     1     1      557            600
##  9 2013-01-01 06:00:00      140  2013     1     1      557            600
## 10 2013-01-01 06:00:00      138  2013     1     1      558            600
## # ... with 336,766 more rows, and 12 more variables: dep_delay <dbl>,
## #   arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, distance <dbl>,
## #   hour <dbl>, minute <dbl>

4. Dodavanje novih promenljivih koristeći mutate()

Pored odabira postojećih kolona, često je korisno dodati nove kolone koje su funkcije postojećih. Za to nam služi funkcija mutate().

Funkcija mutate() uvek dodaje novu kolonu na kraj, pa cemo početi kreiranjem baze sa manjim brojem promenjivih kako bismo videli nove promenljive (Kada koristimo RStudio, najlakši nacin da vidimo sve kolone je View).

flights_sml = select(flights, 
  year:day, 
  ends_with("delay"), 
  distance, 
  air_time
)
#dodajemo nove kolone gain i speed
mutate(flights_sml,
  gain = dep_delay - arr_delay,
  speed = distance / air_time * 60
)
## # A tibble: 336,776 x 9
##     year month   day dep_delay arr_delay distance air_time  gain speed
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl> <dbl> <dbl>
##  1  2013     1     1         2        11     1400      227    -9  370.
##  2  2013     1     1         4        20     1416      227   -16  374.
##  3  2013     1     1         2        33     1089      160   -31  408.
##  4  2013     1     1        -1       -18     1576      183    17  517.
##  5  2013     1     1        -6       -25      762      116    19  394.
##  6  2013     1     1        -4        12      719      150   -16  288.
##  7  2013     1     1        -5        19     1065      158   -24  404.
##  8  2013     1     1        -3       -14      229       53    11  259.
##  9  2013     1     1        -3        -8      944      140     5  405.
## 10  2013     1     1        -2         8      733      138   -10  319.
## # ... with 336,766 more rows

Takođe, možemo dodavati promenljive koje su funkcije onih koje smo upravo kreirali, u istom pozivu funkcije mutate().

mutate(flights_sml,
  gain = dep_delay - arr_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 10
##     year month   day dep_delay arr_delay distance air_time  gain hours
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl> <dbl> <dbl>
##  1  2013     1     1         2        11     1400      227    -9 3.78 
##  2  2013     1     1         4        20     1416      227   -16 3.78 
##  3  2013     1     1         2        33     1089      160   -31 2.67 
##  4  2013     1     1        -1       -18     1576      183    17 3.05 
##  5  2013     1     1        -6       -25      762      116    19 1.93 
##  6  2013     1     1        -4        12      719      150   -16 2.5  
##  7  2013     1     1        -5        19     1065      158   -24 2.63 
##  8  2013     1     1        -3       -14      229       53    11 0.883
##  9  2013     1     1        -3        -8      944      140     5 2.33 
## 10  2013     1     1        -2         8      733      138   -10 2.3  
## # ... with 336,766 more rows, and 1 more variable: gain_per_hour <dbl>

Ako želimo da zadržimo samo nove promenljive, koristimo funkciju transmute():

transmute(flights,
  gain = dep_delay - arr_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 3
##     gain hours gain_per_hour
##    <dbl> <dbl>         <dbl>
##  1    -9 3.78          -2.38
##  2   -16 3.78          -4.23
##  3   -31 2.67         -11.6 
##  4    17 3.05           5.57
##  5    19 1.93           9.83
##  6   -16 2.5           -6.4 
##  7   -24 2.63          -9.11
##  8    11 0.883         12.5 
##  9     5 2.33           2.14
## 10   -10 2.3           -4.35
## # ... with 336,766 more rows

Korisne funkcije za kreiranje promenljivih

Postoje mnoge funkcije za kreiranje novih promenljivih koje se mogu kombinovati sa mutate(). Navedimo neke značajne funkcije koje se često koriste:

  • Aritmeticki operatori: (+,-,*,/,^)

  • Modularna aritmetika: %/%(celobrojni deo količnika) i %%(ostatak), gde je x == y * (x %/% y) + (x %% y). Modularna aritmetika je zgodan alat jer omogućava da cele brojeve razbijemo na delove. Npr., u bazi podataka flights, možemo da izdvojimo sate i minute iz dep_time:

transmute(flights, dep_time, hour = dep_time %/% 100, minute = dep_time %% 100)
## # A tibble: 336,776 x 3
##    dep_time  hour minute
##       <int> <dbl>  <dbl>
##  1      517     5     17
##  2      533     5     33
##  3      542     5     42
##  4      544     5     44
##  5      554     5     54
##  6      554     5     54
##  7      555     5     55
##  8      557     5     57
##  9      557     5     57
## 10      558     5     58
## # ... with 336,766 more rows
  • Logaritmi: log(), log2(), log10(). Logaritmi su jako korisna alatka jer pretvaraju multiplikativne odnose u aditivne. Preporučuje se korišćenje log2(), jer je lako za interpretaciju: razlika od 1 na logaritamskoj skali odgovara udvostručenju na originalnoj skali, a razlika od -1 odgovara prepolovljenju.

  • Nalaženje sledbenika i prethodnika: lead() i lag(). One su najčešće koriste u kombinaciji sa group_by(), o kojoj ćemo kasnije.

(x = 1:10)
#>  [1]  1  2  3  4  5  6  7  8  9 10
lead(x)
#>  [1]  2  3  4  5  6  7  8  9 10 NA
lag(x)
#>  [1] NA  1  2  3  4  5  6  7  8  9
  • Kumulativne funkcije: cumsum(), cummax(), cummin(), cumprod(), a paket dplyr sadrži i cummean() za računanje kumulativnih srednjih vrednosti.
x
#>  [1]  1  2  3  4  5  6  7  8  9 10
cumsum(x)
#>  [1]  1  3  6 10 15 21 28 36 45 55
cummean(x)
#>  [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5
  • Logički operatori: (<, <=, >, >=, !=, ==) Ako radimo kompleksan niz operacija često je dobra ideja da čuvamo privremene vrednosti u novim promenljivim, tako da možemo lako proveriti da li svaki korak radi kako treba.

  • Rangovi: Postoje razne funkcije za rangiranje, ali najčešće se koristi min_rank(), koja malim vrednostima pridružuje male rangove, a možemo koristiti desc(x), da najvećim vrednostima damo najmanje rangove.

y = c(1, 2, 2, NA, 3, 4)
min_rank(y)
#> [1]  1  2  2 NA  4  5
min_rank(desc(y))
#> [1]  5  3  3 NA  2  1

Postoje razne druge varijante rangiranja, npr. row_number(), dense_rank(), percent_rank(), cume_dist(), itd.

row_number(y)
#> [1]  1  2  3 NA  4  5
dense_rank(y)
#> [1]  1  2  2 NA  3  4
percent_rank(y)
#> [1] 0.00 0.25 0.25   NA 0.75 1.00
cume_dist(y)
#> [1] 0.2 0.6 0.6  NA 0.8 1.0

5. Računanje sumarnih vrednosti koriscenjem summarise()

Polednja važna funkcija koju obrađujemo je summarise(). Ona sažima čitavu bazu u jedan red - nalazi sumarne vrednosti za navedene kolone, npr. srednju vrednost, sumu, itd.

Na primer, da bismo izračunali prosečno kašnjenje pri poletanju, primenjujemo funkciju mean() na kolonu dep_delay i rezultat (što je samo jedna vrednost) smeštamo u delay.

summarise(flights,
  delay = mean(dep_delay, na.rm = TRUE)
)
## # A tibble: 1 x 1
##   delay
##   <dbl>
## 1  12.6

Vratićemo se na to šta na.rm = TRUE znači uskoro.

Grupisanje operacija pomoću group_by()

Funkcija summarise() nije naročito korisna ukoliko je ne kombinujemo sa group_by(). Ovo menja jedinicu proučavanja sa čitave baze podataka na pojedinačne skupine. Dakle, kada koristimo dplyr glagole na grupisanoj bazi podataka, oni će automatski biti primenjeni “po grupama”.

Funkcija group_by() je jedna od najvažnijih funkcija u paketu dplyr (pored već navednih pet glagola). Kao što smo napomenuli, ona se odnosi na koncept: “razdvoji-primeni-sastavi”. Mi bukvalno želimo da izdelimo bazu na grupe, primenimo funkciju koju želimo na pojedinačne podatke, a zatim kombinujemo dobijene rezultate za izlaz.

Na primer, ako primenimo isti kod na bazu grupisanu po datumu, dobijemo prosečno kašnjenje po datumu:

by_day = group_by(flights, year, month, day)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE)) #za svaki dan dobijemo prosecno kasnjenje
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day delay
##    <int> <int> <int> <dbl>
##  1  2013     1     1 11.5 
##  2  2013     1     2 13.9 
##  3  2013     1     3 11.0 
##  4  2013     1     4  8.95
##  5  2013     1     5  5.73
##  6  2013     1     6  7.15
##  7  2013     1     7  5.42
##  8  2013     1     8  2.55
##  9  2013     1     9  2.28
## 10  2013     1    10  2.84
## # ... with 355 more rows

Zajedno, group_by() i summarise() obezbeđuju jedan od alata koji se najčešće koristi u radu sa dplyr paketom - izdvajanje sumarnih vrednosti po grupama.

Operator cevi (the pipe)

Pre nego što nastavimo dalje, upoznaćemo se sa operatorom cevi: %>% (dpylr ga uvozi iz drugog paketa magrittr). Ovaj operator nam dozvoljava da izlaz jedne funkcije koristimo kao ulaz druge funkcije. Umesto ugnježdenih funkcija (koje se čitaju iznutra prema van), ideja operatora cevi je čitanje s leva na desno.

Na primer:

head(select(flights, month, dep_time))
## # A tibble: 6 x 2
##   month dep_time
##   <int>    <int>
## 1     1      517
## 2     1      533
## 3     1      542
## 4     1      544
## 5     1      554
## 6     1      554

Koristeći operator cevi, možemo prikazati na sledeći način:

flights %>% 
  select(month, dep_time) %>%
  head
## # A tibble: 6 x 2
##   month dep_time
##   <int>    <int>
## 1     1      517
## 2     1      533
## 3     1      542
## 4     1      544
## 5     1      554
## 6     1      554

Operator cevi nam je jako koristan kada želimo da kombinujemo puno funkcija, radi bolje čitljivosti koda i lakšeg uočavanja greške.

“Iza scene”, x %>% f(y) se pretvara u f(x,y), a x %>% f(y) %>% g(z) se pretvara u g(f(x, y), z) i tako dalje. Dakle, možemo koristiti operator cevi da pišemo naredbe za izvršenje više operacija tako da možemo da čitamo sleva na desno, od vrha do dna. Od sada ćemo često korsititi operator cevi jer značajno poboljšava čitljivost koda.

Kombinovanje više operacija sa cevima

Recimo da želimo da ispitamo odnos između prosečne udaljenosti (distance) i prosečnog kašnjenja (arr_delay) za svaku od lokacija. Koristeći ono što znamo o dplyr funkcijama možemo napisati sledeći kod:

by_dest = group_by(flights, dest)
delays = summarise(by_dest,
  count = n(),
  dist = mean(distance, na.rm = TRUE),
  delay = mean(arr_delay, na.rm = TRUE)
)
delays = filter(delays, count > 20, dest != "HNL")

# Izgleda da se kasnjenja povecavaju do udaljenosti ~750 milja, a zatim se smanjuju. 
# Mozda se, sto je let duzi, povecava mogucnost da se nadoknadi kasnjenje u vazduhu?
ggplot(data = delays, mapping = aes(x = dist, y = delay)) +
  geom_point(aes(size = count), alpha = 1/3) +
  geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Priprema podataka se odvija kroz tri koraka:

  1. Grupišemo letove po destinaciji.

  2. Koristimo summarise() da izračunamo prosečnu udaljenost (dist = mean(distance, na.rm = TRUE)), prosečno kašnjenje(delay = mean(arr_delay, na.rm = TRUE)) i broj letova (count = n()) za svaku od destinacija.

  3. Filtriramo tako da uklonimo destinacije sa malo letova i aerodrom u Honoluluu (Havaji), koji je skoro duplo udaljen od sledećeg najbližeg aerodroma.

Ovo može biti malo dosadno za pisanje, jer moramo dati ime svakoj privremenoj bazi, čak iako nam ona nije bitna. Imenovanje stvari je teško i dosadno, tako da to dosta usporava našu analizu.

Postoji drugi način da rešimo isti problem koristeći operator cevi, %>%:

delays = flights %>% 
  group_by(dest) %>% 
  summarise(
    count = n(),
    dist = mean(distance, na.rm = TRUE),
    delay = mean(arr_delay, na.rm = TRUE)
  ) %>% 
  filter(count > 20, dest != "HNL")

Ovde se fokus stavlja na transformacije, a ne na ono što se transformiše, sto čini kod lakšim za čitanje. Možemo ga čitati kao niz naredbi: grupisanje, računanje sumarnih vrednosti, i na kraju filtriranje. Dakle, dobar način da se izgovori %>% pri čitanju koda je “tada”.

Jedina smetnja u radu sa cevima nam je ggplot2: napisan je pre otkrivanja cevi. Postoji unapređenje koje koristi cevi, ggvis, međutim, jos uvek je u razvoju i podložno promenama, pa je ipak bolje koristiti ggplot2.

NA vrednosti

Ranije smo koristili argument na.rm. Međutim, šta se dešava ako ga izostavimo?

flights %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day  mean
##    <int> <int> <int> <dbl>
##  1  2013     1     1    NA
##  2  2013     1     2    NA
##  3  2013     1     3    NA
##  4  2013     1     4    NA
##  5  2013     1     5    NA
##  6  2013     1     6    NA
##  7  2013     1     7    NA
##  8  2013     1     8    NA
##  9  2013     1     9    NA
## 10  2013     1    10    NA
## # ... with 355 more rows

Dobijamo mnogo NA vrednosti! To je zato što ove funkcije prate uobičajeno pravilo NA vrednosti: ako postoji bilo kakva NA vrednost na ulazu, rezultat će biti NA vrednost. Srećom, sve ove funkcije imaju na.rm argument koji uklanja NA vrednosti pre izračunavanja:

flights %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay, na.rm = TRUE))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day  mean
##    <int> <int> <int> <dbl>
##  1  2013     1     1 11.5 
##  2  2013     1     2 13.9 
##  3  2013     1     3 11.0 
##  4  2013     1     4  8.95
##  5  2013     1     5  5.73
##  6  2013     1     6  7.15
##  7  2013     1     7  5.42
##  8  2013     1     8  2.55
##  9  2013     1     9  2.28
## 10  2013     1    10  2.84
## # ... with 355 more rows

U ovom slučaju, kada NA vrednosti predstavljaju otkazane letove, možemo se takođe pozabaviti problemom brisanja otkazanih letova. Sačuvaćemo ovu bazu za kasnije kako bismo mogli ponovo da je koristimo u narednih nekoliko primera.

not_cancelled = flights %>% 
  filter(!is.na(dep_delay), !is.na(arr_delay))

not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day  mean
##    <int> <int> <int> <dbl>
##  1  2013     1     1 11.4 
##  2  2013     1     2 13.7 
##  3  2013     1     3 10.9 
##  4  2013     1     4  8.97
##  5  2013     1     5  5.73
##  6  2013     1     6  7.15
##  7  2013     1     7  5.42
##  8  2013     1     8  2.56
##  9  2013     1     9  2.30
## 10  2013     1    10  2.84
## # ... with 355 more rows

Prebrojavanja

Kad god računamo neke sumarne karakteristike, dobra je ideja da uključimo ukupan broj vrednosti (n()), ili broj vrednosti koje nisu NA (sum(!is.na(x))) u grupi. Na taj način možemo proveriti da li donosimo zaključke na osnovu premale količine podataka. Na primer, pogledajmo avione (identifikovane njihovim repnim brojem (tailnum)), koji imaju najveća prosečna kašnjenja:

delays = not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay)
  )

ggplot(data = delays, mapping = aes(x = delay)) + 
  geom_freqpoly(binwidth = 10)

Izgleda da ima nekih aviona koji imaju prosečno kašnjenje više od 5 sati (300 minuta)!

Možemo steći bolji uvid ako nacrtamo grafik broja letova u odnosu na prosečno kašnjenje.

delays = not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay, na.rm = TRUE),
    n = n()
  )

ggplot(data = delays, mapping = aes(x = n, y = delay)) + 
  geom_point(alpha = 1/10)

Nije iznenađujuće da postoji mnogo veća varijacija u prosečnom kašnjenju kada postoji samo nekoliko letova. Sa ovog grafika se jasno vidi da se varijacija smanjuje kako se veličina uzorka tj. grupe povećava.

Kad gledamo ovu vrstu grafika, često je korisno da izbacimo grupe sa najmanjim brojem opservacija, tako da možemo bolje da uočimo šablon, a manje ekstremnu varijaciju u manjim grupama. To je ono što sledeći kod radi. Vidimo i kako da “uglavimo” ggplot2 u dplyr tokove. Malo je nezgodno što moramo da se prebacimo sa %>% na +, ali jednom kad se srodimo s tim, postaje jako korisno.

delays %>% 
  filter(n > 25) %>% 
  ggplot(mapping = aes(x = n, y = delay)) + 
    geom_point(alpha = 1/10)

Pogledajmo još jednu zanimljivu varijantu ovog problema. Posmatrajmo sada odnos veštine udarca u bejzbolu i broja pokušaja. Ovde ćemo koristiti podatke iz Lahman paketa da izračunamo veštinu udarca (broj pogodaka / broj pokušaja) svakog bejzbol igrača.

#konvertujemo u tibl, da bi se lepo prikazivalo

batting = as_tibble(Lahman::Batting)

batters = batting %>% 
  group_by(playerID) %>% 
  summarise(
    vestina = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
    br_sansi = sum(AB, na.rm = TRUE)
  )

batters %>% 
  filter(br_sansi > 100) %>% 
  ggplot(mapping = aes(x = br_sansi, y = vestina)) +
    geom_point() + 
    geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

Kada nacrtamo veštinu udarca (merenu prosekom pogodaka, vestina) u odnosu na broj šansi za udaranje lopte (mereno u br_sansi), vidimo dva šablona:

  1. Što imamo više tačaka (podataka), varijacija je manja.

  2. Postoji pozitivna korelacija između veštine (vestina) i šansi za udaranje lopte (br_sansi). Ovo važi jer timovi kontrolišu ko će igrati, pa će očigledno odabrati najbolje igrače.

Ovo nam je važno za upoređivanje. Ako bismo samo naivno sortirali sa desc(vestina), vidimo da su igrači sa najboljim prosekom udaraca očigledno imali sreće, a ne bili vešti.

batters %>% 
  arrange(desc(vestina))
## # A tibble: 18,915 x 3
##    playerID  vestina br_sansi
##    <chr>       <dbl>    <int>
##  1 abramge01       1        1
##  2 banisje01       1        1
##  3 bartocl01       1        1
##  4 bassdo01        1        1
##  5 berrijo01       1        1
##  6 birasst01       1        2
##  7 bruneju01       1        1
##  8 burnscb01       1        1
##  9 cammaer01       1        1
## 10 campsh01        1        1
## # ... with 18,905 more rows

Korisne funkcije za summarise()

Čak i samo korišćenje prebrojavanja, sumiranja i sredina može daleko da nas odvede, ali R daje i mnoge druge korisne funkcije:

  • Mere položaja: Koristili smo mean(x), ali i median(x) je takođe veoma korisna. (Prisetimo se - mean(x) je suma svih elemenata podeljena brojem elemenata, a median(x) daje vrednost od koje je 50% vrednosti iz x manje, a 50% veće).
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(
    avg_delay1 = mean(arr_delay),
    avg_delay2 = mean(arr_delay[arr_delay > 0]) # prosecno pozitivno kasnjenje
  )
## # A tibble: 365 x 5
## # Groups:   year, month [?]
##     year month   day avg_delay1 avg_delay2
##    <int> <int> <int>      <dbl>      <dbl>
##  1  2013     1     1     12.7         32.5
##  2  2013     1     2     12.7         32.0
##  3  2013     1     3      5.73        27.7
##  4  2013     1     4     -1.93        28.3
##  5  2013     1     5     -1.53        22.6
##  6  2013     1     6      4.24        24.4
##  7  2013     1     7     -4.95        27.8
##  8  2013     1     8     -3.23        20.8
##  9  2013     1     9     -0.264       25.6
## 10  2013     1    10     -5.90        27.3
## # ... with 355 more rows
  • Mere raspršenosti: sd(x), IQR(x). Standardna devijacija sd(x) je standardna mera raspršenosti. Interkvartilno rastojanje IQR(x) je robusnija mera, koja može biti korisna ako imamo autlajere.
not_cancelled %>% 
  group_by(dest) %>% 
  summarise(distance_sd = sd(distance)) %>% 
  arrange(desc(distance_sd))
## # A tibble: 104 x 2
##    dest  distance_sd
##    <chr>       <dbl>
##  1 EGE         10.5 
##  2 SAN         10.4 
##  3 SFO         10.2 
##  4 HNL         10.0 
##  5 SEA          9.98
##  6 LAS          9.91
##  7 PDX          9.87
##  8 PHX          9.86
##  9 LAX          9.66
## 10 IND          9.46
## # ... with 94 more rows
  • Mere rangiranja: min(x), quantile(x,0.25), max(x). Kvantili su uopštenje medijane. Na primer, quantile(x,0.25) ce pronaći vrednost u vektoru x koja je veća od 25% vrednosti, a manja od preostalih 75%.
# Kada polecu prvi i poslednji avion svakog dana?
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(
    first = min(dep_time),
    last = max(dep_time)
  )
## # A tibble: 365 x 5
## # Groups:   year, month [?]
##     year month   day first  last
##    <int> <int> <int> <dbl> <dbl>
##  1  2013     1     1   517  2356
##  2  2013     1     2    42  2354
##  3  2013     1     3    32  2349
##  4  2013     1     4    25  2358
##  5  2013     1     5    14  2357
##  6  2013     1     6    16  2355
##  7  2013     1     7    49  2359
##  8  2013     1     8   454  2351
##  9  2013     1     9     2  2252
## 10  2013     1    10     3  2320
## # ... with 355 more rows
  • Mere pozicije: first(x), nth(x, s), last(x).

  • Prebrojavanja: Spomenuli smo n(), koja nema argumente, i vraća broj elemenata trenutne grupe. Da prebrojimo vrednosti koje nisu NA, koristimo sum(!is.na(x)). Da odredimo broj različitih (jedinstvenih) vrednosti, koristimo n_distinct(x).

# Koje destinacije imaju najvise prevoznika?
not_cancelled %>% 
  group_by(dest) %>% 
  summarise(carriers = n_distinct(carrier)) %>% 
  arrange(desc(carriers))
## # A tibble: 104 x 2
##    dest  carriers
##    <chr>    <int>
##  1 ATL          7
##  2 BOS          7
##  3 CLT          7
##  4 ORD          7
##  5 TPA          7
##  6 AUS          6
##  7 DCA          6
##  8 DTW          6
##  9 IAD          6
## 10 MSP          6
## # ... with 94 more rows

Prebrojavanja su toliko korisna da dplyr obezbeđuje malu prečicu ako samo želimo da izvršimo prebrojavanje:

not_cancelled %>% 
  count(dest)
## # A tibble: 104 x 2
##    dest      n
##    <chr> <int>
##  1 ABQ     254
##  2 ACK     264
##  3 ALB     418
##  4 ANC       8
##  5 ATL   16837
##  6 AUS    2411
##  7 AVL     261
##  8 BDL     412
##  9 BGR     358
## 10 BHM     269
## # ... with 94 more rows

Prethodni kod zapravo radi ovo:

not_cancelled %>% 
  group_by(dest) %>%
    summarise(n = n())

Opciono, možemo dodati i argument težina. Na primer, možemo to da iskoristimo da bismo prebrojali (sum) ukupan broj milja koje je avion preleteo:

not_cancelled %>% 
  count(tailnum, wt = distance)
## # A tibble: 4,037 x 2
##    tailnum      n
##    <chr>    <dbl>
##  1 D942DN    3418
##  2 N0EGMQ  239143
##  3 N10156  109664
##  4 N102UW   25722
##  5 N103US   24619
##  6 N104UW   24616
##  7 N10575  139903
##  8 N105UW   23618
##  9 N107US   21677
## 10 N108UW   32070
## # ... with 4,027 more rows
# Ili analogno
not_cancelled %>%
  group_by(tailnum) %>%
    summarise(count = n(), n = sum(distance))
  • Broj i udeo vrednosti koje zadovoljavaju određeni uslov: sum(y > 10), mean(y == 0). Kada koristimo numeričke funkcije, TRUE se uvek konvertuje u 1, a FALSE u 0. Ovo pravi sum() i mean() veoma korisnim: sum(x) daje broj elemenata, a mean(x) udeo elemenata iz x koji imaju vrednost TRUE.
# Koliko letova je poletelo pre 5 sati ujutru? (ovo obicno ukazuje da je let iz prethodnog dana odlozen)
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(n_early = sum(dep_time < 500))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day n_early
##    <int> <int> <int>   <int>
##  1  2013     1     1       0
##  2  2013     1     2       3
##  3  2013     1     3       4
##  4  2013     1     4       3
##  5  2013     1     5       3
##  6  2013     1     6       2
##  7  2013     1     7       2
##  8  2013     1     8       1
##  9  2013     1     9       3
## 10  2013     1    10       3
## # ... with 355 more rows
# Koliki je udeo letova odlozenih za vise od sat vremena?
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(hour_perc = mean(arr_delay > 60))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day hour_perc
##    <int> <int> <int>     <dbl>
##  1  2013     1     1    0.0722
##  2  2013     1     2    0.0851
##  3  2013     1     3    0.0567
##  4  2013     1     4    0.0396
##  5  2013     1     5    0.0349
##  6  2013     1     6    0.0470
##  7  2013     1     7    0.0333
##  8  2013     1     8    0.0213
##  9  2013     1     9    0.0202
## 10  2013     1    10    0.0183
## # ... with 355 more rows

Grupisanje po vise promenljivih

Grupisanje po više promenljivih nam omogućava progresivno sakupljanje podataka:

daily = group_by(flights, year, month, day)
# broj letova po danima
(per_day   = summarise(daily, flights = n()))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day flights
##    <int> <int> <int>   <int>
##  1  2013     1     1     842
##  2  2013     1     2     943
##  3  2013     1     3     914
##  4  2013     1     4     915
##  5  2013     1     5     720
##  6  2013     1     6     832
##  7  2013     1     7     933
##  8  2013     1     8     899
##  9  2013     1     9     902
## 10  2013     1    10     932
## # ... with 355 more rows
# broj letova po mesecima
(per_month = summarise(per_day, flights = sum(flights)))
## # A tibble: 12 x 3
## # Groups:   year [?]
##     year month flights
##    <int> <int>   <int>
##  1  2013     1   27004
##  2  2013     2   24951
##  3  2013     3   28834
##  4  2013     4   28330
##  5  2013     5   28796
##  6  2013     6   28243
##  7  2013     7   29425
##  8  2013     8   29327
##  9  2013     9   27574
## 10  2013    10   28889
## 11  2013    11   27268
## 12  2013    12   28135
# broj letova po godinama
(per_year  = summarise(per_month, flights = sum(flights)))
## # A tibble: 1 x 2
##    year flights
##   <int>   <int>
## 1  2013  336776

Medutim, moramo biti oprezni kada radimo na ovaj način: u redu je za sume i prebrojavanja, ali se problem javlja za sredine i odstupanja, a posebno za statistike bazirane na rangiranju poput medijane. Suma grupnih suma je ukupna suma, ali medijana grupnih medijana nije ukupna medijana.

Degrupisanje

Ako želimo da uklonimo grupisanje i vratimo se na operacije na negrupisanim podacima, koristimo funkciju ungroup().

daily %>% 
  ungroup() %>%             # nisu vise grupisani po datumu
  summarise(flights = n())  # svi letovi
## # A tibble: 1 x 1
##   flights
##     <int>
## 1  336776

Grupisanje i mutate()(i filter())

Grupisanje je najkorisnije u kombinaciji sa summarise(), ali se mogu obaviti zgodne operacije i kombinovanjem sa mutate(), odnosno filter(). Na primer:

  • Pronalaženje svih grupa sa brojem elemenata većim od zadatog praga:
popular_dests = flights %>% 
  group_by(dest) %>% 
  filter(n() > 365)
popular_dests
## # A tibble: 332,577 x 19
## # Groups:   dest [77]
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 332,567 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
  • Skaliranje:
popular_dests %>% 
  filter(arr_delay > 0) %>% 
  mutate(prop_delay = arr_delay / sum(arr_delay)) %>% 
  select(year:day, dest, arr_delay, prop_delay)
## # A tibble: 131,106 x 6
## # Groups:   dest [77]
##     year month   day dest  arr_delay prop_delay
##    <int> <int> <int> <chr>     <dbl>      <dbl>
##  1  2013     1     1 IAH          11  0.000111 
##  2  2013     1     1 IAH          20  0.000201 
##  3  2013     1     1 MIA          33  0.000235 
##  4  2013     1     1 ORD          12  0.0000424
##  5  2013     1     1 FLL          19  0.0000938
##  6  2013     1     1 ORD           8  0.0000283
##  7  2013     1     1 LAX           7  0.0000344
##  8  2013     1     1 DFW          31  0.000282 
##  9  2013     1     1 ATL          12  0.0000400
## 10  2013     1     1 DTW          16  0.000116 
## # ... with 131,096 more rows