Razdvajanje i sjedinjavanje

Do sada smo naučili kako da uredimo table2 i table4, ali ne i table3. Ona ima drugačiji problem: imamo jednu kolonu (rate) koja sadrži vrednosti dve promenljive (cases i population). Da bismo rešili ovaj problem, trebaće nam funkcija separate(). Takođe, proučićemo i komplement od separate(): unite(), koju koristimo ako je jedna promenljiva raštrkana na više kolona.

Razdvajanje

separate() razdvaja jednu kolonu na više kolona, razdvajajući kad god se pojavi znak za razdvajanje. Uzmimo table3:

table3
## # A tibble: 6 x 3
##   country      year rate             
## * <chr>       <int> <chr>            
## 1 Afghanistan  1999 745/19987071     
## 2 Afghanistan  2000 2666/20595360    
## 3 Brazil       1999 37737/172006362  
## 4 Brazil       2000 80488/174504898  
## 5 China        1999 212258/1272915272
## 6 China        2000 213766/1280428583

Kolona rate sadrži vrednosti obe promenljive - cases i population, pa mi želimo da je podelimo u dve promenljive. separate() uzima kao argument ime kolone koju treba razdvojti, kao i imena kolona na koje se razdvaja, kao što je prikazano na slici dole i u kodu ispod:

table3 %>% 
  separate(rate, into = c("cases", "population"))
## # A tibble: 6 x 4
##   country      year cases  population
## * <chr>       <int> <chr>  <chr>     
## 1 Afghanistan  1999 745    19987071  
## 2 Afghanistan  2000 2666   20595360  
## 3 Brazil       1999 37737  172006362 
## 4 Brazil       2000 80488  174504898 
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

Podrazumevano, separate() će podeliti vrednosti kad god vidi ne-alfanumerički znak (tj. znak koji nije ni broj ni slovo). Na primer, u gornjem kodu, separate() deli vrednosti rate na delove razdvojene kosom crtom. Ako želimo poseban znak za razdvajanje, možemo da ga navedemo u sep argumentu u separate(). Na primer, možemo kod odozgo pisati kao:

table3 %>% 
  separate(rate, into = c("cases", "population"), sep = "/")
## # A tibble: 6 x 4
##   country      year cases  population
## * <chr>       <int> <chr>  <chr>     
## 1 Afghanistan  1999 745    19987071  
## 2 Afghanistan  2000 2666   20595360  
## 3 Brazil       1999 37737  172006362 
## 4 Brazil       2000 80488  174504898 
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

(Formalno, sep je regularni izraz, o kome ćemo saznati više kad bude priče o stringovima.)

Kada pogledamo pažljvo tipove kolona: primetićemo da su cases i population kolone karaktera. Ovo je podrazumevano ponašanje u separate(): ostavlja tip kolone takvim kakav je. Ovde, međutim, to nije baš korisno jer su to zapravo brojevi. Možemo zatražiti od separate() da pokuša da konvertuje u bolje tipove koristeći convert = TRUE.

table3 %>% 
  separate(rate, into = c("cases", "population"), convert = TRUE)
## # A tibble: 6 x 4
##   country      year  cases population
## * <chr>       <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3 Brazil       1999  37737  172006362
## 4 Brazil       2000  80488  174504898
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

Možemo proslediti i vektor celih brojeva sep argumentu. separate() će interpretirati cele brojeve kao pozicije gde treba da razdvoji string. Pozitivne vrednosti počinju od 1 na levoj strani stringa; negativne od -1 na desnoj strani stringa. Kada se koriste celi brojevi za razdvajanje stringova, dužina sep treba da bude za jedan manja od broja imena navedenih u into.

Ovo možemo koristiti da, na primer, razdvajamo godine na po dve cifre. Ovo čini podatke manje urednim, ali je korisno u nekim slučajevima.

table3 %>% 
  separate(year, into = c("century", "year"), sep = 2)
