Data.frames jsou nejobvyklejší formou tabulek. Uvažování o tabulkách vyžaduje dva úhly pohledu:

Data.frames z pohledu programování - základ

Vytvoření data.frame

Z tabulky v externím souboru

Načtením tabulky ze souborů .tsv, .csv a .txt pomocí funkce read.table() a jejích příbuzných v balíčcích utils a readr se automaticky vytvoří data.frame. O jednotlivých parametrech těchto funkcí jsme mluvili v sekci o načítání souborů. U načítání souborů aplikace MS excel pomocí balíčků readxl a xlsx se taky vytvoří data.frame, a pokud funkce umí načíst více pracovních listů (worksheets) najednou, načte data.frames do seznamu. Načtením plochého souboru pomocí balíčku data.table vznikne taky data.frame, ale dostaneme na výběr, jestli chceme data.frame, nebo data.table, což je tabulkový formát vytvořený v rámci toho balíčku.

Z jednotlivých vektorů

Jednotlivé vektory spojíme do data.framu pomocí funkce data.frame. Všechny vektory musí mít stejnou délku.

Příklad:

jmeno <- c("Anča", "Máňa", "Cilka", "Pepina")
smena <- c("dopolední", "dopolední", "odpolední", "odpolední")
pocet_neceho <- c(3, 4, 5.5, NA)
holky <- data.frame(jmeno, smena, pocet_neceho, stringsAsFactors = FALSE)
str(holky)
## 'data.frame':    4 obs. of  3 variables:
##  $ jmeno       : chr  "Anča" "Máňa" "Cilka" "Pepina"
##  $ smena       : chr  "dopolední" "dopolední" "odpolední" "odpolední"
##  $ pocet_neceho: num  3 4 5.5 NA
holky
##    jmeno     smena pocet_neceho
## 1   Anča dopolední          3.0
## 2   Máňa dopolední          4.0
## 3  Cilka odpolední          5.5
## 4 Pepina odpolední           NA

Jak je vidět, funkce data.frame přijme neomezený počet vektorů a za nimi můžou následovat její možné parametry. Pro nás je důležitý parametr stringsAsFactors. Sloupce nově vzniklého data.framu se automaticky pojmenují podle názvů původních vektorů. Řádky se automaticky číslují. Čísla jsou jejich “row.names”. Každý řádek musí mít svůj unikátní identifikátor. Automatické číslování řádků toto zajistí. Místo čísel řádků můžeme stanovit jiné identifikátory, a to buď určit jako identifikátor nějaký sloupec tabulky, tj. statistickou proměnnou, nebo dodat jiný vektor (znakový nebo číselný). V obou případech si R zkontroluje, že délka vektoru dodaných jmen řádků je stejná jako skutečný počet řádků a že se žádné jméno neopakuje. Pokud ano, hlásí chybu a data.frame nevytvoří.

Ke jménům řádků se nedá přistupovat jako k samostatnému sloupci. Ke jménům řádků i sloupců se přistupuje pomocí funkcí rownames() a colnames(), když už je data.frame vytvořený a je uložený v proměnné:

rownames(holky) <- c(101:104)
rownames(holky)
## [1] "101" "102" "103" "104"
rownames(holky)[2] <- 555
rownames(holky)
## [1] "101" "555" "103" "104"
colnames(holky) <- c("Křestní jméno", "Která směna", "Nějaká číselná proměnná") 

str(holky)
## 'data.frame':    4 obs. of  3 variables:
##  $ Křestní jméno          : chr  "Anča" "Máňa" "Cilka" "Pepina"
##  $ Která směna            : chr  "dopolední" "dopolední" "odpolední" "odpolední"
##  $ Nějaká číselná proměnná: num  3 4 5.5 NA

Nic nehlídá, jestli jména sloupců jsou unikátní:

colnames(holky) <- c("Křestní jméno","Křestní jméno","Křestní jméno")
str(holky)
## 'data.frame':    4 obs. of  3 variables:
##  $ Křestní jméno: chr  "Anča" "Máňa" "Cilka" "Pepina"
##  $ Křestní jméno: chr  "dopolední" "dopolední" "odpolední" "odpolední"
##  $ Křestní jméno: num  3 4 5.5 NA
colnames(holky) <- c("Křestní jméno", "Která směna", "Nějaká číselná proměnná") 

Pozor, funkce colnames() a rownames používané pro již vzniklý data.frame v proměnné se snadno pletou s parametry col.names a row.names ve funkci data.frame()!

Z matice

Matice sama o sobě je taky tabulka, jenomže pojme jenom jeden typ elementů. Na data.frame, jehož sloupce můžou mít různé typy elementů, ji snadno převedeme funkcí as.data.frame. Taky můžeme nastavovat parametry row.names a stringsAsFactors.

Ze seznamu

a <- list  (1:3, letters[1:3])
a
## [[1]]
## [1] 1 2 3
## 
## [[2]]
## [1] "a" "b" "c"
str(as.data.frame(a, stringsAsFactors = FALSE))
## 'data.frame':    3 obs. of  2 variables:
##  $ X1.3            : int  1 2 3
##  $ c..a....b....c..: chr  "a" "b" "c"

Manipulace s daty v data.frame - subsetting

K datům v data.frame přistupujeme přes jejich pozice, jména, nebo přes výsledky logických testů.

Subsetting pomocí pozice:

Používáme hranatou závorku, v níž jsou dvě pozice oddělené čárkou: tabulka[které řádky, které sloupce]. Na každé z pozic může být číslice, interval číslic (např. 2:20), vektor číslic (např. c(1, 3:10, 23)), nebo proměnná, v níž je uložený číselný vektor (např. cisla_radku). Může to být i výběr z takového vektoru, takže se hranaté závorky objeví vnořeně, např. tabulka[cisla_do_radku[3], cisla_do_sloupcu[6]]. To je důležité si uvědomit, až budete chtít používat subsetting ve for-cyklech a budete někam potřebovat nacpat index [i] !

Hranatá závorka může taky obsahovat jenom jedno číslo. Když u něj nikde není čárka, znamená to číslo vybraná čísla sloupců a všechny řádky. Například:

holky[1]
##     Křestní jméno
## 101          Anča
## 555          Máňa
## 103         Cilka
## 104        Pepina
holky[c(1,3)] #první a třetí sloupec se všemi řádky
##     Křestní jméno Nějaká číselná proměnná
## 101          Anča                     3.0
## 555          Máňa                     4.0
## 103         Cilka                     5.5
## 104        Pepina                      NA

Totéž se zapíše taky s prázdnou pozicí řádků, čárkou a číselným údajem pro sloupce za ní:

holky[,c(1,3)]
##     Křestní jméno Nějaká číselná proměnná
## 101          Anča                     3.0
## 555          Máňa                     4.0
## 103         Cilka                     5.5
## 104        Pepina                      NA

To je zápis konzistentní s tím, jak se zapisují vybraná čísla řádků se všemi sloupci:

holky[c(1, 4),]
##     Křestní jméno Která směna Nějaká číselná proměnná
## 101          Anča   dopolední                       3
## 104        Pepina   odpolední                      NA

Výběr může být, stejně jako u subsettingu vektorů, taky negativní, pomocí záporného znaménka před daným číselným údajem. Když se R bude bouřit, pomůže kulatá závorka kolem daného číselného údaje a mínus vně ní.

Výběr sloupců podle jména

Jméno sloupce se píše za dolarovou značku $:

holky$jmeno
## NULL

případně

holky$"Křestní jméno"
## [1] "Anča"   "Máňa"   "Cilka"  "Pepina"

V prvním případě je použito jméno vektoru, z kterého sloupec vznikl. To je název proměnné, takže se u něj nepíšou uvozovky. Ve jménech sloupců uvedených v colnames() (dá se použít i names()), se ale musí napsat dvojité uvozovky, protože je to řetězec.

