Tabuľkové údaje, ktoré majú čistú, usporiadanú (angl. tidy) štruktúru, sa vyznačujú týmito vlastnosťami:
každá premenná má svoj stĺpec,
každé pozorovanie je vo svojom riadku a
každá hodnota má svoju bunku v tabuľke.
Zatiaľ čo čisté dáta sú všetky rovnako čisté, tie neporiadne sú neporiadne svojim vlastným spôsobom. Výhoda čistých údajov spočíva – podobne ako pri iných štandardoch – v dostupnosti väčšieho počtu a ľahšie pochopiteľných nástrojov na prácu s dátami. Príkladom takého subsystému nástrojov v prostredí R je súbor balíkov tidyverse, do ktorého patria tieto známe (i menej známe) balíky:
tibble – vylepšená koncepcia dátových rámcov a metód manipulácie s nimi,
readr – rýchly a jednoduchý spôsob importu tabuľkových údajov (prednostne do formátu tibble),
tidyr – nástroje na vytvorenie čistých dát z tých neporiadnych (Wickham a Henry 2020),
dplyr – gramatika manipulácie s dátami,
ggplot2 – vizualizáčný systém založený na gramatike grafiky,
purrr – konzistentný súbor nástrojov funkcionálneho programovania,
stringr – súbor nástrojov uľahčujúci prácu so znakovými reťazcami.
Hoci sa zdá, že princípy poriadku v tabuľkových dátach sú zjavné a zdá sa nám to zvláštne, že by dáta v praxi mali vyzerať inak, opak je realitou. Väčšia časť zaznamenaných súborov údajov nie je v poriadku, a to najmä z toho dôvodu, že:
veľa ľudí nie je oboznámených s princípmi čistých údajov, a je ťažké k nim prirodzene dôjsť, kým človek s dátami nestrávi dostatočne veľa času,
dáta sú často organizované pre úplne iný účel než analýzu.
To znamená, že pre seriózny matematický rozbor údajov je vo väčšine prípadov potrebné čistenie. V prvom kroku treba rozpoznať, čo v tabuľke predstavuje premenné a čo pozorovania. Niekedy je to ľahké, inokedy si to vyžaduje konzultáciu s autormi datasetu. V druhom kroku treba vyriešiť jeden z dvoch bežných problémov (iba zriedka oboch naraz):
jedna premenná sa rozprestiera cez viacero stĺpcov,
jedno pozorovanie je rozstratené po viacerých riadkoch.
Na vyriešenie týchto dvoch problémov budú potrebné dva kľúčové nástroje balíku tidyr : funkcie pivot_longer, pivot_wider. (Slovo pivoting sa dá preložiť ako otáčanie.)
5.2 Zber stĺpcov
V prvom probléme názvy niektorých stĺpcov v skutočnosti nepredstavujú názvy premenných ale hodnoty jednej premennej. Napr. v tabuľke
table4a
# A tibble: 3 × 3
country `1999` `2000`
<chr> <dbl> <dbl>
1 Afghanistan 745 2666
2 Brazil 37737 80488
3 China 212258 213766
sú názvy druhého a tretieho stĺpca hodnotami premennej (nazvime ju) year, pričom hodnoty v bunkách týchto stĺpcov sú počty pozitívnych prípadov, čiže premennej cases. Cieľom je vytvoriť stĺpce pre tieto dve premenné, naplniť ich hodnotami (roky a počty prípadov) a staré stĺpce zmazať.
# A tibble: 6 × 3
country year cases
<chr> <chr> <dbl>
1 Afghanistan 1999 745
2 Afghanistan 2000 2666
3 Brazil 1999 37737
4 Brazil 2000 80488
5 China 1999 212258
6 China 2000 213766
S tabuľkou table4a súvisí tabuľka table4b, ktorá namiesto počtu pozitívnych prípadov obsahuje celkový počet v populácii. Najprv si ju zobrazíme, potom „otočíme” na dlhý (a čistý) formát a znovu zobrazíme.2
# A tibble: 3 × 3
country `1999` `2000`
<chr> <dbl> <dbl>
1 Afghanistan 19987071 20595360
2 Brazil 172006362 174504898
3 China 1272915272 1280428583
# A tibble: 6 × 3
country year population
<chr> <chr> <dbl>
1 Afghanistan 1999 19987071
2 Afghanistan 2000 20595360
3 Brazil 1999 172006362
4 Brazil 2000 174504898
5 China 1999 1272915272
6 China 2000 1280428583
Tabuľky table4a_tidy a table4b_tidy sa teraz prekrývajú v dvoch stĺpcoch, každá však obsahuje aj unikátne stĺpce, je teda prirodzené ich zlúčiť, aby údaje z nich boli dostupné na jednom mieste. To sa dosiahne funkciou left_join z balíka dplyr.
dplyr::left_join(table4a_tidy, table4b_tidy, by = dplyr::join_by(country, year))
# A tibble: 6 × 4
country year cases population
<chr> <chr> <dbl> <dbl>
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
Okrem funkcie left_join, ktorá zachováva všetky riadky prvého argumentu, sú v balíku dplyr k dispozícii aj funkcie right_join (zachová riadky druhého argumentu), inner_join (všetky riadky, ktoré sú v oboch zároveň) a full_join (všetky riadky, ktoré sú aspoň v jednom).
5.3 Zber riadkov
V tabuľke table2 nastáva druhý zo spominaných problémov, pozorovania sú rozložené do dvoch riadkov:
table2
# A tibble: 12 × 4
country year type count
<chr> <dbl> <chr> <dbl>
1 Afghanistan 1999 cases 745
2 Afghanistan 1999 population 19987071
3 Afghanistan 2000 cases 2666
4 Afghanistan 2000 population 20595360
5 Brazil 1999 cases 37737
6 Brazil 1999 population 172006362
7 Brazil 2000 cases 80488
8 Brazil 2000 population 174504898
9 China 1999 cases 212258
10 China 1999 population 1272915272
11 China 2000 cases 213766
12 China 2000 population 1280428583
Potrebujeme identifikovať stĺpec, v ktorom sú názvy premenných (čiže type) a stĺpec, v ktorom sú ich hodnoty (count), zvyšok je práca funkcie inverznej ku pivot_longer, čiže funkcie pivot_wider:
# A tibble: 6 × 4
country year cases population
<chr> <dbl> <dbl> <dbl>
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
Zjednodušene povedané, pivot_longer() tabuľky zoštíhľuje a predlžuje, naopak pivot_wider() robí tabuľky širšie a kratšie.
5.4 Rozdelenie a spojenie stĺpcov
Iný problém nastane, ak stĺpec obsahuje viac než jednu premennú, ako je tomu v nasledujúcej tabuľke,
table3
# A tibble: 6 × 3
country year rate
<chr> <dbl> <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
kde sa pod názvom rate nachádza podiel dvoch premenných, cases/population. Riešením je použiť funkciu separate,
table3 |>separate(rate, into =c("cases", "population"), sep ="/", convert =TRUE)
# A tibble: 6 × 4
country year cases population
<chr> <dbl> <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
kde argument sep definuje oddeľovací znak (môže byť aj regulárny výraz, prípadne počet znakov od začiatku) a argument convert povolí konverziu reťazca na číslo (alebo logickú hodnotu či NA). Niekedy môže byť užitočné rozdeliť jedno číslo (rok) na viacero (storočie, rok), hoci to dáta robí menej čistými:
table3 |>separate(col = year, into =c("century", "year"), sep =2)
# A tibble: 6 × 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
Inverznú operáciu ku separate zabezpečuje funkcia unite, ktorá môže napraviť rozptýlenie hodnôt jednej premennej v bunkách viacerých stĺpcov (čiže nie v ich názvoch), napr.
table5 |>print() |>unite(col = new, century, year, sep ="")
# A tibble: 6 × 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
# A tibble: 6 × 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
5.5 Chýbajúce hodnoty
Problémy pri príprave údajov spôsobuje aj absencia určitého počtu hodnôt. Hodnota môže byť chýbajúca
explicitne, označená znakom NA,
implicitne, jednoducho v údajoch chýba.
Povedané trochu nadnesene: explicitne chýbajúca hodnota je prítomnosť absencie; implicitná je zas absencia prítomnosti. Dokážete oba prípady identifikovať v nasledujúcej tabuľke?
V dôsledku toho, ako sú datasety reprezentované, možno z implicitne chýbajúcej hodnoty urobiť explicitnú (NA), napr. konverziou na široký formát a späť
ktorá vezme súbor stĺpcov, vytvorí všetky kombinácie a doplní k nim originálne dáta, pričom do ostatných pozícií doplní NA.
Niekedy explicitne chýbajúca hodnota môže znamenať, že zastupuje duplikát predošlej hodnoty (a nikto sa neobťažoval doplniť ju). Vtedy sa na nahradenie NA použije funkcia fill tak, ako v nasledujúcom príklade:
V poslednej časti predošlej kapitoly o vizualizácii pomocou ggplot2 sme si ukázali tématickú mapu rozšírenia koronavírusu. Hoci dáta boli v čistom tvare, predsa ich bolo v dôsledku rozdielnych štandardov v názvosloví krajín potrebné dočisťovať, aby sa dali zlúčiť dva datasety (hranice krajín a vývoj pandémie).
Siahnime teraz po iných údajoch CSSE Johns Hopkins University. Úlohou bude zobraziť časový rad počtu úmrtí v dôsledku COVID-19 vo vybraných krajinách odo dňa výskytu prvého prípadu (po dátum 2.2.2022). Inšpirácia pochádza z blogu (Kajzar 2020). Pre zmenu, na načítanie dát bude použitý balík readr (a jeho používateľsky jednoduchšia funkcia read_csv):
dat <- readr::read_csv("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv" )
Ak už odkaz nie je platný, je možné, že v ňom došlo len k drobnej zmene a internetovým vyhľadávačom sa dá nájsť aktuálna poloha súboru. V horšom prípadne je potrebné nájsť úplne nový zdroj údajov v podobnom formáte. Pre ľahší prístup je k dispozícii aj záloha súboru.
dat <- readr::read_csv("data/time_series_covid19_deaths_global.csv")dat[1:7]
# A tibble: 281 × 7
`Province/State` `Country/Region` Lat Long `1/22/20` `1/23/20` `1/24/20`
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 <NA> Afghanistan 33.9 67.7 0 0 0
2 <NA> Albania 41.2 20.2 0 0 0
3 <NA> Algeria 28.0 1.66 0 0 0
4 <NA> Andorra 42.5 1.52 0 0 0
5 <NA> Angola -11.2 17.9 0 0 0
6 <NA> Antigua and Bar… 17.1 -61.8 0 0 0
7 <NA> Argentina -38.4 -63.6 0 0 0
8 <NA> Armenia 40.1 45.0 0 0 0
9 Australian Capit… Australia -35.5 149. 0 0 0
10 New South Wales Australia -33.9 151. 0 0 0
# ℹ 271 more rows
Údaje sú – zjavne – v širokom formáte, pretože dátum sa rozprestiera v záhlaví stĺpcov. Každý riadok zodpovedá jednej krajine (prípadne jej provincii). Po odstránení referenčnej polohy a provincie (keďže naše cieľové krajiny žiadne nemajú) a po transformácii (pivot_longer) bude každý riadok unikátnou kombináciou krajiny a dátumu. Zároveň je vidieť, že dátum je kódovaný tvare mm/dd/yy, teda ho treba previesť do formy yyyy-mm-dd, ktorá je v našich končinách čitateľnejšia. Na to sa hodia nástroje z balíku lubridate.
data_to_plot <- dat |> dplyr::select(-"Province/State", -Lat, - Long) |> dplyr::rename(Country ="Country/Region") |>pivot_longer(-Country, names_to ="Date", values_to ="Deaths") |> dplyr::mutate(Date = lubridate::mdy(Date)) |> dplyr::filter(Country %in%c("Slovakia","Czechia", "Hungary", "Poland", "Ukraine"), Deaths >=1, Date <="2022-02-02")data_for_labels <- data_to_plot |> dplyr::group_by(Country) |> dplyr::slice(which.max(Date))data_to_plot |>ggplot(mapping =aes(x = Date, y = Deaths, group = Country, color = Country)) +geom_line() +labs(x ="dátum", y =NULL, title ="Počet úmrtí v okolí Slovenska") +# umiestnenie popisu radov do grafu (pre efekt)theme(legend.position ="none") +geom_text(data = data_for_labels, aes(label = Country), hjust =-0.1, vjust =0) +scale_x_date(expand =c(0.15, 1)) # vyhradenie miesta za poslednou hodnotou
Aby bola zohľadnená aj ľudnatosť krajiny, vyjadrime úmrtnosť na vírus v relatívnej mierke ako podiel počtu prípadov na 1 milión obyvateľov. Na to je potrebný dataset o počte obyvateľov. Načítame najmenší CSV súbor, aký sa dá zbežným hľadaním na internete nájsť.
# A tibble: 263 × 5
Country Country_Code Year_1960 Year_1961 Year_1962
<chr> <chr> <dbl> <dbl> <dbl>
1 Aruba ABW 54211 55438 56225
2 Afghanistan AFG 8996351 9166764 9345868
3 Angola AGO 5643182 5753024 5866061
4 Albania ALB 1608800 1659800 1711319
5 Andorra AND 13411 14375 15370
6 Arab World ARB 92490932 95044497 97682294
7 United Arab Emirates ARE 92634 101078 112472
8 Argentina ARG 20619075 20953077 21287682
9 Armenia ARM 1874120 1941491 2009526
10 American Samoa ASM 20013 20486 21117
# ℹ 253 more rows
Štruktúra je podobná ako v predošlom csv súbore, nám bude stačiť posledný rok, názvy niektorých krajín však treba prekódovať (v komplexnejšom prípade pomocou balíku countrycode). Potom už len ponechať predtým vybrané krajiny, spojiť s datasetom úmrtí, vytvoriť relatívnu početnosť a zobraziť. Všimnime si použitie zámena .data vtedy, keď treba na premenné odkázať znakovým reťazcom (napr. ak názov obsahuje medzery):
dat_population |> dplyr::transmute(Country = dplyr::recode(Country, "Slovak Republic"="Slovakia", "Czech Republic"="Czechia"), Population = Year_2016) |> dplyr::filter(Country %in% data_for_labels$Country) |> dplyr::right_join(data_to_plot, by ="Country") |> dplyr::mutate("Deaths per milion"= Deaths/Population*1e+06) |>ggplot() +aes(x = Date, y = .data[["Deaths per milion"]], group = Country, color = Country) +geom_line()
Zdá sa, že Ukrajina je na tom s úmrtnosťou na COVID-19 na začiatku roku 2022 najlepšie spomedzi krajín strednej Európy – nielen z pohľadu relatívneho počtu, ale aj rýchlosti rastu.
5.7 Import excelovských tabuliek
Ľudová kreativita sa často dá vidieť aj v tzv. excelovských tabuľkách. Dáta sa v tabuľkových procesoroch typu MS Excel či LibreOffice Calc vytvárajú predovšetkým s cieľom výmeny informácií medzi bežnými používateľmi, pričom sa využívajú základné matematické operácie ako súčet, priemer násobenie a podobne. Pre grafické zobrazenie si už softvér vyžiada určitú organizáciu údajov (premenné v stĺpcoch alebo riadkoch).
Aby sme dokázali analyticky pracovať s dátami, ktoré boli pripravené v “ľudsky čitateľnej” podobe, potrebujeme ich previesť do “strojovo zrozumiteľnej” podoby, teda vyčistiť. V Rku na to poslúžia dva balíky:
tidyxl pre pokročilý import štruktúry excelovských hárkov,
unpivotr pre konverziu zložitého a nepravidelného usporiadania údajov do stĺpcovej, čistej formy.
Pomôžeme si niekoľkými ilustráciami z knihy Garmonsway (2023).
Napríklad už umiestnenie premenných do riadkov (čiže pozorovaní do stĺpcov) si vyžaduje pomerne komplikovaný zásah klasickým spôsobom. V nasledujúcej tabuľke sú dve premenné a tri pozorovania (na troch ďeťoch ako štatistických jednotkách).
Import tabuľky tradičným spôsobom pomocou balíka readxl zo systému tidyverse je potrebné manuálne doplniť krokmi ako transpozícia a presun názvov stĺpcov či riadkov.
readxl::read_excel(path, sheet ="transposed") |>print() |># zobrazenie importovanej tabuľkyt() |># transponovanie, výstupom je vždy matica (\(x) `colnames<-`(x, x[1,]))() |># umiestniť prvý riadok do hlavičkytail(-1) |># vynechanie prvého riadkudata.frame() |># matrix -> data.frame tibble::rownames_to_column("Name") |># premiestniť názvy riadkov do prvého stĺpcatype.convert(as.is =TRUE) |># automatická konverzia char -> num tibble::as_tibble() # nepovinný krok pre zobrazenie ako tibble
# A tibble: 1 × 4
Name Matilda Nicholas Olivia
<chr> <dbl> <dbl> <dbl>
1 Age 1 3 5
# A tibble: 3 × 2
Name Age
<chr> <int>
1 Matilda 1
2 Nicholas 3
3 Olivia 5
# Alternatívny spôsob:# dat |> # as.matrix() |> # data.frame -> matrix# (\(x) rbind(colnames(x), x))() |> # spojenie názvov so zvyškom # (`colnames<-`)(NULL) |> # zmazanie názvov# t() |> # data.frame() |> # matrix -> data.frame# (\(x) `colnames<-`(x, x[1,]))() |> # skopírovať názvy riadkov do prvého stĺpca# (`[`)(-1,) # to isté ako tail(-1)
Naproti tomu balík tidyxl importuje hárok excelovskej tabuľky v surovej podobe. Takto unpivotr môže pracovať s hlavičkami, nech už sa nachádzajú “hore” alebo “vľavo”. Vyskúšajte si zobraziť výstupy nasledujúcich príkazov krok po kroku (označením reťaze od začiatku po konkrétny príkaz).
# A tibble: 3 × 2
Name Age
<chr> <dbl>
1 Matilda 1
2 Nicholas 3
3 Olivia 5
Pôvodná (nečistá) i nová (čistá) tabuľka predstavujú rovnocenné zobrazenia údajov, ktoré sú funkciou tidyxl::xlsx_cells uložené v univerzálnom formáte. Ten tak môže sprostredkovať transformáciu medzi nimi.
5.7.2 Rozklad viacvrstvovej hlavičky
Tabuľky s niekoľkými úrovňami hlavičky (či už hore, alebo vľavo) sú výhodné na zobrazenie viacerých premenných v (pre človeka) prehľadnej podobe, no z hľadiska strojového spracovania majú od čistého formátu ďaleko. V nasledujúcom príklade tabuľka obsahuje 5 premenných: Name, Sex, Course, Subject, Score.
Pomocou rectify získame po načítaní náhľad na zvolený hárok.
dat <- tidyxl::xlsx_cells(path, sheets ="pivot-annotations")dat |> unpivotr::rectify()
# A tibble: 6 × 7
`row/col` `2(B)` `3(C)` `4(D)` `5(E)` `6(F)` `7(G)`
<int> <chr> <chr> <chr> <chr> <chr> <chr>
1 2 <NA> <NA> Female <NA> Male <NA>
2 3 <NA> <NA> Matilda Olivia Nicholas Paul
3 4 Humanities Classics 1 2 3 0
4 5 <NA> History 3 4 5 1
5 6 Performance Music 5 6 9 2
6 7 <NA> Drama 7 8 12 3
Najprv funkciou behead upraceme horné vrstvy hlavičky, potom tie vľavo.
dat |> dplyr::filter(!is_blank) |># ignorovanie prázdnych buniek dplyr::select(row, col, data_type, character, numeric) |>#zaujímajú nás iba číselné a znakové hodnoty unpivotr::behead("up-left", Sex) |> unpivotr::behead("up", `Name`) |> unpivotr::behead("left-up", Field) |> unpivotr::behead("left", Subject) |> dplyr::rename(Score = numeric) |> dplyr::select(-row, -col, -character, -data_type) # info o štruktúre už netreba
# A tibble: 16 × 5
Score Sex Name Field Subject
<dbl> <chr> <chr> <chr> <chr>
1 1 Female Matilda Humanities Classics
2 2 Female Olivia Humanities Classics
3 3 Female Matilda Humanities History
4 4 Female Olivia Humanities History
5 3 Male Nicholas Humanities Classics
6 0 Male Paul Humanities Classics
7 5 Male Nicholas Humanities History
8 1 Male Paul Humanities History
9 5 Female Matilda Performance Music
10 6 Female Olivia Performance Music
11 7 Female Matilda Performance Drama
12 8 Female Olivia Performance Drama
13 9 Male Nicholas Performance Music
14 2 Male Paul Performance Music
15 12 Male Nicholas Performance Drama
16 3 Male Paul Performance Drama
Hotovo.
5.8 Cvičenie
Načítajte dataset USArrest z balíku datasets, zoznámte sa s významom stĺpcov. Ktoré predstavujú druh zločinu?
Pomocou pipe operátora vytvorte sekvenciu príkazov (v zátvorke je tip, čo pri tom použiť), ktorá
začne datasetom USArrest,
pridá premennú state s názvami riadkov, (funkcie mutate alebo cbind, rownames(.))
prevedie tabuľku do dlhého formátu, teda zoskupí názvy zločinov do premennej crime a počty zatknutí do premennej cases, (pivot_longer)
bodovým grafom zobrazí vzťah medzi percentom ľudnatosti v mestských oblastiach (na osi x) a počtom prípadov (v logaritmickej mierke na osi y), pričom farebne sú odlíšené jednotlivé zločiny. (ggplot + scale_y_login10)
V predošlom príklade zmeňte bodový graf na textový (s názvami štátov) a jednotlivé zločiny zobrazte vo svojom vlastnom grafe pod sebou. Zvážte použitie individuálnej mierky na osi y a prispôsobte veľkosť textu. (geom_text + facet_grid, scales, size)
Adam, Bibiana a Cindy sa prihlásili na kurz. Pred ním, počas aj po ňom písali test (hodnotenie 0-10 bodov), výsledky však neboli importované poriadne, niečo sa z nich možno aj stratilo (tabuľka definovaná nižšie, poradie riadkov zodpovedá abecednému radeniu mien študentov). Príkazy sa snažte reťaziť a preferujte funkcie z balíkov ekosystému tidyverse.
Úlohy:
Rozdeľte hodnotenie do stĺpcov podľa času testovania, pripojte mená a pomocou ggplot zobrazte vývoj ich vedomostí do spoločného grafu.
Kto z nich sa ulieval a kto snažil?
Čo by ste zmenili vo funkcii separate, ak by ste sa dozvedeli, že Cindy sa prvého testu nemohla zúčastniť?
Wickham, Hadley, a Garrett Grolemund. 2016. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. 1st vyd. O’Reilly Media, Inc. https://r4ds.had.co.nz/.