## # A tibble: 6 x 4
##   country     century year  rate             
## * <chr>       <chr>   <chr> <chr>            
## 1 Afghanistan 19      99    745/19987071     
## 2 Afghanistan 20      00    2666/20595360    
## 3 Brazil      19      99    37737/172006362  
## 4 Brazil      20      00    80488/174504898  
## 5 China       19      99    212258/1272915272
## 6 China       20      00    213766/1280428583

Sjedinjavanje

Funkcija unite() je inverzna funkciji separate(): kombinuje više kolona u jednu kolonu. Ona će nam trebati mnogo ređe nego separate(), ali je i dalje koristan alat.

Možemo da koristimo unite() da bismo ponovo udružili kolone year i century koje smo kreirali u prethodnom primeru. unite() uzima bazu podataka, ime nove promenljive koju kreira i skup kolona koje se kombinuju, opet navedeno u dplyr::select() stilu:

table5 %>% 
  unite(new, century, year)
## # A tibble: 6 x 3
##   country     new   rate             
##   <chr>       <chr> <chr>            
## 1 Afghanistan 19_99 745/19987071     
## 2 Afghanistan 20_00 2666/20595360    
## 3 Brazil      19_99 37737/172006362  
## 4 Brazil      20_00 80488/174504898  
## 5 China       19_99 212258/1272915272
## 6 China       20_00 213766/1280428583

I u ovom slučaju moramo koristiti i sep argument. Podrazumevano će se postaviti donja crta (_) između vrednosti iz različitih kolona. Ovde ne želimo nikakav separator pa koristimo "".

table5 %>% 
  unite(new, century, year, sep = "")
## # A tibble: 6 x 3
##   country     new   rate             
##   <chr>       <chr> <chr>            
## 1 Afghanistan 1999  745/19987071     
## 2 Afghanistan 2000  2666/20595360    
## 3 Brazil      1999  37737/172006362  
## 4 Brazil      2000  80488/174504898  
## 5 China       1999  212258/1272915272
## 6 China       2000  213766/1280428583

Nedostajuće vrednosti

Promena reprezentacije skupa podataka donosi komplikacije sa nedostajućim vrednostima. Interesantno je da vrednost može da nedostaje na dva načina:

  • Eksplicitno, tj. označeno sa NA.

  • Implicitno, tj. jednostavno nije prisutna u podacima.

Ilustrujmo ovu ideju vrlo jednostavnim skupom podataka:

stocks = tibble(
  year   = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
  qtr    = c(   1,    2,    3,    4,    2,    3,    4),
  earn = c(1.88, 0.59, 0.35,   NA, 0.92, 0.17, 2.66)
)
stocks
## # A tibble: 7 x 3
##    year   qtr  earn
##   <dbl> <dbl> <dbl>
## 1  2015     1  1.88
## 2  2015     2  0.59
## 3  2015     3  0.35
## 4  2015     4 NA   
## 5  2016     2  0.92
## 6  2016     3  0.17
## 7  2016     4  2.66

U ovom skupu nedostaju dve vrednosti:

  • Zarada za četvrtu četvrtinu 2015. godine nedostaje eksplicitno, jer ćelija u kojoj bi trebalo da je ta vrednost sadrži NA.

  • Zarada za prvu četvrtinu 2016. godine implicitno nedostaje, jer se jednostavno ne pojavljuje u skupu podataka.

Dakle, možemo da shvatimo eksplicitnu nedostajuća vrednost kao prisustvo odsustva, a implicitnu kao odsustvo prisustva.

Način na koji je skup podataka prikazan može implicitne vrednosti učiniti eksplicitnim. Na primer, možemo napraviti implicitnu nedostajuću vrednost eksplicitnom stavljajući godine u kolone:

stocks %>% 
  spread(key = year, value = earn)
## # A tibble: 4 x 3
##     qtr `2015` `2016`
##   <dbl>  <dbl>  <dbl>
## 1     1   1.88  NA   
## 2     2   0.59   0.92
## 3     3   0.35   0.17
## 4     4  NA      2.66