V této souvislosti je třeba zmínit funkce attach() a detach() na jedné straně a with() na druhé straně: kvůli šetření místem a přehlednosti kódu byl vyvinut způsob, jak moci psát jenom názvy sloupců a přimět R, aby pořád vědělo, že jsou to sloupce tabulky, kterou už má načtenou v proměnné. První způsob je zavolání funkce attach() s názvem tabulky. Dokud R nedojde ve skriptu k detach() s názvem téže tabulky, bude všechny proměnné, které se v názvu shodují s některým sloupcem tabulky, kterou má právě označenou funkcí attach(), považovat za sloupce této tabulky. Běda, když zatím začnete pracovat s jinou tabulkou, která má náhodou stejně pojmenovaný nějaký sloupec! R bude všechny příkazy provádět se sloupcem té označené tabulky. V lepším případě to hned vyústí v nějakou chybovou hlášku, ale v horším případě to bude systematicky provádět něco jiného, než očekáváte. Proto se silně nedoporučuje tyto funkce volat, když pracujete s více než jednou tabulkou najednou. Tento pár funkcí se typicky používá v grafice, když se pohybujete v různých sloupcích téže tabulky.

Trochu bezpečnější funkcí je with(tabulka, {skript týkající se tabulky}), ale tam je zase vnoření skriptu do složených závorek, což znepřehledňuje skript, protože tam noří další podúroveň. Tak si vyberte…

Uvnitř těchto funkcí ale nejde používat jména z colnames() (ty řetězce v uvozovkách)

Příklady použití attach() s detach():

attach(holky)
toupper(jmeno)
## [1] "ANČA"   "MÁŇA"   "CILKA"  "PEPINA"
detach(holky)

a

with(holky, {toupper(smena)})
## [1] "DOPOLEDNÍ" "DOPOLEDNÍ" "ODPOLEDNÍ" "ODPOLEDNÍ"

Subsetting pomocí logických podmínek - funkce which() a subset()

Už dávno umíme zjišťovat, na kterých pozicích ve vektoru jsou elementy, které splňují nějakou podmínku. Používáme k tomu funkci which().

Příklad:

maly_vek <- c("citron", "jablko", "jablko", "jablko", "citron", "jablko", "citron" )
which(maly_vek == "citron")
## [1] 1 5 7

Výstupem funkce which() je vždy numerický vektor, který udává čísla pozic v testovaném vektoru, které vyhovují podmínkám logického testu uvedeného uvnitř funkce which(). Když chceme získat vektor s výběrem podle pozic v původním vektoru, použijeme funkci which() v subsetovací závorce (je to logické, protože jejím výsledkem je vektor čísel!):

jeste_mensi_vek <- maly_vek[which(maly_vek == "citron")]
jeste_mensi_vek
## [1] "citron" "citron" "citron"

Pro úplnost dodáme, že stejná věc se dá zapsat taky bez funkce which():

jeste_mensi_vek[jeste_mensi_vek == "citron"]
## [1] "citron" "citron" "citron"

A - to už je asi zbytečné opakovat, ale stejně: když si vybereme rovnou elementy, které splňují nějakou podmínku, nedostaneme informaci o tom, kde v tom původním vektoru byly.

Stejný princip funguje u tabulek data.frame. Když prohledáváme tabulku, typicky chceme všechny řádky, které splňují nějakou podmínku na obsah některého ze sloupců, a informace, kterou získáme pomocí funkce which() nebo rovnou uplatněním logického testu, tedy patří na první pozici do hranaté závorky, pomocí které vybíráme z původní tabulky data.frame. Pokud u řádků, které vyhovují podmínce, chceme vidět všechny sloupce, napíšeme za podmínku (nebo which()) čárku a hranatou závorku ukončíme. Kdybychom tam tu čárku nenapsali, náš příkaz by znamenal, že chceme sloupec/sloupce, jejichž pořadová čísla vyhovují podmínkám. Jenže tato čísla znamenají, jak víme, čísla řádků, protože označují pozice ve vektoru, jímž ten sloupec v podmínce je! Kdybychom je považovali za čísla sloupců, dostali bychom nepředvídané výsledky, pokud by sloupec s daným pořadovým číslem v tabulce existoval, nebo dokonce chybovou hlášku, že takový sloupec není definován. Ukážeme si to:

Tady budeme správně chtít řádek, jehož sloupec “Křestní jméno” obsahuje řetězec “Máňa”:

holky[which(holky$`Křestní jméno` == "Máňa"),] # sic! Ta čárka za první pozicí!
##     Křestní jméno Která směna Nějaká číselná proměnná
## 555          Máňa   dopolední                       4

nebo synonymum

holky[holky$`Křestní jméno` == "Máňa",] # Taky čárka za první pozicí!
##     Křestní jméno Která směna Nějaká číselná proměnná
## 555          Máňa   dopolední                       4

případně

holky[holky[,1] == "Máňa",]  # Taky čárka za první pozicí!
##     Křestní jméno Která směna Nějaká číselná proměnná
## 555          Máňa   dopolední                       4

Proč tam tu čárku musíme dát? Protože:

which(holky$`Křestní jméno` == "Máňa")
## [1] 2
holky$`Křestní jméno` == "Máňa"
## [1] FALSE  TRUE FALSE FALSE
holky[,1] == "Máňa"
## [1] FALSE  TRUE FALSE FALSE

Všude se nám vrátí dvojka, buď rovnou, nebo na jako hodnota TRUE v logickém vektoru. Když se ta dvojka ocitne na druhé pozici (a jako číslo na druhé pozice se u tabulek chápe i číslo, před kterým není čárka), znamená to prostě totéž jako tohle:

holky[2]
##     Která směna
## 101   dopolední
## 555   dopolední
## 103   odpolední
## 104   odpolední

nebo (to je tedy opravdu podivný zápis a asi není k ničemu dobrý)

holky[c(FALSE, TRUE, FALSE, FALSE)] 
##     Která směna
## 101   dopolední
## 555   dopolední
## 103   odpolední
## 104   odpolední

Připomeňme, že Čísla 101, 555, 103 a 104 nepatří do žádné proměnné a netvoří samostatný sloupec, ale jsou to jména řádků, která jsme si předtím náhodně zvolili, když jsme si ukazovali, jak se pojmenovávají řádky.

Prohledávat tabulku pomocí logických testů na řádek nedává moc smysl, protože tím, že řádky nejsou vektory, nemůžeme žádným which dostat číslo sloupce.

Přidání řádku

K již existující tabulce se snadno přidá libovolný počet řádků pomocí funkce rbind(), stejně jako u matice. Podmínkou je, že řádky musejí odpovídat stavbě tabulky, ke které je chceme přidat, včetně jmen sloupců. Ukážeme si to na následujícím příkladu:

jmeno <- c("Mirek", "Richard")
smena <- c("noční", "noční")
nejaka_ciselna_promenna <- c(0.2, 8)
kluci <- data.frame(jmeno, smena, nejaka_ciselna_promenna)

# brigadnici <- rbind(holky, kluci) # tady se R vzbouří, že se neshodují jména sloupců
 
colnames(kluci) <- colnames(holky) # tady to napravíme 

brigadnici <- rbind(holky, kluci)

brigadnici
##     Křestní jméno Která směna Nějaká číselná proměnná
## 101          Anča   dopolední                     3.0
## 555          Máňa   dopolední                     4.0
## 103         Cilka   odpolední                     5.5
## 104        Pepina   odpolední                      NA
## 5           Mirek       noční                     0.2
## 6         Richard       noční                     8.0

Přidání sloupce

Zatímco řádky musí mít samy datový typ data.frame, pro přidání sloupce k data.frame existuje jediné omezení, totiž shoda délky budoucího sloupce s počtem řádků ve stávající tabulce. Samotný sloupec má datový typ vektor. Sloupec bude pojmenován názvem vektoru. Dá se přidat na pravý nebo levý kraj tabulky.

