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.
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
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
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
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:
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.
Sledeća dva (ili tri) slova opisuju tip tuberkuloze: rel
, ep
, sn
, ili sp
.
Sledeće slovo daje pol bolesnika - muškarci (m
) i žene (f
).
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
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:
Alternativne reprezentacije mogu imati značajne prednosti u smislu performansi ili prostora.
Postoje posebne konvencije za čuvanje podataka koje mogu biti sasvim drugačije od konvencija urednih 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.