Pošto ove eksplicitne nedostajuće vrednosti možda nisu važne u drugim reprezentacijama podataka, možemo postaviti na.rm = TRUE u gather() da bismo eksplicitne nedostajuće vrednosti implicitno uključili:

stocks %>% 
  spread(year, earn) %>% 
  gather(year, earn, `2015`:`2016`, na.rm = TRUE)
## # A tibble: 6 x 3
##     qtr year   earn
## * <dbl> <chr> <dbl>
## 1     1 2015   1.88
## 2     2 2015   0.59
## 3     3 2015   0.35
## 4     2 2016   0.92
## 5     3 2016   0.17
## 6     4 2016   2.66

Ako želimo nedostajuće vrednosti da napravimo eksplicitnim u urednim podacima koristimo complete():

stocks %>% 
  complete(year, qtr)
## # A tibble: 8 x 3
##    year   qtr  earn
##   <dbl> <dbl> <dbl>
## 1  2015     1  1.88
## 2  2015     2  0.59
## 3  2015     3  0.35
## 4  2015     4 NA   
## 5  2016     1 NA   
## 6  2016     2  0.92
## 7  2016     3  0.17
## 8  2016     4  2.66

complete() kao argumente uzima skup kolona i pronalazi sve jedinstvene kombinacije. Zatim se osigurava da baza podataka sadrži sve te vrednosti, popunjavajući eksplicitno sa NA gde god je potrebno.

Postoji još jedan važan alat koji treba da znamo za rad sa nedostajućim vrednostima. Ponekad, kad se baza primarno koristi za unos podataka, nedostajuće vrednosti ukazuju da prethodna vrednost treba da se prenese:

treatment = tribble(
  ~ person,           ~ treatment, ~response,
  "Derrick Whitmore", 1,           7,
  NA,                 2,           10,
  NA,                 3,           9,
  "Katherine Burke",  1,           4
)

Možemo popuniti ove nedostajuće vrednosti sa fill(). Ona uzima kao argumente set kolona u kojima želimo da nedostajuće vrednosti budu zamenjene najskorijom ne-nedostajućom vrednošću (ovo se ponekad naziva i prenošenje poslednje opservacije).

treatment %>% 
  fill(person)
## # A tibble: 4 x 3
##   person           treatment response
##   <chr>                <dbl>    <dbl>
## 1 Derrick Whitmore         1        7
## 2 Derrick Whitmore         2       10
## 3 Derrick Whitmore         3        9
## 4 Katherine Burke          1        4

Case Study

Da završimo ovo poglavlje, hajde da sastavimo sve što smo naučili kako bismo se uhvatili u koštac sa realnim problemom uređivanja podataka. Skup podataka tidyr::who sadrži slučajeve tuberkoloze (TB) razdvojene po godini, zemlji, starosti, polu i dijagnostičkoj metodi.

Postoji mnoštvo epidemioloških informacija u ovom skupu podataka, i veliki je izazov raditi sa podacima u formi koja je data:

who
## # A tibble: 7,240 x 60
##    country     iso2  iso3   year new_sp_m014 new_sp_m1524 new_sp_m2534
##    <chr>       <chr> <chr> <int>       <int>        <int>        <int>
##  1 Afghanistan AF    AFG    1980          NA           NA           NA
##  2 Afghanistan AF    AFG    1981          NA           NA           NA
##  3 Afghanistan AF    AFG    1982          NA           NA           NA
##  4 Afghanistan AF    AFG    1983          NA           NA           NA
##  5 Afghanistan AF    AFG    1984          NA           NA           NA
##  6 Afghanistan AF    AFG    1985          NA           NA           NA
##  7 Afghanistan AF    AFG    1986          NA           NA           NA
##  8 Afghanistan AF    AFG    1987          NA           NA           NA
##  9 Afghanistan AF    AFG    1988          NA           NA           NA
## 10 Afghanistan AF    AFG    1989          NA           NA           NA
## # ... with 7,230 more rows, and 53 more variables: new_sp_m3544 <int>,
## #   new_sp_m4554 <int>, new_sp_m5564 <int>, new_sp_m65 <int>,
## #   new_sp_f014 <int>, new_sp_f1524 <int>, new_sp_f2534 <int>,
## #   new_sp_f3544 <int>, new_sp_f4554 <int>, new_sp_f5564 <int>,
## #   new_sp_f65 <int>, new_sn_m014 <int>, new_sn_m1524 <int>,
## #   new_sn_m2534 <int>, new_sn_m3544 <int>, new_sn_m4554 <int>,
## #   new_sn_m5564 <int>, new_sn_m65 <int>, new_sn_f014 <int>,
## #   new_sn_f1524 <int>, new_sn_f2534 <int>, new_sn_f3544 <int>,
## #   new_sn_f4554 <int>, new_sn_f5564 <int>, new_sn_f65 <int>,
## #   new_ep_m014 <int>, new_ep_m1524 <int>, new_ep_m2534 <int>,
## #   new_ep_m3544 <int>, new_ep_m4554 <int>, new_ep_m5564 <int>,
## #   new_ep_m65 <int>, new_ep_f014 <int>, new_ep_f1524 <int>,
## #   new_ep_f2534 <int>, new_ep_f3544 <int>, new_ep_f4554 <int>,
## #   new_ep_f5564 <int>, new_ep_f65 <int>, newrel_m014 <int>,
## #   newrel_m1524 <int>, newrel_m2534 <int>, newrel_m3544 <int>,
## #   newrel_m4554 <int>, newrel_m5564 <int>, newrel_m65 <int>,
## #   newrel_f014 <int>, newrel_f1524 <int>, newrel_f2534 <int>,
## #   newrel_f3544 <int>, newrel_f4554 <int>, newrel_f5564 <int>,
## #   newrel_f65 <int>

Ovo je vrlo tipičan skup podataka iz realnog života. Sadrži suvišne kolone, čudna su imena kolona i nedostaje mnogo vrednosti. Ukratko, who je neuredna i trebaće nam više koraka da je uredimo. Kao i dplyr, tidyr je dizajniran tako da svaka funkcija radi jednu stvar dobro. To znači da ćemo u situacijama iz realnog života obično morati da spojimo više glagola u “cevovod”.

Najbolje mesto za početak je skoro uvek skupljanje kolona koje nisu promenljive, Hajde da vidimo šta imamo:

  • Deluje da su country, iso2 i iso3 tri promenljive koje rade istu stvar - određuju zemlju.

  • year je takođe promenljiva.

  • Još ne znamo šta su ostale kolone, ali s obzirom na strukturu imena promenljivih (npr. new_sp_m014, new_ep_m014. new_ep_f014) verovatno će biti vrednosti, a ne promenljive.

Dakle, hoćemo da skupimo sve kolone od new_sp_m014 do newrel_f65. Ne znamo šta ove vrednosti predstavljaju još uvek, pa ćemo im dati generičko ime “key” (ključ). Znamo da ćelije predstavljaju broj slučajeva tuberkuloze, tako da ćemo korisititi promenljivu cases. Postoji mnogo nedostajućih vrednosti u trenutnoj reprezentaciji, tako da ćemo za sada koristiti na.rm samo da bismo mogli da se fokusiramo na vrednosti koje su prisutne.

who1 = who %>% 
  gather(new_sp_m014:newrel_f65, key = "key", value = "cases", na.rm = TRUE)
who1
## # A tibble: 76,046 x 6
##    country     iso2  iso3   year key         cases
##  * <chr>       <chr> <chr> <int> <chr>       <int>
##  1 Afghanistan AF    AFG    1997 new_sp_m014     0
##  2 Afghanistan AF    AFG    1998 new_sp_m014    30
##  3 Afghanistan AF    AFG    1999 new_sp_m014     8
##  4 Afghanistan AF    AFG    2000 new_sp_m014    52
##  5 Afghanistan AF    AFG    2001 new_sp_m014   129
##  6 Afghanistan AF    AFG    2002 new_sp_m014    90
##  7 Afghanistan AF    AFG    2003 new_sp_m014   127
##  8 Afghanistan AF    AFG    2004 new_sp_m014   139
##  9 Afghanistan AF    AFG    2005 new_sp_m014   151
## 10 Afghanistan AF    AFG    2006 new_sp_m014   193
## # ... with 76,036 more rows
who1 %>% 
  count(key)
