Pakett tidyverse

Tidyverse

Pakett tidyverse on andmeanalüüsi n-ö komplekspakett, mis koondab enda alla terve hulga teisi pakette ja nende funktsioone (sh ggplot2). Ehkki enamikku sellest, mida need funktsioonid teevad, saab ära teha ka teiste pakettide ja funktsioonidega, ühendab tidyverse’i pakette sarnane kasutusloogika ja sarnaste andmestruktuuride kasutus.

Tidyverse’i pakette saab installida ka ükshaaval (nt install.packages("tidyr")). Kiire ülevaate üksikute pakettide põhifunktsioonidest saad nt erinevatest spikritest:

Selles praktikumis kasutame tidyverse’i kollektsioonist pakette dplyr ja tidyr (andmete töötlemiseks) ja ggplot2 (andmete visualiseerimiseks).

Tidyverse’i installimiseks kasuta käsku install.packages("tidyverse") ning laadimiseks library(tidyverse).

# install.packages("tidyverse")
library(tidyverse)

Ära muretse, kui paketi laadimise käigus konsooli midagi trükitakse (kui tegemist pole just erroriga).

Loeme sisse ka valimiste andmestiku, mida siin põhiliselt kasutame.

valimised <- read.delim("data/kandidaadid_2023.csv")

Torud

Tidyverse’i pakette ühendab sarnane kindlas järjekorras asjade tegemise loogika. Üks paketi oluline omadus on võimalus kasutada nn torusid ehk pipe’e (sümbol %>%), mille funktsioon tuleb tegelikult tidyverse’iga koos installitavast ja laaditavast paketist nimega magrittr.
Torud lubavad tõsta R-i n-ö pesastatud funktsioonidest funktsioone välja nende käivitamise järjekorra alusel, suunates iga eelmise käsu väljundi järgmisse käsku.
Kui R-i baaspaketis on kõige viimane käsk graafiliselt kõige esimene, siis torud lubavad teha neid asju loogilisemas järjekorras. Näiteks kui tahame 1) võtta tabelist kandidaatide soo tulba, 2) teha soo tunnuse väärtustest sagedustabeli, 3) leida absoluutsageduste põhjal suhtelised sagedused, siis oleks baaspaketi ja tidyverse paketi lahenduskäigud graafiliselt justkui peegelpildis.

# Leia mees- ja naiskandidaatide suhtelised sagedused
# baaspakett
prop.table(table(valimised$sugu))

# tidyverse
valimised$sugu %>% table() %>% prop.table()

# Kui saad teate 'could not find function "%>%"', tähendab see seda, 
# et tidyverse'i pakett on ilmselt lihtsalt library(tidyverse) käsuga laadimata jäänud.

Või kui tahaksime lahendada natuke keerulisemat ülesannet, nt leida kandidaatide ametinimetustes (vaba tekstiga tunnus) esinevad 50 sagedamat sõna, oleks baaspaketi käsk üpris keeruliselt loetav.

# baaspakett
head(sort(table(gsub("[[:punct:]]", "", tolower(unlist(str_split(valimised$amet, " "))))), decreasing = T), 50) 

# tidyverse
valimised$amet %>% # võta valimiste andmestikust tulp "amet"
  str_split(" ") %>% # lõika ameti kirjeldused tühikute koha pealt sõnadeks
  unlist() %>% # tee sõnalistist tavaline vektor
  tolower() %>% # tee kõik vektori sõnad väiketähelisteks
  gsub("[[:punct:]]", "", .) %>% # kustuta sõnade küljest kirjavahemärgid
  table() %>% # tee puhastatud sõnadest sagedustabel
  sort(decreasing = T) %>% # sorteeri sagedustabel kahanevas järjekorras
  head(50) # võta sorteeritud tabelist esimesed 50 sõna

Lisame koodi ühe rea, mis jätaks ametinimetustest alles ainult viimase sõnavormi (nt Riigikogu liige -> liige).

# tidyverse
valimised$amet %>% 
  str_split(" ") %>% 
  lapply(., function(x) x[length(x)]) %>% # igast ametinimetusest võta ainult viimane sõnavorm
  unlist() %>% 
  tolower() %>%
  gsub("[[:punct:]]", "", .) %>% 
  table() %>%
  sort(decreasing = T) %>%
  head(50)
## .
##          liige        esimees       juhataja         nõunik       direktor 
##            227             45             41             34             33 
##      pensionär     vallavanem        õpetaja      tegevjuht           juht 
##             31             26             24             23             20 
##  abivallavanem    abilinnapea     üliõpilane     aseesimees   projektijuht 
##             19             17             14             10             10 
##   koordinaator       minister       linnapea             oü         puudub 
##              8              8              7              7              7 
##          vanem      ettevõtja         omanik      professor    spetsialist 
##              7              6              6              6              6 
##        treener       õppejõud    arendusjuht         jurist         kodune 
##              6              6              5              4              4 
##     konsultant       näitleja peaspetsialist       perearst      toimetaja 
##              4              4              4              4              4 
##         tudeng            abi           arst       disainer         muusik 
##              4              3              3              3              3 
##      müügijuht     produtsent  raamatupidaja         teadur  vabakutseline 
##              3              3              3              3              3 
##    ajakirjanik      asetäitja       audiitor        dotsent        ekspert 
##              2              2              2              2              2

