Data.frames jsou nejobvyklejší formou tabulek. Uvažování o tabulkách vyžaduje dva úhly pohledu:
programování
datová analýza
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.
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()!
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.
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"
K datům v data.frame přistupujeme přes jejich pozice, jména, nebo přes výsledky logických testů.
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í.
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Í"
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.
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
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" ...
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é.
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í řá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í.
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
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
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.
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 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:
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í”.
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, ...
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:
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.
tidyrBalíč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.
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
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
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
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í!
[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