## # A tibble: 56 x 2
##    key              n
##    <chr>        <int>
##  1 new_ep_f014   1032
##  2 new_ep_f1524  1021
##  3 new_ep_f2534  1021
##  4 new_ep_f3544  1021
##  5 new_ep_f4554  1017
##  6 new_ep_f5564  1017
##  7 new_ep_f65    1014
##  8 new_ep_m014   1038
##  9 new_ep_m1524  1026
## 10 new_ep_m2534  1020
## # ... with 46 more rows

Za imena ovih vrednosti koje predstavljaju kolone u izvornoj bazi važi:

  1. Prva tri slova svake kolone označavaju da li kolona sadrži nove ili stare slučajeve tuberkuloze. U ovoj bazi, svaka kolona sadrži nove slučajeve.

  2. Sledeća dva (ili tri) slova opisuju tip tuberkuloze: rel, ep, sn, ili sp .

  3. Sledeće slovo daje pol bolesnika - muškarci (m) i žene (f).

  4. Preostali brojevi daju dobnu grupu. Skup podataka grupiše slučajeve u sedam dobnih grupa:

  • 014 = 0 - 14 godina

  • 1524 = 15 - 24 godine

  • 2534 = 25 - 34 godine

  • 3544 = 35 - 44 godina

  • 4554 = 45 - 54 godina

  • 5564 = 55 - 64 godina

  • 65 = 65 ili više godina

Moramo da napravimo manju izmenu u nazivima kolona: imena su blago neuniformisana, jer umesto new_rel imamo newrel (ako se to ne popravi, dobićemo greške u narednim koracima). Saznaćemo više o str_replace() u delu o stringovima, ali osnovna ideja je prilično jednostavna: zamenimo karaktere newrel sa new_rel. Ovo čini sva imena promenljivih uniformisanim.

who2 = who1 %>% 
  mutate(key = stringr::str_replace(key, "newrel", "new_rel"))
who2
## # A tibble: 76,046 x 6
##    country     iso2  iso3   year key         cases
##    <chr>       <chr> <chr> <int> <chr>       <int>
##  1 Afghanistan AF    AFG    1997 new_sp_m014     0
##  2 Afghanistan AF    AFG    1998 new_sp_m014    30
##  3 Afghanistan AF    AFG    1999 new_sp_m014     8
##  4 Afghanistan AF    AFG    2000 new_sp_m014    52
##  5 Afghanistan AF    AFG    2001 new_sp_m014   129
##  6 Afghanistan AF    AFG    2002 new_sp_m014    90
##  7 Afghanistan AF    AFG    2003 new_sp_m014   127
##  8 Afghanistan AF    AFG    2004 new_sp_m014   139
##  9 Afghanistan AF    AFG    2005 new_sp_m014   151
## 10 Afghanistan AF    AFG    2006 new_sp_m014   193
## # ... with 76,036 more rows

Možemo raščlaniti imena sa dva prolaza funkcijom separate(). Prvi prolaz će raščlaniti imena po donjoj crti.

who3 = who2 %>% 
  separate(key, c("new", "type", "sexage"), sep = "_")