Nagu baaspaketis ei ole vahet, kas komadega eraldatud argumendid on eraldi ridadel või ühel real, ei ole seda vahet ka tidyverse’is. Samuti ei ole vahet, kas torudega eraldatud funktsioonid jätta samale reale või viia need eraldi ridadele. Tidyverse’i toru kiiremaks kasutamiseks võid kasutada ka klahvikombinatsiooni Ctrl+Shift+M (Macis Shift+Cmd+M).

Tõele au andes on uuemates R-i versioonides ka baaspaketis juba oma toru |>, mis töötab enam-vähem samamoodi nagu tidyverse’i toru. Selles aga ei saa näiteks viidata funktsioonis andmetele tagasi punktiga.

# Mitu erinevat eesnime kandidaatide hulgas leidub?

# töötab
gsub("^([^ ]+?) .*$", "\\1", valimised$nimi) |>
  unique() |>
  length()

# ei tööta
valimised$nimi |>
  gsub("^([^ ]+?) .*$", "\\1", .) %>%
  unique() |>
  length()

Kumba või kas üldse kasutada on maitse küsimus, aga kui juba muid tidyverse’i funktsioone on vaja kasutada, tundub loogilisem kasutada ka vastavat toru %>%.

Andmete filtreerimine ja valimine

Baaspaketis oleme harjunud andmestikest mingi konkreetse alamosa väljavõtmiseks kasutama kantsulgusid ning nende sees võrratusi. Kordamiseks põhilised võrratustes kasutatud tehted:

Sümbol Näide Selgitus Sobiv tunnusetüüp
== a == b a võrdub b-ga arvulised, kategoriaalsed, loogilised
!= a != b a ei võrdu b-ga arvulised, kategoriaalsed, loogilised
> a > b a on suurem kui b arvulised, loogilised
< a < b a on väiksem kui b arvulised, loogilised
>= a >= b a on suurem/võrdne b-ga arvulised, loogilised
<= a <= b a on väiksem/võrdne b-ga arvulised, loogilised

Lisaks on kasulik funktsioon is.na(), mis otsib välja ainult read või elemendid, mille väärtus on NA (nt is.na(a)) või ei ole NA (!is.na(a)).


Liittehted:

Sümbol Näide Selgitus
& a == b & a != c a on võrdne b-ga JA a ei ole võrdne c-ga
| a == b | a == c a on võrdne b-ga VÕI a on võrdne c-ga
%in% a %in% c(b, c, d) a väärtus on b VÕI c VÕI d
!%in% !a %in% c(b, c, d) a väärtus ei ole b EGA c EGA d

Keerulisemate võrratuste puhul võib pilt minna päris kirjuks.

# ainult Keskerakonna nimekirja kandidaatide andmed
valimised[valimised$nimekiri == "Eesti Keskerakond",]

# kõikide erakondade, v.a Keskerakonna nimekirja alla 30-aastaste kandidaatide andmed
valimised[valimised$nimekiri != "Eesti Keskerakond" & valimised$vanus < 30,]

