R paket tidyr i njegove nove funkcije pivot_longer i pivot_wider
Paket tidyr uključeno u jezgro jedne od najpopularnijih biblioteka na R jeziku - tidyverse.
Glavna svrha paketa je da dovede podatke u tačan oblik.
Već dostupno na Habréu objavljivanje posvećen ovom paketu, ali datira iz 2015. godine. I želim da vam kažem o najaktuelnijim promenama koje je pre nekoliko dana najavio njen autor, Hedli Vikam.
SJK: Hoće li gather() i spread() biti zastarjeli?
Hadley Wickham: Donekle. Više nećemo preporučivati korištenje ovih funkcija i ispravljati greške u njima, ali će one i dalje biti prisutne u paketu u svom trenutnom stanju.
Sadržaj
Ako ste zainteresovani za analizu podataka, možda će vas zanimati moja telegram и youtube kanala. Većina sadržaja je posvećena R jeziku.
Cilj tidyr — pomoći vam da dovedete podatke u takozvani uredan oblik. Uredni podaci su podaci gdje:
Svaka varijabla je u stupcu.
Svako zapažanje je niz.
Svaka vrijednost je ćelija.
Mnogo je lakše i praktičnije raditi sa podacima koji se prikazuju u urednim podacima prilikom provođenja analize.
Glavne funkcije uključene u tidyr paket
tidyr sadrži skup funkcija dizajniranih za transformaciju tablica:
fill() — popunjavanje nedostajućih vrijednosti u koloni prethodnim vrijednostima;
separate() — dijeli jedno polje na nekoliko pomoću separatora;
unite() — obavlja operaciju spajanja nekoliko polja u jedno, inverzno djelovanje funkcije separate();
pivot_longer() — funkcija koja pretvara podatke iz širokog formata u dugi format;
pivot_wider() - funkcija koja pretvara podatke iz dugog formata u široki format. Obrnuta operacija od one koju obavlja funkcija pivot_longer().
gather()zastarjelo — funkcija koja pretvara podatke iz širokog formata u dugi format;
spread()zastarjelo - funkcija koja pretvara podatke iz dugog formata u široki format. Obrnuta operacija od one koju obavlja funkcija gather().
Novi koncept za pretvaranje podataka iz širokog u dugi format i obrnuto
Ranije su se funkcije koristile za ovu vrstu transformacije gather() и spread(). Tokom godina postojanja ovih funkcija postalo je očigledno da za većinu korisnika, uključujući i autora paketa, nazivi ovih funkcija i njihovi argumenti nisu bili sasvim očigledni, što je izazvalo poteškoće u njihovom pronalaženju i razumijevanju koje od ovih funkcija pretvara okvir datuma od širokog do dugog formata, i obrnuto.
S tim u vezi, u tidyr Dodane su dvije nove, važne funkcije koje su dizajnirane za transformaciju okvira datuma.
Nove funkcije pivot_longer() и pivot_wider() inspirisani nekim karakteristikama u paketu cdata, koju su kreirali John Mount i Nina Zumel.
Instaliranje najnovije verzije tidyr 0.8.3.9000
Da instalirate novu, najnoviju verziju paketa tidyr0.8.3.9000, gdje su dostupne nove funkcije, koristite sljedeći kod.
devtools::install_github("tidyverse/tidyr")
U vrijeme pisanja ove funkcije su dostupne samo u dev verziji paketa na GitHubu.
Prelazak na nove funkcije
Zapravo, nije teško prenijeti stare skripte za rad s novim funkcijama; radi boljeg razumijevanja, uzet ću primjer iz dokumentacije starih funkcija i pokazati kako se iste operacije izvode pomoću novih pivot_*() funkcije.
Pretvorite široki format u dugi format.
Primjer koda iz dokumentacije funkcije prikupljanja
# example
library(dplyr)
stocks <- data.frame(
time = as.Date('2009-01-01') + 0:9,
X = rnorm(10, 0, 1),
Y = rnorm(10, 0, 2),
Z = rnorm(10, 0, 4)
)
# old
stocks_gather <- stocks %>% gather(key = stock,
value = price,
-time)
# new
stocks_long <- stocks %>% pivot_longer(cols = -time,
names_to = "stock",
values_to = "price")
Pretvaranje dugog formata u široki format.
Primjer koda iz dokumentacije funkcije širenja
# old
stocks_spread <- stocks_gather %>% spread(key = stock,
value = price)
# new
stock_wide <- stocks_long %>% pivot_wider(names_from = "stock",
values_from = "price")
Jer u gornjim primjerima rada sa pivot_longer() и pivot_wider(), u originalnoj tabeli Akcije nema kolona navedenih u argumentima names_to и vrijednosti_za njihova imena moraju biti pod navodnicima.
Tabela koja će vam pomoći da najlakše shvatite kako se prebaciti na rad s novim konceptom tidyr.
Napomena autora
Sav tekst ispod je adaptivan, čak bih rekao i besplatan prijevod vinjete sa službene web stranice biblioteke tidyverse.
Jednostavan primjer pretvaranja podataka iz širokog u dugi format
pivot_longer () — čini skupove podataka dužim smanjenjem broja kolona i povećanjem broja redova.
Da biste pokrenuli primjere predstavljene u članku, prvo morate povezati potrebne pakete:
library(tidyr)
library(dplyr)
library(readr)
Recimo da imamo tabelu s rezultatima ankete koja je (između ostalog) pitala ljude o njihovoj vjeri i godišnjem prihodu:
Ova tabela sadrži podatke o religiji ispitanika u redovima, a nivoi prihoda su razbacani po nazivima kolona. Broj ispitanika iz svake kategorije pohranjen je u vrijednosti ćelije na sjecištu religije i nivoa prihoda. Da bi se tabela dovela u uredan, ispravan format, dovoljno je koristiti pivot_longer():
Prvi argument ovratnici, opisuje koje kolone treba spojiti. U ovom slučaju, sve kolone osim vrijeme.
Argument names_to daje ime varijable koja će biti kreirana iz imena kolona koje smo spojili.
vrijednosti_za daje naziv varijable koja će se kreirati iz podataka pohranjenih u vrijednostima ćelija spojenih kolona.
Спецификации
Ovo je nova funkcionalnost paketa tidyr, koji je ranije bio nedostupan pri radu sa naslijeđenim funkcijama.
Specifikacija je okvir podataka, čiji svaki red odgovara jednoj koloni u novom izlaznom datumskom okviru i dvije posebne kolone koje počinju sa:
.name sadrži originalno ime kolone.
.value sadrži naziv kolone koja će sadržavati vrijednosti ćelije.
Preostale kolone specifikacije odražavaju kako će nova kolona prikazati naziv komprimiranih stupaca iz .name.
Specifikacija opisuje metapodatke pohranjene u nazivu stupca, s jednim redom za svaki stupac i jednim stupcem za svaku varijablu, u kombinaciji s imenom stupca, ova definicija može izgledati zbunjujuće u ovom trenutku, ali nakon što pogledate nekoliko primjera, to će postati mnogo jasnije.
Poenta specifikacije je da možete dohvatiti, modificirati i definirati nove metapodatke za okvir podataka koji se pretvara.
Za rad sa specifikacijama pri pretvaranju tablice iz širokog formata u dugi format, koristite funkciju pivot_longer_spec().
Način na koji ova funkcija funkcionira je da uzima bilo koji okvir datuma i generiše svoje metapodatke na gore opisan način.
Kao primjer, uzmimo who dataset koji se isporučuje s paketom tidyr. Ovaj skup podataka sadrži informacije međunarodne zdravstvene organizacije o učestalosti tuberkuloze.
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 Afghan… AF AFG 1980 NA NA NA
#> 2 Afghan… AF AFG 1981 NA NA NA
#> 3 Afghan… AF AFG 1982 NA NA NA
#> 4 Afghan… AF AFG 1983 NA NA NA
#> 5 Afghan… AF AFG 1984 NA NA NA
#> 6 Afghan… AF AFG 1985 NA NA NA
#> 7 Afghan… AF AFG 1986 NA NA NA
#> 8 Afghan… AF AFG 1987 NA NA NA
#> 9 Afghan… AF AFG 1988 NA NA NA
#> 10 Afghan… AF AFG 1989 NA NA NA
#> # … with 7,230 more rows, and 53 more variables
Hajde da napravimo njegovu specifikaciju.
spec <- who %>%
pivot_longer_spec(new_sp_m014:newrel_f65, values_to = "count")
polja zemlja, iso2, iso3 su već varijable. Naš zadatak je da okrenemo kolone sa new_sp_m014 na newrel_f65.
Nazivi ovih kolona pohranjuju sljedeće informacije:
Prefiks new_ označava da kolona sadrži podatke o novim slučajevima tuberkuloze, trenutni datumski okvir sadrži podatke samo o novim bolestima, tako da ovaj prefiks u trenutnom kontekstu nema nikakvo značenje.
sp/rel/sp/ep opisuje metodu za dijagnosticiranje bolesti.
m/f spol pacijenta.
014/1524/2535/3544/4554/65 dob pacijenata.
Ove kolone možemo podijeliti pomoću funkcije extract()koristeći regularni izraz.
Konačno, kako bismo primijenili specifikaciju koju smo kreirali na originalni okvir datuma koji moramo koristiti argument spec u funkciji pivot_longer().
who %>% pivot_longer(spec = spec)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <fct> <ord> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rows
Sve što smo upravo uradili može se shematski prikazati na sljedeći način:
Specifikacija koristeći više vrijednosti (.value)
U gornjem primjeru, kolona specifikacije .value sadrži samo jednu vrijednost, u većini slučajeva je to slučaj.
Ali povremeno se može pojaviti situacija kada trebate prikupiti podatke iz stupaca s različitim tipovima podataka u vrijednostima. Korištenje stare funkcije spread() ovo bi bilo prilično teško izvesti.
Primjer ispod je preuzet iz vinjete na paket data.table.
Kreirani datumski okvir sadrži podatke o djeci jedne porodice u svakom redu. Porodice mogu imati jedno ili dvoje djece. Za svako dijete daju se podaci o datumu rođenja i spolu, a podaci za svako dijete su u posebnim kolonama, a naš zadatak je da te podatke dovedemo u ispravan format za analizu.
Napominjemo da imamo dvije varijable sa informacijama o svakom djetetu: njegov spol i datum rođenja (kolone s prefiksom dop sadrže datum rođenja, kolone sa prefiksom pol sadrže spol djeteta). Očekivani rezultat je da se pojavljuju u odvojenim kolonama. To možemo učiniti generiranjem specifikacije u kojoj stupac .value imaće dva različita značenja.
spec <- family %>%
pivot_longer_spec(-family) %>%
separate(col = name, into = c(".value", "child"))%>%
mutate(child = parse_number(child))
#> # A tibble: 4 x 3
#> .name .value child
#> <chr> <chr> <dbl>
#> 1 dob_child1 dob 1
#> 2 dob_child2 dob 2
#> 3 gender_child1 gender 1
#> 4 gender_child2 gender 2
Dakle, pogledajmo korak po korak radnje koje izvodi gornji kod.
pivot_longer_spec(-family) — kreirajte specifikaciju koja komprimira sve postojeće kolone osim kolone porodice.
separate(col = name, into = c(".value", "child")) - podeli kolonu .name, koji sadrži nazive izvornih polja, koristeći donju crtu i unoseći rezultirajuće vrijednosti u stupce .value и Dete.
mutate(child = parse_number(child)) — transformirajte vrijednosti polja Dete od tekstualnog do numeričkog tipa podataka.
Sada možemo primijeniti rezultirajuću specifikaciju na originalni okvir podataka i dovesti tablicu u željeni oblik.
Koristimo argument na.rm = TRUE, jer trenutni oblik podataka prisiljava stvaranje dodatnih redova za nepostojeća opažanja. Jer porodica 2 ima samo jedno dijete, na.rm = TRUE garantuje da će porodica 2 imati jedan red na izlazu.
Pretvaranje okvira datuma iz dugog u široki format
pivot_wider() - je inverzna transformacija, i obrnuto povećava broj kolona okvira datuma smanjenjem broja redova.
Ova vrsta transformacije se izuzetno rijetko koristi za dovođenje podataka u tačan oblik, međutim, ova tehnika može biti korisna za kreiranje pivot tablica koje se koriste u prezentacijama ili za integraciju s nekim drugim alatima.
Zapravo funkcije pivot_longer() и pivot_wider() su simetrične i proizvode akcije inverzne jedna drugoj, tj.: df %>% pivot_longer(spec = spec) %>% pivot_wider(spec = spec) и df %>% pivot_wider(spec = spec) %>% pivot_longer(spec = spec) će vratiti originalni df.
Najjednostavniji primjer pretvaranja tablice u široki format
Da demonstrira kako funkcija radi pivot_wider() koristićemo skup podataka fish_encounters, koji pohranjuje informacije o tome kako različite stanice bilježe kretanje ribe duž rijeke.
#> # A tibble: 114 x 3
#> fish station seen
#> <fct> <fct> <int>
#> 1 4842 Release 1
#> 2 4842 I80_1 1
#> 3 4842 Lisbon 1
#> 4 4842 Rstr 1
#> 5 4842 Base_TD 1
#> 6 4842 BCE 1
#> 7 4842 BCW 1
#> 8 4842 BCE2 1
#> 9 4842 BCW2 1
#> 10 4842 MAE 1
#> # … with 104 more rows
U većini slučajeva, ova tabela će biti informativnija i lakša za korištenje ako predstavite informacije za svaku stanicu u posebnoj koloni.
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 NA NA NA NA NA
#> 5 4847 1 1 1 NA NA NA NA NA NA NA
#> 6 4848 1 1 1 1 NA NA NA NA NA NA
#> 7 4849 1 1 NA NA NA NA NA NA NA NA
#> 8 4850 1 1 NA 1 1 1 1 NA NA NA
#> 9 4851 1 1 NA NA NA NA NA NA NA NA
#> 10 4854 1 1 NA NA NA NA NA NA NA NA
#> # … with 9 more rows, and 1 more variable: MAW <int>
Ovaj skup podataka bilježi samo informacije kada stanica detektuje ribu, tj. ako neku ribu nije snimila neka stanica, onda ti podaci neće biti u tabeli. To znači da će izlaz biti popunjen sa NA.
Međutim, u ovom slučaju znamo da izostanak zapisa znači da riba nije viđena, pa možemo koristiti argument values_fill u funkciji pivot_wider() i popunite ove nedostajuće vrijednosti nulama:
Generiranje naziva stupca iz više izvornih varijabli
Zamislite da imamo tabelu koja sadrži kombinaciju proizvoda, zemlje i godine. Za generiranje testnog datumskog okvira, možete pokrenuti sljedeći kod:
df <- expand_grid(
product = c("A", "B"),
country = c("AI", "EI"),
year = 2000:2014
) %>%
filter((product == "A" & country == "AI") | product == "B") %>%
mutate(value = rnorm(nrow(.)))
#> # A tibble: 45 x 4
#> product country year value
#> <chr> <chr> <int> <dbl>
#> 1 A AI 2000 -2.05
#> 2 A AI 2001 -0.676
#> 3 A AI 2002 1.60
#> 4 A AI 2003 -0.353
#> 5 A AI 2004 -0.00530
#> 6 A AI 2005 0.442
#> 7 A AI 2006 -0.610
#> 8 A AI 2007 -2.77
#> 9 A AI 2008 0.899
#> 10 A AI 2009 -0.106
#> # … with 35 more rows
Naš zadatak je proširiti okvir podataka tako da jedna kolona sadrži podatke za svaku kombinaciju proizvoda i zemlje. Da biste to učinili, samo unesite argument imena_od vektor koji sadrži imena polja koja se spajaju.
Također možete primijeniti specifikacije na funkciju pivot_wider(). Ali kada se podnese pivot_wider() specifikacija radi suprotnu konverziju pivot_longer(): Kolone navedene u .name, koristeći vrijednosti iz .value i druge kolone.
Za ovaj skup podataka možete generirati prilagođenu specifikaciju ako želite da svaka moguća kombinacija zemlje i proizvoda ima svoj stupac, a ne samo one prisutne u podacima:
#> # A tibble: 4 x 4
#> .name product country .value
#> <chr> <chr> <chr> <chr>
#> 1 A_AI A AI value
#> 2 A_EI A EI value
#> 3 B_AI B AI value
#> 4 B_EI B EI value
df %>% pivot_wider(spec = spec) %>% head()
#> # A tibble: 6 x 5
#> year A_AI A_EI B_AI B_EI
#> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 2000 -2.05 NA 0.607 1.20
#> 2 2001 -0.676 NA 1.65 -0.114
#> 3 2002 1.60 NA -0.0245 0.501
#> 4 2003 -0.353 NA 1.30 -0.459
#> 5 2004 -0.00530 NA 0.921 -0.0589
#> 6 2005 0.442 NA -1.55 0.594
Nekoliko naprednih primjera rada s novim tidyr konceptom
Čišćenje podataka koristeći skup podataka o prihodima i stanarini u SAD kao primjer.
Skup podataka us_rent_income sadrži informacije o srednjem prihodu i stanarini za svaku državu u SAD-u za 2017. (skup podataka dostupan u paketu tidycensus).
us_rent_income
#> # A tibble: 104 x 5
#> GEOID NAME variable estimate moe
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 01 Alabama income 24476 136
#> 2 01 Alabama rent 747 3
#> 3 02 Alaska income 32940 508
#> 4 02 Alaska rent 1200 13
#> 5 04 Arizona income 27517 148
#> 6 04 Arizona rent 972 4
#> 7 05 Arkansas income 23789 165
#> 8 05 Arkansas rent 709 5
#> 9 06 California income 29454 109
#> 10 06 California rent 1358 3
#> # … with 94 more rows
U obliku u kojem su podaci pohranjeni u skupu podataka us_rent_income rad sa njima je izuzetno nezgodan, pa bismo želeli da kreiramo skup podataka sa kolonama: najam, rent_moe, doći, prihod_moe. Postoji mnogo načina za kreiranje ove specifikacije, ali glavna stvar je da moramo generirati svaku kombinaciju varijabilnih vrijednosti i procjena/moea zatim generirajte naziv stupca.
Ponekad je za dovođenje skupa podataka u željeni oblik potrebno nekoliko koraka.
Skup podataka world_bank_pop sadrži podatke Svjetske banke o stanovništvu svake zemlje između 2000. i 2018. godine.
Naš cilj je da kreiramo uredan skup podataka sa svakom promenljivom u sopstvenoj koloni. Nije jasno koji su tačno koraci potrebni, ali počećemo s najočiglednijim problemom: godina je raspoređena u više kolona.
Da biste to popravili, morate koristiti funkciju pivot_longer().
Sljedeći korak je da pogledate varijablu indikatora. pop2 %>% count(indicator)
#> # A tibble: 4 x 2
#> indicator n
#> <chr> <int>
#> 1 SP.POP.GROW 4752
#> 2 SP.POP.TOTL 4752
#> 3 SP.URB.GROW 4752
#> 4 SP.URB.TOTL 4752
Gdje je SP.POP.GROW rast populacije, SP.POP.TOTL je ukupna populacija, a SP.URB. * ista stvar, ali samo za urbana područja. Podijelimo ove vrijednosti na dvije varijable: područje - područje (ukupno ili urbano) i varijablu koja sadrži stvarne podatke (stanovništvo ili rast):
Tabelarno sastavljanje ove liste je prilično teško jer ne postoji varijabla koja identifikuje koji podaci pripadaju kom kontaktu. To možemo popraviti tako što ćemo primijetiti da podaci svakog novog kontakta počinju s "name", tako da možemo kreirati jedinstveni identifikator i povećati ga za jedan svaki put kada stupac polja sadrži vrijednost "name":
#> # A tibble: 6 x 3
#> field value person_id
#> <chr> <chr> <int>
#> 1 name Jiena McLellan 1
#> 2 company Toyota 1
#> 3 name John Smith 2
#> 4 company google 2
#> 5 email [email protected] 2
#> 6 name Huxley Ratcliffe 3
Sada kada imamo jedinstven ID za svaki kontakt, možemo pretvoriti polje i vrijednost u stupce:
#> # A tibble: 3 x 4
#> person_id name company email
#> <int> <chr> <chr> <chr>
#> 1 1 Jiena McLellan Toyota <NA>
#> 2 2 John Smith google [email protected]
#> 3 3 Huxley Ratcliffe <NA> <NA>
zaključak
Moje lično mišljenje je da je novi koncept tidyr zaista intuitivniji i značajno superiorniji u funkcionalnosti u odnosu na stare funkcije spread() и gather(). Nadam se da vam je ovaj članak pomogao da se nosite pivot_longer() и pivot_wider().