who3
## # A tibble: 76,046 x 8
##    country     iso2  iso3   year new   type  sexage cases
##    <chr>       <chr> <chr> <int> <chr> <chr> <chr>  <int>
##  1 Afghanistan AF    AFG    1997 new   sp    m014       0
##  2 Afghanistan AF    AFG    1998 new   sp    m014      30
##  3 Afghanistan AF    AFG    1999 new   sp    m014       8
##  4 Afghanistan AF    AFG    2000 new   sp    m014      52
##  5 Afghanistan AF    AFG    2001 new   sp    m014     129
##  6 Afghanistan AF    AFG    2002 new   sp    m014      90
##  7 Afghanistan AF    AFG    2003 new   sp    m014     127
##  8 Afghanistan AF    AFG    2004 new   sp    m014     139
##  9 Afghanistan AF    AFG    2005 new   sp    m014     151
## 10 Afghanistan AF    AFG    2006 new   sp    m014     193
## # ... with 76,036 more rows

Zatim bismo mogli da izbacimoo i new kolonu jer je konstantna u ovoj bazi podataka. Kad već izbacujemo kolone, izbacimo i iso2 i iso3 jer su suvišne.

who3 %>% 
  count(new)
## # A tibble: 1 x 2
##   new       n
##   <chr> <int>
## 1 new   76046
who4 = who3 %>% 
  select(-new, -iso2, -iso3)

Sada ćemo razdvojiti sexage na sex i age razdvajanjem nakon prvog karaktera.

who5 = who4 %>% 
  separate(sexage, c("sex", "age"), sep = 1)
who5
## # A tibble: 76,046 x 6
##    country      year type  sex   age   cases
##    <chr>       <int> <chr> <chr> <chr> <int>
##  1 Afghanistan  1997 sp    m     014       0
##  2 Afghanistan  1998 sp    m     014      30
##  3 Afghanistan  1999 sp    m     014       8
##  4 Afghanistan  2000 sp    m     014      52
##  5 Afghanistan  2001 sp    m     014     129
##  6 Afghanistan  2002 sp    m     014      90
##  7 Afghanistan  2003 sp    m     014     127
##  8 Afghanistan  2004 sp    m     014     139
##  9 Afghanistan  2005 sp    m     014     151
## 10 Afghanistan  2006 sp    m     014     193
## # ... with 76,036 more rows

Sada je baza who uredna!

Prikazali smo kod u delovima, smeštajući svaki privremeni rezultat u novu promenljivu. Umesto toga, lepše i lakše nam je da postepeno izgradimo složenu cev:

who %>%
  gather(key, value, new_sp_m014:newrel_f65, na.rm = TRUE) %>% 
  mutate(key = stringr::str_replace(key, "newrel", "new_rel")) %>%
  separate(key, c("new", "var", "sexage")) %>% 
  select(-new, -iso2, -iso3) %>% 
  separate(sexage, c("sex", "age"), sep = 1)
## # A tibble: 76,046 x 6
##    country      year var   sex   age   value
##    <chr>       <int> <chr> <chr> <chr> <int>
##  1 Afghanistan  1997 sp    m     014       0
##  2 Afghanistan  1998 sp    m     014      30
##  3 Afghanistan  1999 sp    m     014       8
##  4 Afghanistan  2000 sp    m     014      52
##  5 Afghanistan  2001 sp    m     014     129
##  6 Afghanistan  2002 sp    m     014      90
##  7 Afghanistan  2003 sp    m     014     127
##  8 Afghanistan  2004 sp    m     014     139
##  9 Afghanistan  2005 sp    m     014     151
## 10 Afghanistan  2006 sp    m     014     193
## # ... with 76,036 more rows

Neuredni podaci

Trebalo bi da kažemo nešto i o “neurednim” podacima. Možda je ovo pregrub izraz, jer postoji mnogo korisnih i dobro utemeljenih struktura podataka koje nisu “uredni” podaci. Postoje dva glavna razloga za korišćenje drugih struktura podataka:

Bilo koji od ovih razloga znači da će nam trebati nešto drugo osim tibla (ili obične baze (data.frame)). Ako se naši podaci prirodno uklapaju u pravougaonu strukturu sastavljenu od opservacija i promenljivih, uredni podaci bi trebalo da budu logičan izbor, ali postoje dobri razlozi da se koriste i druge strukture, uredni podaci nisu jedini način.