bonus <- c("ano", "ano", "ne", "ano", "ano", "ne")
oddeleni <- c("psychiatrie", "patologie", "psychiatrie", "patologie", "chirurgie", "neurologie")

brigadnici_bonus <- cbind(brigadnici, bonus, stringsAsFactors = FALSE) # i tady můžeme zakázat tvorbu faktorů

brigadnici_bonus
##     Křestní jméno Která směna Nějaká číselná proměnná bonus
## 101          Anča   dopolední                     3.0   ano
## 555          Máňa   dopolední                     4.0   ano
## 103         Cilka   odpolední                     5.5    ne
## 104        Pepina   odpolední                      NA   ano
## 5           Mirek       noční                     0.2   ano
## 6         Richard       noční                     8.0    ne
brigadnici_bonus_oddeleni <- cbind(oddeleni, brigadnici_bonus) # tady jsme zrovna nezakázali tvorbu faktorů

str(brigadnici_bonus_oddeleni)
## 'data.frame':    6 obs. of  5 variables:
##  $ oddeleni               : Factor w/ 4 levels "chirurgie","neurologie",..: 4 3 4 3 1 2
##  $ Křestní jméno          : chr  "Anča" "Máňa" "Cilka" "Pepina" ...
##  $ Která směna            : chr  "dopolední" "dopolední" "odpolední" "odpolední" ...
##  $ Nějaká číselná proměnná: num  3 4 5.5 NA 0.2 8
##  $ bonus                  : chr  "ano" "ano" "ne" "ano" ...

Samozřejmě můžeme upravit názvy sloupců:

colnames(brigadnici_bonus_oddeleni) <- c("Oddělení", colnames(brigadnici), "Nárok na bonus?")
str(brigadnici_bonus_oddeleni)
## 'data.frame':    6 obs. of  5 variables:
##  $ Oddělení               : Factor w/ 4 levels "chirurgie","neurologie",..: 4 3 4 3 1 2
##  $ Křestní jméno          : chr  "Anča" "Máňa" "Cilka" "Pepina" ...
##  $ Která směna            : chr  "dopolední" "dopolední" "odpolední" "odpolední" ...
##  $ Nějaká číselná proměnná: num  3 4 5.5 NA 0.2 8
##  $ Nárok na bonus?        : chr  "ano" "ano" "ne" "ano" ...

Výběr pomocí funkce subset()

Velkým pomocníkem, který funguje u tabulek, ale ne u vektorů, je funkce subset(). Chce název data.frame a logický test, který určí podmínku pro řádky, které se mají vybrat. Jejím výstupem je nový data.frame, který obsahuje jenom řádky vyhovující výběru.

jen_bez_bonusu <- subset(brigadnici_bonus_oddeleni, `Nárok na bonus?` == "ne")
jen_bez_bonusu
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 103 psychiatrie         Cilka   odpolední                     5.5
## 6    neurologie       Richard       noční                     8.0
##     Nárok na bonus?
## 103              ne
## 6                ne

Všimněte si, že se tady operuje s názvy sloupců bez názvu celé tabulky, jako kdybychom si ji napřed uložili do paměti funkcí attach(), nebo with(). To je pohodlné.

Změna pořadí sloupců

Kdybychom chtěli změnit pořadí sloupců nebo vložit nový sloupec někam doprostřed tabulky, musíme to udělat výběrem sloupců a jejich postupným seřazením pomocí funkce cbind().

attach(brigadnici_bonus_oddeleni) # attach(), nezapomenout na konci detach()
prehazena_tabulka <- cbind(`Která směna`, `Křestní jméno`, brigadnici_bonus_oddeleni[1], brigadnici_bonus_oddeleni[4:ncol(brigadnici_bonus_oddeleni)])
# vyzkoušeli jsme několik způsobů zápisu sloupců
prehazena_tabulka
##     Která směna Křestní jméno    Oddělení Nějaká číselná proměnná
## 101   dopolední          Anča psychiatrie                     3.0
## 555   dopolední          Máňa   patologie                     4.0
## 103   odpolední         Cilka psychiatrie                     5.5
## 104   odpolední        Pepina   patologie                      NA
## 5         noční         Mirek   chirurgie                     0.2
## 6         noční       Richard  neurologie                     8.0
##     Nárok na bonus?
## 101             ano
## 555             ano
## 103              ne
## 104             ano
## 5               ano
## 6                ne
detach(brigadnici_bonus_oddeleni) 

Pozor! Člověka by snadno mohlo napadnout, že by se dalo postupovat jako při přemisťování řádků v Excelu pomocí Cut’n’Paste, kdy se řádek ze schránky vloží mezi nějaké dva sloupce. To ale nefunguje. V příkladu níže uvidíme, že se obsah třetího sloupce přepsal obsahem pátého sloupce a přitom jeho název zůstal stejný.

prehazena_tabulka[3] <- prehazena_tabulka[5]
prehazena_tabulka
##     Která směna Křestní jméno Oddělení Nějaká číselná proměnná
## 101   dopolední          Anča      ano                     3.0
## 555   dopolední          Máňa      ano                     4.0
## 103   odpolední         Cilka       ne                     5.5
## 104   odpolední        Pepina      ano                      NA
## 5         noční         Mirek      ano                     0.2
## 6         noční       Richard       ne                     8.0
##     Nárok na bonus?
## 101             ano
## 555             ano
## 103              ne
## 104             ano
## 5               ano
## 6                ne

A co kdybychom chtěli mezi sloupce vložit nějaký úplně cizí vektor?

novy_vektor <- c(555:560)
prehazena_tabulka[1] <- novy_vektor
prehazena_tabulka
##     Která směna Křestní jméno Oddělení Nějaká číselná proměnná
## 101         555          Anča      ano                     3.0
## 555         556          Máňa      ano                     4.0
## 103         557         Cilka       ne                     5.5
## 104         558        Pepina      ano                      NA
## 5           559         Mirek      ano                     0.2
## 6           560       Richard       ne                     8.0
##     Nárok na bonus?
## 101             ano
## 555             ano
## 103              ne
## 104             ano
## 5               ano
## 6                ne

To už asi nebylo velké překvapení - obsah prvního sloupce se přepsal obsahem proměnné novy_vektor a název sloupce zůstal stejný. Takže vkládat nový sloupec mezi stávající sloupce opravdu nejde!

Setřídění (seřazení) řádků podle velikosti hodnot v některém ze sloupců

Setřídění řádků podle velikosti hodnot v některém ze sloupců provedeme tak, že si setřídíme hodnoty v daném sloupci pomocí funkce order() (tato funkce má argument decreasing, který je defaultně FALSE, takže třídí vzestupně). Tato funkce vrací vektor s pozicemi uspořádaných hodnot, tj. číselný vektor, který neobsahuje hodnoty samotné, ale jejich pořadí v nesetříděném výchozím vektoru (na rozdíl od sort(), která vrací přímo ty setříděné elementy, tj. hodnoty statistické proměnné). Tento číselný vektor vložíme do subsetovací hranaté závorky na první pozici a za ním musíme napsat čárku.

Příklad:

brigadnici_bonus_oddeleni[order(brigadnici_bonus_oddeleni$`Nějaká číselná proměnná`),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 5     chirurgie         Mirek       noční                     0.2
## 101 psychiatrie          Anča   dopolední                     3.0
## 555   patologie          Máňa   dopolední                     4.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 6    neurologie       Richard       noční                     8.0
## 104   patologie        Pepina   odpolední                      NA
##     Nárok na bonus?
## 5               ano
## 101             ano
## 555             ano
## 103              ne
## 6                ne
## 104             ano
brigadnici_bonus_oddeleni[order(brigadnici_bonus_oddeleni$`Nějaká číselná proměnná`, decreasing = TRUE, na.last = NA),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 555   patologie          Máňa   dopolední                     4.0
## 101 psychiatrie          Anča   dopolední                     3.0
## 5     chirurgie         Mirek       noční                     0.2
##     Nárok na bonus?
## 6                ne
## 103              ne
## 555             ano
## 101             ano
## 5               ano

Ve druhém výsledku si všimněte ještě nastavení parametru na.last. Defaultně (na.last = TRUE) se prázdné hodnoty neúčastní třídění, ale umístí se jako poslední řádky bez ohledu na to, zda je třídění sestupné, nebo vzestupné. Kromě toho můžeme zvolit FALSE, tj. budou se umisťovat na začátek, opět nezávisle na vzestupnosti/sestupnosti třídění. Třetí možností je NA (bez uvozovek!), které způsobí, že řádky s prázdnou hodnotou v daném sloupci budou smazány.

Třídění u číselných hodnot probíhá podle velikosti čísla, u znakových hodnot podle abecedního pořadí.

Náhodné setřídění (seřazení) řádků

To se hodí, kdykoli je třeba provést tzv. resampling kvůli statistické analýze. Náhodné setřídění řádků provedeme obalením funkce order() funkcí sample(). Argumentem funkce order() bude sloupec, podle jehož hodnot chceme řadit. Při náhodném třídění řádků to často může být kterýkoli sloupec. Záleží na povaze úlohy. Výsledkem této vnořené funkce je číselný vektor označující čísla řádků. Proto se objeví na první pozici v hranaté závorce a za ním musí být čárka.

brigadnici_bonus_oddeleni_nahoda <- brigadnici_bonus_oddeleni[sample(order(brigadnici_bonus_oddeleni$`Křestní jméno`)),]
brigadnici_bonus_oddeleni_nahoda
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 104   patologie        Pepina   odpolední                      NA
## 103 psychiatrie         Cilka   odpolední                     5.5
## 5     chirurgie         Mirek       noční                     0.2
## 555   patologie          Máňa   dopolední                     4.0
## 101 psychiatrie          Anča   dopolední                     3.0
##     Nárok na bonus?
## 6                ne
## 104             ano
## 103              ne
## 5               ano
## 555             ano
## 101             ano
brigadnici_bonus_oddeleni_nahoda <- brigadnici_bonus_oddeleni[sample(order(brigadnici_bonus_oddeleni$`Křestní jméno`)),] #teď to dopadne zase jinak
brigadnici_bonus_oddeleni_nahoda
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 101 psychiatrie          Anča   dopolední                     3.0
## 5     chirurgie         Mirek       noční                     0.2
## 555   patologie          Máňa   dopolední                     4.0
## 104   patologie        Pepina   odpolední                      NA
##     Nárok na bonus?
## 6                ne
## 103              ne
## 101             ano
## 5               ano
## 555             ano
## 104             ano

Sestupné třídění kategoriálních proměnných v alternativním zápisu pomocí záporného znaménka

Pokud při nastavování sestupného třídění použijeme ve funkci order() hodnotu parametru decreasing = TRUE, není rozdíl v chování kvalitativních a kvantitativních proměnných.

brigadnici_bonus_oddeleni_nahoda[order(brigadnici_bonus_oddeleni_nahoda$`Křestní jméno`, decreasing = TRUE),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 104   patologie        Pepina   odpolední                      NA
## 5     chirurgie         Mirek       noční                     0.2
## 555   patologie          Máňa   dopolední                     4.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 101 psychiatrie          Anča   dopolední                     3.0
##     Nárok na bonus?
## 6                ne
## 104             ano
## 5               ano
## 555             ano
## 103              ne
## 101             ano

U třídění kvantitativních proměnných se ale v něčích skriptech můžete setkat s tímto zápisem:

brigadnici_bonus_oddeleni_nahoda[order(-brigadnici_bonus_oddeleni_nahoda$`Nějaká číselná proměnná`),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 555   patologie          Máňa   dopolední                     4.0
## 101 psychiatrie          Anča   dopolední                     3.0
## 5     chirurgie         Mirek       noční                     0.2
## 104   patologie        Pepina   odpolední                      NA
##     Nárok na bonus?
## 6                ne
## 103              ne
## 555             ano
## 101             ano
## 5               ano
## 104             ano

Kvantitativní proměnné (tj. vyžadující abecední třídění) se takto třídit nedají.

> brigadnici_bonus_oddeleni_nahoda[order(-brigadnici_bonus_oddeleni_nahoda$`Křestní jméno`),]
Error in -brigadnici_bonus_oddeleni_nahoda$`Křestní jméno` : 
invalid argument to unary operator

Ta kvalitativní proměnná se musí obalit funkcí rank().

brigadnici_bonus_oddeleni_nahoda[order(-rank(brigadnici_bonus_oddeleni_nahoda$`Křestní jméno`)),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 6    neurologie       Richard       noční                     8.0
## 104   patologie        Pepina   odpolední                      NA
## 5     chirurgie         Mirek       noční                     0.2
## 555   patologie          Máňa   dopolední                     4.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 101 psychiatrie          Anča   dopolední                     3.0
##     Nárok na bonus?
## 6                ne
## 104             ano
## 5               ano
## 555             ano
## 103              ne
## 101             ano

Třídění (seřazení) podle hodnot několika sloupců

Několikanásobné třídění je možné.

brigadnici_bonus_oddeleni[order(brigadnici_bonus_oddeleni$`Nějaká číselná proměnná`, brigadnici_bonus_oddeleni$`Křestní jméno`, na.last = FALSE),]
##        Oddělení Křestní jméno Která směna Nějaká číselná proměnná
## 104   patologie        Pepina   odpolední                      NA
## 5     chirurgie         Mirek       noční                     0.2
## 101 psychiatrie          Anča   dopolední                     3.0
## 555   patologie          Máňa   dopolední                     4.0
## 103 psychiatrie         Cilka   odpolední                     5.5
## 6    neurologie       Richard       noční                     8.0
##     Nárok na bonus?
## 104             ano
## 5               ano
## 101             ano
## 555             ano
## 103              ne
## 6                ne

Prvním kritériem je hodnota kvantitativní proměnné. Kdyby dva řádky měly v dané proměnné stejnou hodnotu, jejich pořadí by nebylo náhodné, ale rozhodovalo by se podle abecedního pořadí hodnot proměnné Křestní jméno u daných dvou řádků. Kdyby například Anča a Máňa měly stejnou hodnotu kvantitativní proměnné, ve výsledné tabulce by (při vzestupném třídění) jako první z těchto dvou vždy figuroval řádek s Ančou.

Kdyby bylo třeba třídit podle jednoho sloupce sestupně a druhého vzestupně, asi by bylo potřeba postup rozložit do dvou kroků. Napřed sestupně setřídit podle kvantitativní proměnné a výsledek si uložit do samostatné proměnné a tu pak setřídit podle křestních jmen vzestupně. Nevyzkoušeno.

Sdružení dvou tabulek, i když mají každá jinak uspořádané řádky

Myslí se tím typicky přidání sloupců, i když funkce merge(), která nám k tomuto účelu poslouží, v sobě zahrnuje i přidání řádků, když je to relevantní. Aby funkce mohla pracovat, musíme si v každé tabulce stanovit jeden sloupec, který funkci předhodíme jako ten, podle kterého se tabulky mají sloučit. Když tento sloupec bude mít v obou tabulkách stejný název, stačí ho uvést jenom jednou, jinak se musí zmínit oba. Pro hodnoty, které funkce najde v obou sloupcích, jen přidá sloupec, pro hodnoty, které se v daném sloupci vyskytují pouze v jedné z tabulek, ve výsledné tabulce vytvoří nové řádky. Pomocí parametrů se dá regulovat, jestli to mají být všechny hodnoty, nebo jenom hodnoty přítomné v první tabulce, nebo jenom hodnoty přítomné v druhé tabulce.

Uživatelský scénář pro použití funkce merge() by mohl být následující: v klinické studii si lékaři pozvali deset pacientů, které nazvali skupinou A, a změřili jim tlak. Pak někoho napadlo, že by se každého mohli zeptat, zda je kuřákem. Tak si je museli pozvat znovu. Sestra si pokaždé otevřela novou excelovou tabulku a do ní vyplnila výsledky sezení. Pacienti byli do tabulek zapisováni v pořadí, jak se dostavili do ordinace.

Po dvou seancích tedy měli lékaři všechna potřebná data, ale měli je ve dvou různých tabulkách! Jejich data si nasimulujeme tady:

pacienti_A_1_jmeno <- c("Novák J.", "Suchánková M.", "Kotrba H.", "Malinová S.", "Chudý B.", "Skákalová L.", "Boháč N.", "Škorpil O.", "Hlavsa T.", "Starý R.")
pacienti_A_2_jmeno <- sample(pacienti_A_1_jmeno)
pacienti_A_1_tlak <- paste(sample(130:140,length(pacienti_A_1_jmeno)), sample(80:100, length(pacienti_A_1_jmeno)), sep = "/")
pacienti_A_2_kurak <- sample(c("ano", "ne"), length(pacienti_A_2_jmeno), replace = TRUE)
pacienti_A_1 <- data.frame(pacienti_A_1_jmeno, pacienti_A_1_tlak)
pacienti_A_2 <- data.frame(pacienti_A_2_jmeno, pacienti_A_2_kurak)
pacienti_A_1
##    pacienti_A_1_jmeno pacienti_A_1_tlak
## 1            Novák J.           140/100
## 2       Suchánková M.            139/83
## 3           Kotrba H.            130/94
## 4         Malinová S.            135/81
## 5            Chudý B.            133/84
## 6        Skákalová L.            138/91
## 7            Boháč N.            136/93
## 8          Škorpil O.            137/98
## 9           Hlavsa T.            131/95
## 10           Starý R.            132/86
pacienti_A_2
##    pacienti_A_2_jmeno pacienti_A_2_kurak
## 1          Škorpil O.                ano
## 2           Hlavsa T.                 ne
## 3        Skákalová L.                 ne
## 4            Boháč N.                ano
## 5            Chudý B.                 ne
## 6            Starý R.                ano
## 7       Suchánková M.                 ne
## 8            Novák J.                 ne
## 9           Kotrba H.                ano
## 10        Malinová S.                ano

Chtěli bychom spojit tabulky pacienti_A_1 a pacienti_A_2. Nedá se jen tak připojit sloupec, protože pořadí jmen je v každé tabulce rozdílné. Co teď? Tento problém nám vyřeší funkce merge(). Chce vědět, které tabulky (x a y) má sloučit, a dává si podmínku, že musí znát jeden sloupec, který mají obě tabulky společný. Ten se dokonce může v každé tabulce jmenovat jinak, jen je třeba ho pro každou tabulku správně uvést parametry by.x a by.y. Tato funkce má taky parametr by bez dalšího písmenka, ale ten se dá použít pouze tehdy, když jsou v obou tabulkách dané sloupce pojmenované stejně. To není náš případ, protože jsme číslovali sezení pacientů skupiny A jedničkou a dvojkou.

kompletni_pacienti_A <- merge(pacienti_A_1, pacienti_A_2, by.x = "pacienti_A_1_jmeno", by.y = "pacienti_A_2_jmeno")
kompletni_pacienti_A
##    pacienti_A_1_jmeno pacienti_A_1_tlak pacienti_A_2_kurak
## 1            Boháč N.            136/93                ano
## 2           Hlavsa T.            131/95                 ne
## 3            Chudý B.            133/84                 ne
## 4           Kotrba H.            130/94                ano
## 5         Malinová S.            135/81                ano
## 6            Novák J.           140/100                 ne
## 7        Skákalová L.            138/91                 ne
## 8            Starý R.            132/86                ano
## 9       Suchánková M.            139/83                 ne
## 10         Škorpil O.            137/98                ano

A je to. Můžete si to ručně zkontrolovat proti tabulkám pacienti_A_1 a pacienti_A_2.

Funkce merge() je ale připravena nám pomoct, i když průnik obou sloupců nebude dokonalý, tj. když sloupec v první tabulce bude obsahovat hodnoty, které neobsahuje odpovídající sloupec v druhé tabulce. Udělá to tak, že chybějícím hodnotám sama doplní NA. Pomocí parametrů all.x a all.y můžeme řídit, jestli to udělá obousměrně, nebo jestli zahrne třeba vyloučí ty, které jsou v druhé tabulce, ale nebyly v první (a naopak). My budeme chtít všechna jména, která můžeme získat. Vyzkoušíme to. Do tabulky s prvním sezením doplníme několik pozorování pacientů skupiny B. Napřed si nasimulujeme nové pacienty:

pacienti_B_1_jmeno <- c("Mašková A.", "Hotová E.", "Krůta F.")
pacienti_B_1_tlak <- paste(sample(130:140,length(pacienti_B_1_jmeno)), sample(80:100, length(pacienti_B_1_jmeno)), sep = "/")
pacienti_B_1 <- data.frame(pacienti_B_1_jmeno, pacienti_B_1_tlak)
#pozor, musíme jim správně pojmenovat sloupce, aby je přijala tabulka se skupinou A_1
colnames(pacienti_B_1) <- colnames(pacienti_A_1)

A teď je přidáme k pacientům skupiny A_1 pomocí funkce rbind():

pacienti_A_1_B_1 <- rbind(pacienti_A_1, pacienti_B_1)
pacienti_A_1_B_1
##    pacienti_A_1_jmeno pacienti_A_1_tlak
## 1            Novák J.           140/100
## 2       Suchánková M.            139/83
## 3           Kotrba H.            130/94
## 4         Malinová S.            135/81
## 5            Chudý B.            133/84
## 6        Skákalová L.            138/91
## 7            Boháč N.            136/93
## 8          Škorpil O.            137/98
## 9           Hlavsa T.            131/95
## 10           Starý R.            132/86
## 11         Mašková A.            140/85
## 12          Hotová E.           133/100
## 13           Krůta F.            132/81
pacienti_AB <- merge(pacienti_A_1_B_1, pacienti_A_2, by.x = "pacienti_A_1_jmeno", by.y = "pacienti_A_2_jmeno", all = TRUE)
pacienti_AB
##    pacienti_A_1_jmeno pacienti_A_1_tlak pacienti_A_2_kurak
## 1            Boháč N.            136/93                ano
## 2           Hlavsa T.            131/95                 ne
## 3            Chudý B.            133/84                 ne
## 4           Kotrba H.            130/94                ano
## 5         Malinová S.            135/81                ano
## 6            Novák J.           140/100                 ne
## 7        Skákalová L.            138/91                 ne
## 8            Starý R.            132/86                ano
## 9       Suchánková M.            139/83                 ne
## 10         Škorpil O.            137/98                ano
## 11          Hotová E.           133/100               <NA>
## 12           Krůta F.            132/81               <NA>
## 13         Mašková A.            140/85               <NA>

To all je zkratka pro all.x a zároveň all.y.

Dokonce se nám ještě nabízí možnost výslednou tabulku dostat rovnou setříděnou podle hodnot ve sloupcích, podle kterých se tabulky sjednocují, ale setřídí se napřed společné hodnoty obou sloupců, po nich teprve přijdou setříděné hodnoty sloupce z tabulky x a pak teprve setříděné hodnoty z tabulky y. V našem případě se napřed setřídí jména z tabulky pacientů A a po nich přijdou setříděná jména z tabulky pacientů B.

pacienti_AB_sorted <- merge(pacienti_A_1_B_1, pacienti_A_2, by.x = "pacienti_A_1_jmeno", by.y = "pacienti_A_2_jmeno", all = TRUE, sort = TRUE)
pacienti_AB_sorted
##    pacienti_A_1_jmeno pacienti_A_1_tlak pacienti_A_2_kurak
## 1            Boháč N.            136/93                ano
## 2           Hlavsa T.            131/95                 ne
## 3            Chudý B.            133/84                 ne
## 4           Kotrba H.            130/94                ano
## 5         Malinová S.            135/81                ano
## 6            Novák J.           140/100                 ne
## 7        Skákalová L.            138/91                 ne
## 8            Starý R.            132/86                ano
## 9       Suchánková M.            139/83                 ne
## 10         Škorpil O.            137/98                ano
## 11          Hotová E.           133/100               <NA>
## 12           Krůta F.            132/81               <NA>
## 13         Mašková A.            140/85               <NA>

Tabulky z pohledu datové analýzy

Struktura data.frame

Tabulky sestávají z řádků a sloupců. Sloupce mají představovat pozorované hodnoty jednotlivých statistických proměnných, např. tlak, výška, hmotnost. Každý řádek naopak nese informaci o hodnotě všech sledovaných statistických proměnných pro dané pozorování (observation). Tohle je vůdčí princip uspořádání tabulky dat pro statistickou analýzu. Vyčištěná a správně uspořádaná data (tidy data) se mají řídit následujícími principy:

  • Každá výzkumná jednotka (observational unit), tj. soubor zkoumaných subjektů, má mít vlastní tabulku.
  • Každý objekt pozorování v rámci té výzkumné jednotky má být ve své tabulce na zvláštním řádku.
  • Každá proměnná má mít vlastní sloupec.
  • Vlastní sloupec si zaslouží proměnná, ne každá její hodnota.

Příklad prohřešku proti prvnímu principu o různých výzkumných souborech v různých tabulkách: pozorujete jednotlivé lidi. Na každém řádku máte jedno unikátní jméno a ve sloupcích máte proměnné jako pohlaví, věk, výška, váha, a dejme tomu počet sourozenců. Dosud v pořádku. Jakmile byste tady ale pro každého sourozence přidali sloupec s jeho jménem a za tento sloupec až do sloupce se jménem dalšího sourozence byste sázeli sloupce s osobními charakteristikami toho jmenovaného sourozence, odchýlili byste se od principu “tidy data”. Údaje o sourozencích patří do separátní tabulky, kde bude každý sourozenec jedním objektem pozorování. A jeden sloupec v té sourozenecké tabulce může obsahovat informaci, ke komu z těch lidí v první tabulce daný sourozenec patří.

Druhý bod je asi jasný. Potíže tohoto typu nastanou třeba v takovéto situaci: na Google Forms zadáte dlouhatánský dotazník vybraným respondentům. Nedá se vyplnit a odeslat naráz a průběžné ukládání Google Forms neumožňuje. Vaši respondenti ho tedy vyplní a odešlou na několikrát, s tím, že si pamatují, kde naposledy přestali, a při příštím sezení navážou. Nicméně pokaždé musí vyplnit políčko se jménem. Pokaždé, když svůj dotazník odešlou, Google Forms ho uloží jako nový řádek do spreadsheetu, který funguje v zákulisí a který vy pak dostanete k analýze. Tak snadno dostanete od stejného respondenta několik řádků, v kterých bude vždy vyplněn nějaký počet odpovědí, tj. přidána hodnota do určitého počtu sloupců, a kolem ní na odpovídajících pozicích ostatních sloupců budou prázdné hodnoty. Aby data odpovídala principům “tidy data”, musíte tyto řádky poslučovat tak, abyste pro každého respondenta měli jenom jeden řádek.

Příklad:

dirty_data_01 <- data.frame(c("Anča", "Anča", "Helena", "Miloš"), c("ano", NA , "nevím", "ne"), c(NA, "ano", "ne", "ano"))
colnames(dirty_data_01) <- c("Jméno", "je_pilný", "je_pořádný")
dirty_data_01
##    Jméno je_pilný je_pořádný
## 1   Anča      ano       <NA>
## 2   Anča     <NA>        ano
## 3 Helena    nevím         ne
## 4  Miloš       ne        ano

Proti třetímu bodu, tj. že každá proměnná má mít svůj vlastní sloupec, se prohřešíte třeba takto:

dirty_data_02 <- data.frame(c("Rostislav", "Chrudoš", "Bivoj"), c("výška", "váha", "věk"), c(170, 67, 63), c(180, 90, 26), c(162, 65, 17))
colnames(dirty_data_02) <- c("Jméno reka", "Tělesná míra")
dirty_data_02
##   Jméno reka Tělesná míra  NA  NA  NA
## 1  Rostislav        výška 170 180 162
## 2    Chrudoš         váha  67  90  65
## 3      Bivoj          věk  63  26  17

Čtvrtý bod, že vlastní sloupec má mít každá proměnná, ne každá její hodnota, ilustrujeme následujícím špatným příkladem:

dirty_data_03 <- data.frame(c(14, 67, 8, 34), c("ano", "ano", "ne", "ano"), c("ne", "ne", "ano", "ne") ) 
colnames(dirty_data_03) <- c("věk", "muž", "žena")
dirty_data_03
##   věk muž žena
## 1  14 ano   ne
## 2  67 ano   ne
## 3   8  ne  ano
## 4  34 ano   ne

Tabulka nesplňuje principy “tidy data” proto, že kategorie “muž” a “žena” se navzájem vylučují (což dopředu víme a není to předmětem experimentu) a jsou to logicky hodnoty jedné kategoriální proměnné “pohlaví”.

Rychlá sumarizace data.frame

Toto jsou nejpoužívanější funkce:

dim(holky) #1: počet řádků, 2: počet sloupců 
## [1] 4 3
str(holky)
## 'data.frame':    4 obs. of  3 variables:
##  $ Křestní jméno          : chr  "Anča" "Máňa" "Cilka" "Pepina"
##  $ Která směna            : chr  "dopolední" "dopolední" "odpolední" "odpolední"
##  $ Nějaká číselná proměnná: num  3 4 5.5 NA
summary(holky)
##  Křestní jméno      Která směna        Nějaká číselná proměnná
##  Length:4           Length:4           Min.   :3.000          
##  Class :character   Class :character   1st Qu.:3.500          
##  Mode  :character   Mode  :character   Median :4.000          
##                                        Mean   :4.167          
##                                        3rd Qu.:4.750          
##                                        Max.   :5.500          
##                                        NA's   :1

Mimochodem, tady nastává okamžik, kdy bychom byli rádi, kdyby kategoriální proměnné byly uložené ve faktorech místo obyčejných znakových vektorů. Podívejme se, o kolik víc informace by funkce summary vydolovala z faktorů:

holky[,1:2] <- lapply(holky[,1:2], factor) # změníme znakové sloupce na faktory
#(pozor, nejde použít apply po sloupcích, musí se použít lapply. Vysvětlení je 
# složité, prostě to tak je.)

summary(holky) # a z faktorů máme informativnější summary!
##  Křestní jméno    Která směna Nějaká číselná proměnná
##  Anča  :1      dopolední:2    Min.   :3.000          
##  Cilka :1      odpolední:2    1st Qu.:3.500          
##  Máňa  :1                     Median :4.000          
##  Pepina:1                     Mean   :4.167          
##                               3rd Qu.:4.750          
##                               Max.   :5.500          
##                               NA's   :1

Rozdíl je vidět na první pohled: R neumí porovnávat jednotlivé elementy znakových vektorů mezi sebou, to umějí jenom faktory. Proto summary znakových sloupců ve formě vektorů obsahovalo jenom údaj o délce vektoru a upřesnění jeho datového typu (class a modus), zatímco u faktorů umělo vyjmenovat všechny unikátní hodnoty a spočítat frekvenci každé.

Funkce summary nám taky řekne, jestli v datovém souboru máme nějaké nevyplněné hodnoty, což je velmi důležité. Některé statistické metody se s nimi neumějí vyrovnat, takže před jejich uplatněním musíme nekompletní pozorování vyhodit nebo si rozmyslet, čím to NA nahradíme. Některé metody si s nimi poradí, ale musí se jim správně nastavit příslušný parametr.

Vyšperkovanou verzí funkce str() pro tabulky je funkce glimpse() z balíčku dplyr. Stejně jako balíček data.table, který jsme zmiňovali v souvislosti s načítáním dat, i dplyr je velmi rozsáhlý balíček tvořící jakýsi dialekt R pro datovou analýzu a postupně se stal jedním z pilířů profesionální datové analýzy. My ho teď pomineme, až na tu funkci glimpse(). Tato funkce se snaží zobrazit maximum z dat na jedné šíři obrazovky.

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
glimpse(holky)
## Observations: 4
## Variables: 3
## $ Křestní jméno           (fctr) Anča, Máňa, Cilka, Pepina
## $ Která směna             (fctr) dopolední, dopolední, odpolední, odpo...
## $ Nějaká číselná proměnná (dbl) 3.0, 4.0, 5.5, NA
glimpse(mtcars)
## Observations: 32
## Variables: 11
## $ mpg  (dbl) 21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19....
## $ cyl  (dbl) 6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, ...
## $ disp (dbl) 160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 1...
## $ hp   (dbl) 110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, ...
## $ drat (dbl) 3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.9...
## $ wt   (dbl) 2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3...
## $ qsec (dbl) 16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 2...
## $ vs   (dbl) 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ am   (dbl) 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ gear (dbl) 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, ...
## $ carb (dbl) 4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, ...
glimpse(iris)
## Observations: 150
## Variables: 5
## $ Sepal.Length (dbl) 5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9,...
## $ Sepal.Width  (dbl) 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1,...
## $ Petal.Length (dbl) 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5,...
## $ Petal.Width  (dbl) 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1,...
## $ Species      (fctr) setosa, setosa, setosa, setosa, setosa, setosa, ...

Dlouhý vs. široký formát dat

Long vs. wide data format je přibližný, ale v praxi často používaný termín. Typická dlouhá data mají víc řádků než sloupců a široká data mívají víc sloupců než řádků. Formát můžeme změnit:

  • transpozicí tabulky
  • rozdělováním nebo slučováním sloupců.

Pozor, každý z těchto postupů se používá za jiným účelem!

Když transponujeme tabulku, tj. když z řádků uděláme sloupce a ze sloupců řádky, měníme celou perspektivu analýzy dat. Co bylo proměnnou, je teď pozorováním, se všemi důsledky pro další statistickou analýzu.

Transpozice tabulky (i matice) se provede pomocí funkce t() a její působení si ukážeme na tabulce iris. V netransponované tabulce pozorujeme 150 nasbíraných kosatcovitých květin různých druhů (v head() je vidět jenom setosa) vzhledem k délce okvětních lístků, šířce okvětních lístků a délce a šířce nějakých špičatých lístků hned pod květem. K čemu taková data? Můžeme z nich např. zjistit, jak dlouhé a široké jedny nebo druhé lístky má který druh a jestli jsou rozdíly mezi nimi dostatečné na to, aby se na základě měření 50 exemplářů každého druhu daly zobecnit na veškerou populaci těchto tří druhů kosatců.

head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

Když tabulku transponujeme, asi bude těžké vymyslet, co by se z ní dalo analýzou sloupců získat pro jednotlivé řádky, protože se nám tady jako proměnné tváří jednotlivé exempláře rostlin. Výzkumné otázky by musely znít asi takto: co se stane s délkou okvětních lístků, když ji pozoruji na květině 1, 2, květině n? Objevím signifikantní rozdíl mezi délkou a šířkou okvětních lístků? Ale možná ani to ne. Navíc budu mít sice hodně proměnných, ale zoufale málo dat, protože mám jenom 5 pozorování.

tiris <- t(iris)
tiris[,c(1:3, 50:52, 100:103)]
##              [,1]     [,2]     [,3]     [,4]     [,5]         [,6]        
## Sepal.Length "5.1"    "4.9"    "4.7"    "5.0"    "7.0"        "6.4"       
## Sepal.Width  "3.5"    "3.0"    "3.2"    "3.3"    "3.2"        "3.2"       
## Petal.Length "1.4"    "1.4"    "1.3"    "1.4"    "4.7"        "4.5"       
## Petal.Width  "0.2"    "0.2"    "0.2"    "0.2"    "1.4"        "1.5"       
## Species      "setosa" "setosa" "setosa" "setosa" "versicolor" "versicolor"
##              [,7]         [,8]        [,9]        [,10]      
## Sepal.Length "5.7"        "6.3"       "5.8"       "7.1"      
## Sepal.Width  "2.8"        "3.3"       "2.7"       "3.0"      
## Petal.Length "4.1"        "6.0"       "5.1"       "5.9"      
## Petal.Width  "1.3"        "2.5"       "1.9"       "2.1"      
## Species      "versicolor" "virginica" "virginica" "virginica"

V lingvistických úlohách můžeme spíš najít smysluplnou interpretaci pro obě transpozice. Typicky třeba, když vyhodnocujeme výzkum založený na několikanásobné lingvistické anotaci, tj. když několik nezávislých anotátorů hodnotí stejná kritéria ve stejném textu. Příklad: dataset WordSim353 - 353 anglických slovních párů, u kterých cca 13 dobrovolníků přidělovalo číselné skóre 0-10 podle subjektivně vnímané vzájemné souvislosti slov v daném páru.

Na jedné straně se dala zkoumat vzájemná souvislost slov - tehdy byly slovní páry pozorováními a názory anotátorů proměnnými. Na druhé straně bylo ale taky potřeba ověřit vzájemnou shodu anotátorů, aby se dalo říct, za jak přesná můžeme považovat průměrná nebo mediánová skóre od všech anotátorů dohromady. Pak byl samozřejmě každý anotátor jedním objektem pozorování, tedy měl vlastní řádek, a každý slovní pár tvořil jednu proměnnou. Z tabulky (a hlavně z výsledného grafu) byl vidět jejich rozptyl na každém páru i subjektivní zkreslení (bias), např. kdo neviděl souvislosti téměř nikde a kdo se snažil indikovat každou stopu souvislosti.

Naším hlavním zájmem ale protentokrát zůstávají úpravy bez transpozice, čili dilema slučování a rozdělování sloupců. To je spíš programátorská a editační úloha než statistická, ale je nutným předpokladem správně provedené statistické analýzy.

Transformace špatně uspořádaných dat do správně uspořádaných dat

Balíček tidyr

Balíček tidyr je zjednodušeným výběrem a alternativní implementací bohatšího balíčku reshape2 a říká se o něm, že je intuitivnější. Obsahuje řadu funkcí, které usnadňují přechod z neuspořádaných dat na správně uspořádaná data. Na několik nejčastěji používaných se podíváme.

Funkce gather()

Používá se, když chceme z většího množství sloupců udělat dva, protože stávající data se prohřešují proti čtvrtému principu správně uspořádaných dat, že vlastní sloupec zasluhuje statistická proměnná, ne každá hodnota statistické proměnné.

Těm dvěma se říká key a value. Do key se sesypou názvy všech sloupců, které chceme gather. Ve value pak u každého key bude hodnota, kterou ta bývalá statistická proměnná dosahovala. Tady je [krátká animace] (https://www.youtube.com/watch?v=ztDSoZcTyWQ&feature=youtu.be) místo dlouhých slov.

library(tidyr)
wide_df <- data.frame(c("a", "b"), c("oh", "ah"), c("bla", "ble"), stringsAsFactors = FALSE)
colnames(wide_df) <- c("first", "second", "third")
wide_df
##   first second third
## 1     a     oh   bla
## 2     b     ah   ble
long_01_df <- gather(wide_df, my_key, my_value, second, first, third, factor_key = TRUE)
long_01_df
##   my_key my_value
## 1 second       oh
## 2 second       ah
## 3  first        a
## 4  first        b
## 5  third      bla
## 6  third      ble
long_02_df <- gather(wide_df, my_key, my_value, second, first, -third, factor_key = TRUE)
long_02_df
##   third my_key my_value
## 1   bla second       oh
## 2   ble second       ah
## 3   bla  first        a
## 4   ble  first        b
long_03_df <- gather(wide_df, my_key, my_value, -third, second, first, factor_key = TRUE)
long_03_df
##   third my_key my_value
## 1   bla  first        a
## 2   ble  first        b
## 3   bla second       oh
## 4   ble second       ah
long_04_df <- gather(wide_df, my_key, my_value, second, first, factor_key = TRUE)
long_04_df
##   third my_key my_value
## 1   bla second       oh
## 2   ble second       ah
## 3   bla  first        a
## 4   ble  first        b

Funkce spread()

Tato funkce dělá z dlouhých tabulek široké. Chce znát tabulku, název sloupce tím, co má brát jako klíč a udělat z toho názvy nových sloupců, a název sloupce s tím, co má brát jako hodnoty a rozřadit to do těch nově vytvořených a pojmenovaných sloupců. Jestli k tomu chcete video, pusťte si pozpátku to o funkci gather() :-)

Příklad:

wide_02_df <- spread(long_04_df, my_key, my_value)
wide_02_df
##   third second first
## 1   bla     oh     a
## 2   ble     ah     b

Funkce separate()

Používá se, když v každé buňce sloupce je vlastně víc proměnných. Například údaj o tlaku v našem datasetu pacienti_AB_sorted.

head(pacienti_AB_sorted)
##   pacienti_A_1_jmeno pacienti_A_1_tlak pacienti_A_2_kurak
## 1           Boháč N.            136/93                ano
## 2          Hlavsa T.            131/95                 ne
## 3           Chudý B.            133/84                 ne
## 4          Kotrba H.            130/94                ano
## 5        Malinová S.            135/81                ano
## 6           Novák J.           140/100                 ne

S takovým číselným údajem nelze provádět žádné automatické výpočty. Víme ale, že je to údaj o dvou veličinách: systolický tlak a diastolický tlak. Logicky by každý zasloužil vlastní sloupec. A s tím nám pomůže funkce separate() z balíčku tidyr.

Funkce separate() chce znát název tabulky, název sloupce k roztržení a znakový vektor názvů sloupců, které má vytvořit_. Obsah buněk roztrhne na znaku, který detekuje jako “ani číslice, ani písmeno”. Kdyby takových bylo v buňkách víc různých a my jsme přesto chtěli jenom jedno roztržení, můžeme separátor napsat do parametru sep, který je nám povědomý z mnoha jiných funkcí. Taky si můžeme zvolit, jestli chceme původní sloupec zachovat, nebo má zmizet. Defaultně zmizí (remove = TRUE). Nové sloupce se defaultně tvoří jako číslicové a znakové vektory. Parametrem convert = TRUE by se ze znakových vektorů staly faktory a z číselných typ double (to je nějaký strojově lépe zpracovatelný formát pro číslo, který ale zabere víc paměti). Při složitých editacích, kdy v některých buňkách může vzniknout víc “trhanců”, se používají různá nastavení parametru extra.

Příklad:

pacienti_tidy <- separate(pacienti_AB_sorted, pacienti_A_1_tlak, into = c("systolický tlak", "diastolický tlak"))
head(pacienti_tidy)
##   pacienti_A_1_jmeno systolický tlak diastolický tlak pacienti_A_2_kurak
## 1           Boháč N.             136               93                ano
## 2          Hlavsa T.             131               95                 ne
## 3           Chudý B.             133               84                 ne
## 4          Kotrba H.             130               94                ano
## 5        Malinová S.             135               81                ano
## 6           Novák J.             140              100                 ne

Totéž půjde udělat s příjmeními a iniciálami křestních jmen:

pacienti_tidy_jmena_01 <- separate(pacienti_tidy, pacienti_A_1_jmeno, into = c("příjmení", "iniciála"))
## Warning: Too many values at 13 locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
## 11, 12, 13
head(pacienti_tidy_jmena_01)
##   příjmení iniciála systolický tlak diastolický tlak pacienti_A_2_kurak
## 1    Boháč        N             136               93                ano
## 2   Hlavsa        T             131               95                 ne
## 3    Chudý        B             133               84                 ne
## 4   Kotrba        H             130               94                ano
## 5 Malinová        S             135               81                ano
## 6    Novák        J             140              100                 ne

A tady je zrovna pěkný příklad, kdy funkce našla dva znaky, které pokládala za separátory, totiž mezeru a tečku. Co zvolí spíš, není dokumentováno. Je tedy lepší separátor specifikovat (a tím si taky nesmazat tu tečku v iniciále):

pacienti_tidy_jmena <- separate(pacienti_tidy, pacienti_A_1_jmeno, into = c("příjmení", "iniciála"), sep = " ")
head(pacienti_tidy_jmena)
##   příjmení iniciála systolický tlak diastolický tlak pacienti_A_2_kurak
## 1    Boháč       N.             136               93                ano
## 2   Hlavsa       T.             131               95                 ne
## 3    Chudý       B.             133               84                 ne
## 4   Kotrba       H.             130               94                ano
## 5 Malinová       S.             135               81                ano
## 6    Novák       J.             140              100                 ne

Funkce unite()

Je opakem funkce separate(). Chce znát název tabulky, pak název nově tvořeného sloupce, kam se sesypou obsahy buněk jiných sloupců (argument col a nesmí to být v uvozovkách), za sebou názvy sloupců, jejichž buňky se mají horizonálně sloučit, požadovaný separátor (default je "_") a parametr remove, který určuje, zda se mají ty původní buňky odstranit, a defaultně je nastavený na TRUE.

Příklad: sloučíme zpátky příjmení a iniciály křestního jména, ale tentokrát aspoň dáme iniciálu jako první (i když je to v reálném životě kontraproduktivní).

pacienti_krestni_napred <- unite(pacienti_tidy_jmena, osoba, iniciála, příjmení, sep = " ")
pacienti_krestni_napred
##            osoba systolický tlak diastolický tlak pacienti_A_2_kurak
## 1       N. Boháč             136               93                ano
## 2      T. Hlavsa             131               95                 ne
## 3       B. Chudý             133               84                 ne
## 4      H. Kotrba             130               94                ano
## 5    S. Malinová             135               81                ano
## 6       J. Novák             140              100                 ne
## 7   L. Skákalová             138               91                 ne
## 8       R. Starý             132               86                ano
## 9  M. Suchánková             139               83                 ne
## 10    O. Škorpil             137               98                ano
## 11     E. Hotová             133              100               <NA>
## 12      F. Krůta             132               81               <NA>
## 13    A. Mašková             140               85               <NA>

To osvobození od uvozovek v názvech sloupců je pro nás, mluvčí jazyka s diakritikou, dost zneklidňující kvůli kódování. Ve Windows to v konzoli prošlo, i když editor značil chyby, ale mohl by to být zdroj nepříjemností. Možná bychom si kvůli dobré praxi měli pojmenovávat sloupce “opravdu česky” až po vyčištění!

Opravdu vřele doporučovaná četba

[Data Tidying - kapitola z knihy H. Wickhama a G. Grolemunda Data Science with R, která má vyjít v nakladatelství O´Reilly v srpnu 2016 ] (http://garrettgman.github.io/tidying/)

Tidy data - názornější verze dokumentace k balíčku tidyr, tzv. vignette