# nende Eesti 200 või Roheliste kandidaatide nimed, kes on alla 30-aastased
valimised[(valimised$nimekiri == "Erakond Eesti 200" | valimised$nimekiri == "Erakond Eestimaa Rohelised") & valimised$vanus < 30, "nimi"]
# või
valimised[valimised$nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised") & valimised$vanus < 30, "nimi"]

# kõikide vähemalt 70-aastaste, v.a Eesti 200 ja Roheliste nimekirja kuuluvate kandidaatide nimed, sünniajad ja tähtkujud
valimised[valimised$nimekiri != "Erakond Eesti 200" & valimised$nimekiri != "Erakond Eestimaa Rohelised" & valimised$vanus >= 70, c("nimi", "sünniaeg", "tähtkuju")]
# või
valimised[!valimised$nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised") & valimised$vanus >= 70, c("nimi", "sünniaeg", "tähtkuju")]

Tidyverse lubab meil kasutada funktsiooni filter() ridade filteerimiseks ning funktsiooni select() tulpade valimiseks. Sealjuures säilitab select() andmetabeli struktuuri.

# ainult Keskerakonna nimekirja kandidaatide andmed
# valimised[valimised$nimekiri == "Eesti Keskerakond",]
valimised %>%
  filter(nimekiri == "Eesti Keskerakond")

# ainult nime tulp
valimised %>%
  select(nimi)

# kõik tulbad peale nime
valimised %>%
  select(-nimi)

Kui tahame pärast toru võtta mingi tulba välja vektorina (või faktorina), saame viidata tervele tabelile punktiga: ... %>% .$tulp. Punkt on nn placeholder, mis laseb torudega toimetades viidata vahetult eelmise käsu väljundile.

valimised %>%
  filter(vanus >= 70) %>%
  .$nimekiri

Tulba küsimiseks vektorina võib kasutada ka tidyverse’i funktsiooni pull(). Kui tahta funktsiooni kasutada faktoril, tuleb faktor esmalt tavaliseks tekstivektoriks teisendada.

valimised %>%
  filter(vanus >= 70) %>%
  pull(nimekiri)

Mitme tingimusega filtreerimiseks võime kasutada ära torusid ja rakendada igat filtrit eraldi käsuna või liita tingimused samamoodi, nagu seda saab teha baaspaketis.

# kõikide erakondade, v.a Keskerakonna nimekirja kandidaatide andmed, kes on alla 30-aastased
# valimised[valimised$nimekiri != "Eesti Keskerakond" & valimised$vanus < 30,]
valimised %>%
  filter(nimekiri != "Eesti Keskerakond") %>%
  filter(vanus < 30)
# või
valimised %>%
  filter(nimekiri != "Eesti Keskerakond", vanus < 30)
# või
valimised %>%
  filter(nimekiri != "Eesti Keskerakond" & vanus < 30)

# nende Eesti 200 või Roheliste kandidaatide nimed, kes on alla 30-aastased
# valimised[valimised$nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised") & valimised$vanus < 30, "nimi"]
valimised %>%
  filter(nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised"), vanus < 30) %>%
  select(nimi)
# võrdle
valimised %>%
  filter(nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised"), vanus < 30) %>%
  pull(nimi)

# kõikide vähemalt 70-aastaste, v.a Eesti 200 või Roheliste nimekirja kuuluvate kandidaatide nimed, sünniajad ja tähtkujud
# valimised[!valimised$nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa Rohelised") & valimised$vanus >= 70, c("nimi", "sünniaeg", "tähtkuju")]
valimised %>%
  filter(!nimekiri %in% c("Erakond Eesti 200", "Erakond Eestimaa ROhelised"), vanus >= 70) %>%
  select(nimi, sünniaeg, tähtkuju)

Saame filtreerimisel kasutada ka teiste pakettide (sh vaikimisi laaditud baaspaketi ja stats paketi) funktsioone.

# kandidaadid, kes said mediaanhäältesaagist rohkem hääli
valimised %>%
  filter(hääli_kokku > median(hääli_kokku))

Harjutused

Harjutus 1

Leia valimiste andmestikust torusid kasutades mõne sind huvitava kandidaadi (või mitme kandidaadi) kontaktandmed.

  1. Filtreeri andmetest nime tunnuse põhjal sobivad read.
  2. Vali andmestikust ainult kandidaadi nime ja kogu kontaktandmete tulbad.
valimised %>%
  filter(nimi == "KAJA KALLAS") %>%
  select(nimi, kontakt)

Harjutus 2

Laadi RStudiosse kursuse küsimustiku andmestik. Leia torusid kasutades, millisel aastal on olnud kursusel kõige rohkem kohvijoojaid. Ärme esialgu sellepärast muretseme, kui palju osalejaid mingil aastal üldse kursuselt läbi käis, ehkki see mõjutab oluliselt ka kohvijoojate absoluutarve.

  1. Filtreeri andmetest ainult kohvijoojate read.
  2. Võta välja ainult aasta tulp.
  3. Tee risttabel käsuga table().
  4. Leia moodi indeks käsuga which.max().
  5. Leia mood ise käsuga names().
load("data/kysimustik_2023.RData")
kysimustik %>% 
    filter(lemmikjook == "Kohv") %>%
    select(aasta) %>%
    table() %>%
    which.max() %>%
    names()

Andmete järjestamine

Andmete järjestamiseks mingi tulba väärtuste alusel võib kasutada funktsiooni arrange().

valimised %>%
  arrange(hääli_kokku) %>% # kasvavas järjekorras
  select(nimekiri, nimi, hääli_kokku, e.hääli)

valimised %>%
  arrange(desc(hääli_kokku)) %>% # kahanevas järjekorras
  select(nimekiri, nimi, hääli_kokku, e.hääli)

Võime järjestada andmeid ka mitme tunnuse järgi korraga.

valimised %>%
  arrange(vanus, sugu) %>% # vanus väiksemast suuremani, iga vanuse puhul kõigepealt M, siis N
  select(nimekiri, nimi, vanus, sugu, hääli_kokku, e.hääli)

Tulpade lisamine

Tulpade lisamiseks tabelisse saab kasutada funktsiooni mutate().

Lisame näiteks valimiste tabelisse tulba, kuhu arvutame iga kandidaadi häältesaagi ilma e-häälteta.

valimised %>%
  mutate(hääli_paberil = hääli_kokku-e.hääli)

Kui tahame, et lisatud tulbad ka andmestikku alles jääksid, peame andmestiku kas üle kirjutama või suunama lisatud tulpadega tabeli uude objekti.

# kirjutame andmestiku "valimised" üle nii, et sinna on lisatud tulp paberhäälte arvuga
valimised %>%
  mutate(hääli_paberil = hääli_kokku-e.hääli) -> valimised

Jooniste tegemine

Tidyverse’i üks suuri plusse on see, et me ei pea vaheväljundeid eraldi objektidesse salvestama, kui tahame neid ainult korraks konkreetseks eesmärgiks kasutada. Nii võime ka kogu andmete puhastamise ja korrastamise protsessi väljundi suunata otse graafikule.

# Kuva karpdiagrammil paberhäälte protsendi jaotumist valimisnimekirjades
valimised %>%
  mutate(paberhäälte_protsent = hääli_paberil/hääli_kokku*100) %>%
  ggplot(aes(y = reorder(nimekiri, paberhäälte_protsent), # järjesta nimekirjad protsendi tunnuse alusel (vaikimisi rühma keskmise järgi) 
             x = paberhäälte_protsent)) +
  geom_boxplot() +
  labs(y = "Nimekiri", x = "Paberhäälte protsent")

Harjutused

Harjutus 3

Näita joonisel, milline oli kandidaatide vanuste jaotus erakondades ning kas vanus mõjutas kuidagi ka (logaritmitud) häältesaaki.

  1. Tee ggplotiga all näidatud karpdiagramm. Punase vertikaalse joone saad funktsiooniga geom_vline(), mis tahab oma aes()-funktsiooni argumendiks xintercept’i ehk x-teljega lõikumise kohta. Selle väärtuseks peaks määrama terve andmestiku kandidaatide mediaanvanuse.
valimised %>%
  ggplot() +
  geom_boxplot(aes(y = nimekiri, x = vanus)) +
  geom_vline(aes(xintercept = median(vanus)), color = "red", linetype = "dashed", linewidth = 1)

  1. Tee joonisel näidatud hajuvusdiagramm vanuse ja häältesaagi vahelise suhte kohta nii, et häältesaaki ei näidataks mitte absoluutarvudes, vaid logaritmituna (log(tunnus)). Punktide läbipaistvust saab kontrollida argumendiga alpha, mille väärtus saab jääda 0 (täiesti läbipaistev) ja 1 (täiesti läbipaistmatu) vahele. Nn trendijoone saab joonistada funktsiooniga geom_smooth().
valimised %>%
  ggplot(aes(x = vanus, y = log(hääli_kokku))) +
  geom_point(alpha = 0.3) +
  geom_smooth()

Harjutus 4

Oletame, et keegi küsib meilt Keskerakonna nimekirjas kandideerinute meiliaadresse. Meiliaadressid on tulbas kontaktandmed, aga seal on ka telefoninumbrid, tavalised aadressid, Facebooki lehed jm. Peaksime niisiis kasutama pisut regulaaravaldisi, et meiliaadressid eraldi kätte saada ja uude tulpa lisada. Ära muretse, kui sa regulaaravaldisi veel ei tunne.

  1. Jäta alles ainult read, kus tunnuse nimekiri väärtus on “Eesti Keskerakond”.
  2. Tee uus tulp nimega meil, kuhu saad väärtused käsuga ifelse(test = grepl("@", kontakt), yes = str_split(kontakt, "; ") %>% unlist() %>% .[grepl("@", .)], no = ""). Käsus küsitakse esmalt, kas kandidaadi kontaktandmetes on üldse @-märki, kui on, siis lõigatakse kontaktandmed semikooloni ja tühiku järjendi koha pealt tükkideks ja jäetakse alles ainult see tükk, mis sisaldab ka @-märki. Kui meiliaadressi kontaktandmetes ei ole, jäetakse tulpa tühi väärtus.
  3. Vali ainult nimede ja meiliaadresside tulbad.
  4. Jäta alles ainult read, kus tunnuse meil väärtus ei ole tühi ("").
  5. Kirjuta tabel faili nimega Keskerakonna_meiliaadressid.txt nii, et tulpasid eraldaks tabulaator \t, teksti ümber ei oleks jutumärke, reanimesid ei kirjutataks faili kaasa ning faili kodeering oleks UTF-8.
valimised$kontakt # millise struktuuriga kontaktandmed on esitatud?

valimised %>%
  filter(nimekiri == "Eesti Keskerakond") %>%
  mutate(meil = ifelse(test = grepl("@", kontakt), # kui kontaktandmetes on @-sümbol,
                       yes = str_split(kontakt, "; ") %>% # siis lõika kontaktandmete tekst semikooloni ja tühiku järjendi koha pealt tükkideks,
                         unlist() %>% # tee tulemuseks saadud listist tavaline vektor
                         .[grepl("@", .)], # ja jäta alles ainult see tükk, mis sisaldab @-sümbolit
                       no = "")) %>% # kui @-sümbolit ei ole, ära pane meili tulpa midagi
  select(nimi, meil) %>%
  filter(meil != "") %>%
  write.table("Keskerakonna_meiliaadressid.txt", quote = F, sep = "\t", row.names = F, fileEncoding = "UTF-8")

Grupeerimine ja kokkuvõtted

Kõige lihtsam viis teha kokkuvõtteid, on lugeda väärtusi kokku. Kiireim viis selleks on kasutada funktsiooni count(), mis loeb täpsustatud tunnuste alusel (gruppides) vaatlused kokku ja esitab nende sagedused uues tabelis.

valimised %>% count()
valimised %>% count(nimekiri)
valimised %>% count(sugu)
valimised %>% count(nimekiri, sugu)

Kui tahame täpsustada ka, mis on sagedustega tulba nimi (vaikimisi on selleks n), saame count() funktsioonis kasutada argumenti name.

valimised %>% count(name = "kandidaate_kokku")
valimised %>% count(nimekiri, sugu, name = "arv")

Funktsioon summarise() laseb teha meie andmetest kokkuvõtteid lisaks vaatluste (gruppides) kokkulugemisele ka muul moel. Tulemus talletatakse samuti eraldi tabelitesse.

# kandidaatide arv kokku
valimised %>%
  summarise(kandidaate_kokku = n()) # funktsioon n() loeb vaatlusi kokku
# see töötab samamoodi nagu valimised %>% count(name = "kandidaate_kokku")

# kandidaatide arv, keskmine vanus, mediaanvanus
valimised %>%
  summarise(kandidaate_kokku = n(),
            keskmine_vanus = mean(vanus),
            mediaanvanus = median(vanus))

# erinevate eesnimede ja perekonnanimede arv
valimised %>%
  summarise(eesnimesid = gsub("^([^ ]+?) .*$", "\\1", nimi) %>% unique() %>% length(),
            perekonnanimesid = gsub("^.* ([^ ]+)$", "\\1", nimi) %>% unique() %>% length())

Samad arvandmed saaksime tabelita kujul kätte ka ilma summarise()-funktsioonita.

nrow(valimised)
mean(valimised$vanus)
median(valimised$vanus)
length(unique(gsub("^([^ ]+?) .*$", "\\1", valimised$nimi)))
length(unique(gsub("^.* ([^ ]+)$", "\\1", valimised$nimi)))

Oluliselt kasulikumaks muutub summarise() funktsioon aga siis, kui seda kasutada koos funktsiooniga group_by(), mis grupeerib andmeid ühe või enama tunnuse põhjal ning laseb kokkuvõtteid teha iga grupi kohta eraldi.

# Kandidaatide arv, keskmine vanus ja mediaanvanus valimisnimekirjades
valimised %>%
  group_by(nimekiri) %>%
  summarise(kandidaate_kokku = n(),
            keskmine_vanus = mean(vanus),
            mediaanvanus = median(vanus))

# Mees- ja naiskandidaatide arv, keskmine vanus, mediaanvanus, keskmine häältesaak ja keskmine paberhäälte protsent valimisnimekirjades
valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(kandidaate_kokku = n(),
            keskmine_vanus = mean(vanus),
            mediaanvanus = median(vanus),
            keskmine_häältesaak = mean(hääli_kokku),
            keskmine_paberhäälte_protsent = mean(hääli_paberil/hääli_kokku*100))

Sisuliselt teevad siin group_by() ja summarise() koos sama asja, mida funktsioon tapply(), mida oleme ka kursusel kasutanud.

tapply(valimised$vanus, valimised$nimekiri, mean)
tapply(valimised$vanus, list(valimised$nimekiri, valimised$sugu), mean)

Vahe on selles, et tapply() käsu väljund on vektor või maatriks, aga group_by()+summarise() annavad väljundiks data.frame tüüpi nn pika andmetabeli, kus iga grupeerivate tunnuste kombinatsioon on omaette real (nt Eesti Keskerakond + M, Eesti Keskerakond + N, Isamaa Erakond + M, Isamaa Erakond + N jne) ning kuhu võib lisada ka muid kokkuvõtteid.

Nagu ülal näidatud, võib grupeerida andmeid ka mitme tunnuse põhjal. group_by()+summarise() kombinatsioon hülgab pärast oma töö lõpetamist ühe grupeerimise aluseks oleva tunnuse, aga kui meil on grupeerimise aluseks mitu tunnust, siis teised jätab alles. Nii et pärast summarise() funktsiooni jooksutamist on meie andmed edasi grupeeritud ainult tunnuse nimekiri põhjal. Seega saame küsida iga nimekirja põhjal ainult seda rida (meeste või naiste rida), kus keskmine häältesaak on suurem. Tulemuseks saame tabeli, kus on erakonnad, kus mehed said keskmiselt rohkem hääli, ja erakonnad, kus naised said keskmiselt rohkem hääli.

valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(keskmine_häältesaak = mean(hääli_kokku)) %>% # andmed jäävad grupeerituks nimekirja alusel
  filter(keskmine_häältesaak == max(keskmine_häältesaak)) %>% # igas nimekirjas jäta alles ainult suurima häältesaagiga rida
  arrange(sugu, desc(keskmine_häältesaak)) # järjesta tabel soo ja (kahaneva) häältesaagi tulpade põhjal

Kui me loobuksime pärast summarise() funktsiooni kõikidest gruppidest (.groups = "drop" või ungroup()), saaksime vastuseks ainult ühe valimisnimekirja rea, kus on kõikide nimekirjade ja sugude lõikes kõige suurem keskmine häältesaak.

valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(keskmine_häältesaak = mean(hääli_kokku),
            .groups = "drop") %>% # loobume guppidest
  filter(keskmine_häältesaak == max(keskmine_häältesaak))

# või
valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(keskmine_häältesaak = mean(hääli_kokku)) %>%
  ungroup() %>% # loobume gruppidest
  filter(keskmine_häältesaak == max(keskmine_häältesaak))

Funktsioonid group_by()+summarise() ja count() ja teevad väärtusi kokku lugedes sisuliselt sama asja, ainult et count() ei jäta andmeid kunagi grupeerituks. Samuti on group_by()+summarise() väljundiks nn tibble-tüüpi tabel, count() väljundiks tavaline data.frame-tüüpi tabel.

valimised %>% group_by(nimekiri, sugu) %>% summarise(n = n()) # andmed jäävad nimekirja järgi grupeerituks
## `summarise()` has grouped output by 'nimekiri'. You can override using the
## `.groups` argument.
## # A tibble: 19 × 3
## # Groups:   nimekiri [10]
##    nimekiri                          sugu      n
##    <chr>                             <chr> <int>
##  1 EESTIMAA ÜHENDATUD VASAKPARTEI    M        15
##  2 EESTIMAA ÜHENDATUD VASAKPARTEI    N        10
##  3 Eesti Keskerakond                 M        84
##  4 Eesti Keskerakond                 N        41
##  5 Eesti Konservatiivne Rahvaerakond M       102
##  6 Eesti Konservatiivne Rahvaerakond N        23
##  7 Eesti Reformierakond              M        79
##  8 Eesti Reformierakond              N        46
##  9 Erakond Eesti 200                 M        76
## 10 Erakond Eesti 200                 N        49
## 11 Erakond Eestimaa Rohelised        M        34
## 12 Erakond Eestimaa Rohelised        N        24
## 13 Erakond Parempoolsed              M        85
## 14 Erakond Parempoolsed              N        40
## 15 ISAMAA Erakond                    M        96
## 16 ISAMAA Erakond                    N        29
## 17 Sotsiaaldemokraatlik Erakond      M        70
## 18 Sotsiaaldemokraatlik Erakond      N        55
## 19 Üksikkandidaadid                  M        10
valimised %>% count(nimekiri, sugu) # andmed ei jää grupeerituks
##                             nimekiri sugu   n
## 1     EESTIMAA ÜHENDATUD VASAKPARTEI    M  15
## 2     EESTIMAA ÜHENDATUD VASAKPARTEI    N  10
## 3                  Eesti Keskerakond    M  84
## 4                  Eesti Keskerakond    N  41
## 5  Eesti Konservatiivne Rahvaerakond    M 102
## 6  Eesti Konservatiivne Rahvaerakond    N  23
## 7               Eesti Reformierakond    M  79
## 8               Eesti Reformierakond    N  46
## 9                  Erakond Eesti 200    M  76
## 10                 Erakond Eesti 200    N  49
## 11        Erakond Eestimaa Rohelised    M  34
## 12        Erakond Eestimaa Rohelised    N  24
## 13              Erakond Parempoolsed    M  85
## 14              Erakond Parempoolsed    N  40
## 15                    ISAMAA Erakond    M  96
## 16                    ISAMAA Erakond    N  29
## 17      Sotsiaaldemokraatlik Erakond    M  70
## 18      Sotsiaaldemokraatlik Erakond    N  55
## 19                  Üksikkandidaadid    M  10

Üsna hiljutine uuendus on võimalus grupeerida andmeid ka teiste funktsioonide sees, ilma group_by()-funktsiooni kasutamata. Sellisel juhul tuleb funktsiooni sisse lisada argument .by =, mis täpsustab, mis tunnuse alusel andmeid rühmitatakse. Ka sellise kasutuse puhul ei jää andmed pärast funktsiooni kasutamist ühegi tunnuse alusel grupeerituks.

# Teeme kokkuvõttest täiesti uue andmetabeli
valimised %>%
  summarise(keskmine_häältesaak = mean(hääli_kokku), .by = nimekiri)

# Lisame algsesse andmetabelisse kokkuvõttega uue tulba
valimised %>%
  mutate(nimekirja_keskmine_häältesaak = mean(hääli_kokku), .by = nimekiri)

Harjutuses 2 saadud vastus selle kohta, mis aastal on kursusel olnud kõige rohkem kohvijoojaid, ei rahulda meid päriselt, sest see on esitatud absoluutarvuna ega võta arvesse seda, kui palju mingil aastal üldse osalejaid oli. Grupeerimine aitab meil aga arvutada kohvijoojate osakaalu iga aasta kohta eraldi.

kysimustik %>%
  group_by(aasta, lemmikjook) %>%
  summarise(vastajaid = n()) %>% 
  mutate(prop = vastajaid/sum(vastajaid)) %>%
  ungroup() %>%
  filter(lemmikjook == "Kohv") %>%
  filter(prop == max(prop)) %>%
  pull(aasta) %>%
  as.character()
kysimustik %>%
  count(aasta, lemmikjook, name = "vastajaid") %>%
  mutate(prop = vastajaid/sum(vastajaid), .by = aasta) %>%
  filter(lemmikjook == "Kohv") %>%
  filter(prop == max(prop)) %>%
  .$aasta %>%
  as.character()

Lisaks funktsioonile n(), mis loeb vaatlusi kokku, ning juba tuttavatele funktsioonidele min(), max(), mean(), median() jm, on kasulik mh ka näiteks funktsioon n_distinct(), mis loeb kokku mingi tunnuse kõik unikaalsed väärtused kas siis terves andmestikus või selle alamosades.

# Kui palju eri õppekavasid on eri aastatel olnud esindatud?
kysimustik %>%
  group_by(aasta) %>%
  summarise(oppekavasid = n_distinct(oppekava))

Harjutused

Harjutus 5

Leia valimiste andmestikust, millistes valimisnimekirjades on protsentuaalselt kõige rohkem Kalade tähtkujust kandidaate.

  1. Grupeeri valimiste andmestik valimisnimekirja (nimekiri) ja tähtkuju alusel.
  2. Tee igas grupis kokkuvõte, milles loed kokku iga grupi vaatluste arvu (andmed peavad jääma nimekirja alusel grupeerituks).
  3. Lisa andmestikku uus tulp prop, kuhu lisad iga tähemärgi esindajate osakaalu kõikidest tähemärkidest igas grupis (= valimisnimekirjas).
  4. Filtreeri andmetest välja ainult read, kus tulpa tähtkuju väärtus on Kalad.
  5. Järjesta andmed osakaalu tulba järgi kahanevalt või võta välja lihtsalt rida, kus on osakaalu tulbas on kõige suurem väärtus.
valimised %>%
  group_by(nimekiri, tähtkuju) %>%
  summarise(arv = n()) %>% # andmed jäävad nimekirja põhjal grupeerituks
  mutate(prop = arv/sum(arv)) %>%
  ungroup() %>%
  filter(tähtkuju == "Kalad") %>%
  filter(prop == max(prop))

# või
valimised %>%
  count(nimekiri, tähtkuju) %>%
  mutate(prop = n/sum(n), .by = nimekiri) %>%
  filter(tähtkuju == "Kalad") %>%
  filter(prop == max(prop))

Pikk ja lai tabel

Nii valimiste kui ka küsimustiku andmestikes on iga osaleja eraldi real ning igat osalejat iseloomustab tulpades iga tunnuse puhul üks kindel väärtus. Samuti näeme tidyverse’iga andmete kokkuvõtteid tehes, et kõikide grupeerivate tunnuste unikaalsed kombinatsioonid on eraldi ridadel ning nende kombinatsioonide mingid väärtused (vaatluste summa, keskmine, mediaan vmt) on pandud omaette tulpa. Sellist andmeformaati nimetatakse pikaks tabeliks (long format). Vahel võib aga juhtuda, et üks tunnus on jaotatud hoopis erinevatesse tulpadesse, mispuhul ridu on vähem, tulpasid on rohkem ja tunnuste kombinatsioonide väärtused on jagatud mitme tulba vahel (lai tabel ehk wide format).

Pika ja laia tabeli erinevust ilmestavad hästi näiteks just group_by()+summarise() ja tapply() funktsioonide väljundid.

valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(hääli_kokku = sum(hääli_kokku)) # pikk tabel
## `summarise()` has grouped output by 'nimekiri'. You can override using the
## `.groups` argument.
## # A tibble: 19 × 3
## # Groups:   nimekiri [10]
##    nimekiri                          sugu  hääli_kokku
##    <chr>                             <chr>       <int>
##  1 EESTIMAA ÜHENDATUD VASAKPARTEI    M           11086
##  2 EESTIMAA ÜHENDATUD VASAKPARTEI    N            3519
##  3 Eesti Keskerakond                 M           74257
##  4 Eesti Keskerakond                 N           18997
##  5 Eesti Konservatiivne Rahvaerakond M           80400
##  6 Eesti Konservatiivne Rahvaerakond N           17566
##  7 Eesti Reformierakond              M          106916
##  8 Eesti Reformierakond              N           83716
##  9 Erakond Eesti 200                 M           46149
## 10 Erakond Eesti 200                 N           35180
## 11 Erakond Eestimaa Rohelised        M            2624
## 12 Erakond Eestimaa Rohelised        N            3262
## 13 Erakond Parempoolsed              M            8814
## 14 Erakond Parempoolsed              N            5223
## 15 ISAMAA Erakond                    M           42723
## 16 ISAMAA Erakond                    N            7395
## 17 Sotsiaaldemokraatlik Erakond      M           29646
## 18 Sotsiaaldemokraatlik Erakond      N           26938
## 19 Üksikkandidaadid                  M            5888
tapply(valimised$hääli_kokku, list(valimised$nimekiri, valimised$sugu), sum) # lai tabel
##                                        M     N
## Eesti Keskerakond                  74257 18997
## Eesti Konservatiivne Rahvaerakond  80400 17566
## Eesti Reformierakond              106916 83716
## EESTIMAA ÜHENDATUD VASAKPARTEI     11086  3519
## Erakond Eesti 200                  46149 35180
## Erakond Eestimaa Rohelised          2624  3262
## Erakond Parempoolsed                8814  5223
## ISAMAA Erakond                     42723  7395
## Sotsiaaldemokraatlik Erakond       29646 26938
## Üksikkandidaadid                    5888    NA

https://r4ds.had.co.nz/tidy-data.html

Laiast tabelist pika tegemiseks saab kasutada funktsiooni pivot_longer() ning pikast tabelist laia tegemiseks funktsiooni pivot_wider().

# laiast pikk
tapply(valimised$hääli_kokku, list(valimised$nimekiri, valimised$sugu), sum) %>%
  as.data.frame() %>% # maatriksist data.frame-tüüpi objekt
  rownames_to_column(var = "nimekiri") %>% # reanimed tulpa "nimekiri"
  pivot_longer(data = ., # andmetabel eelmiste funktsioonide väljundist
               cols = c("M", "N"), # tulbad, mida ühe tunnuse alla koondada
               names_to = "sugu", # uus tulbanimi, kuhu tunnuse kategooriad (nt "M" ja "N") panna
               values_to = "hääli_kokku") # uus tulbanimi, kuhu tunnuse väärtused panna
# pikast lai
valimised %>%
  group_by(nimekiri, sugu) %>%
  summarise(hääli_kokku = sum(hääli_kokku)) %>%
  pivot_wider(data = ., # andmetabel eelmiste funktsioonide väljundist
              names_from = "sugu", # tulp, mis sisaldab kategooriate nimesid, mida eraldi tulpadesse panna
              values_from = "hääli_kokku") # tulp, mis sisaldab tunnuse väärtusi, mida eraldi tulpade lahtritesse panna

Laiast pika tabeli teeb ka funktsioon as.data.frame(), kui kasutame seda mingil table()-funktsioonige tehtud objektil.

table(valimised$sugu)

table(valimised$sugu) %>%
  as.data.frame()

NB! ggplot2 tahab, et tegemist oleks pika tabeliga!

Kasutatud funktsioonide sõnastik

  • arrange() - järjesta andmestik mingi tunnuse alusel
  • count() - loe andmestikus vaatlusi kokku
  • filter() - vali andmestikust ridu
  • group_by() - grupeeri andmestiku vaatlused mingi(te) tunnus(t)e alusel
  • gsub() - asenda mingi sümbol või sümbolite järjend vektoris teise sümboli või sümbolite järjendiga
  • lapply() - rakenda mingit funktsiooni kõikidele vektori või listi elementidele (tulemuseks vaikimisi list)
  • mutate() - lisa andmestikku uus tulp
  • n() - loe vaatlusi kokku
  • n_distinct() - leia erinevate väärtuste arv
  • pivot_longer() - tee laiast tabelist pikk
  • pivot_wider() - tee pikast tabelist lai
  • pull() - vali andmestikust tulp vektorina
  • rownames_to_column() - tee andmetabeli reanimedest omaette andmestiku tulp
  • select() - vali andmestikust tulp(asid) (tulemuseks jääb data.frame objekt, mitte vektor)
  • str_split() - lõika tekst mingi sümboli, nt tühiku koha pealt eraldi elementideks (tulemuseks vaikimisi list)
  • summarise() - tee andmestikust kokkuvõtteid (tulemuseks uus tabel)
  • tolower() - tee vektori elemendid läbivalt väiketäheliseks
  • ungroup() - tühista andmetabeli vaatluste rühmitus(ed)
  • unique() - küsi ainult vektori unikaalseid (st erinevaid) väärtusi
  • unlist() - tee list-tüüpi objektist tavaline vektor

Järgmisel korral

Valim ja populatsioon. Hüpoteesid ja tõenäosus.