Knihovna ggplot2: implementace Grammar of Graphics (Leland Wilkinson), http://docs.ggplot2.org/
Krásný „tahák“ s přehledem základních možností: http://www.rstudio.com/resources/cheatsheets/ (Data Visualization Cheat Sheet)
Přehledné grafy od nejjednodušších po pokročilé: https://psyteachr.github.io/msc-data-skills/ggplot.html
Příklady grafů v R: http://www.cookbook-r.com/Graphs/
Grafy a obrázky zadáváme pomocí „podstatných jmen“, „přídavných jmen“ a „sloves“.
Vycházíme z dat v dobře uspořádaném dataframe (formát tidy data). Faktorové proměnné musejí být v dataframe skutečně označeny jako faktorové (viz kapitola 2. Faktorové proměnné), abychom znázornili, že se jedná o diskrétní kategorie, které nám mohou např. rozdělovat data do skupin. Pozor! Použití faktorových proměnných je obzvláště nutné u číselných hodnot, které mají být chápány jako diskrétní kategorie. Pokud máme např. proměnnou PořadíČtení s hodnotami 1 a 2, při jejím použití na x-ovou osu grafů může dojít k vytvoření reálné spojité osy, pokud je proměnná typu numeric. Jestliže ji převedeme na faktorovou proměnnou, budou správně vytvořeny dvě diskrétní hodnoty.
Vše by mělo být v datech hezky pojmenované, protože se ze jmen automaticky stávají popisky grafů. Tedy raději proměnná ‘Pohlaví’ než ‘pohl’, hodnoty spíše ‘muž’ a ‘žena’ než 0 a 1.
Dále přidáváme tzv. vzhled, estetiku (aesthetics), kde určujeme, co z dat se má mapovat na osy x a y, co má určovat velikost, tvar a barvu. Legendy (popisky) se tvoří podle těchto vlastností automaticky.
Pomocí facets je možné rozdělit obrázek do více panelů na základě jedné nebo více diskrétních kategorií (faktorové proměnné). Jedná se tedy opět o jistou formu aesthetics, kdy se automaticky zjistí, kolika různých hodnot daná proměnná nabývá, podle toho se data rozdělí do příslušných skupin a pro každou se vykreslí samostatný a přehledně popsaný graf.
Důležité je určit druh tvaru (geoms), jakým se mají data znázornit. Zda to mají být body, čáry, sloupce, histogramy, odhady hustoty pravděpodobnosti, krabicové grafy apod. V rámci jednoho zobrazení můžeme přidat současně klidně více různých geoms, pokud to dává smysl.
Aby toho nebylo málo, připojit můžeme statistické ukazatele (stats). Může se jednat například o regresi (automatické vykreslení pásu 95% pravděpodobnosti výskytu je samozřejmostí), zobrazení středu dat včetně případného 95% intervalu spolehlivosti, ale také třeba elipsy znázorňující 2D oblast výskytu 95 % nejčastějších hodnot. To vše pomocí jednoduchého příkazu, což ušetří mnoho náročné práce.
Dále je možné přidávat vlastní popisky (labels), měnit souřadnicový systém (coordinate systems) a volit různá témata (themes), protože základní vzhled je sice hezký, ale občas potřebujeme jednodušší zobrazení.
Pokud ještě není knihovna tidyverse nainstalována, učiníme tak příkazem (stačí provést jednou)
install.packages("tidyverse")
Před použitím knihovny je potřeba ji nejdříve zavést příkazem (nutné vždy po startu R)
library(tidyverse)
Stáhnout soubor s daty: modre_cervene.csv.
tabulka <- read.csv("modre_cervene.csv", encoding = "UTF-8")
ggplot(data = tabulka, aes(x = x, y = y)) +
geom_point()
ggplot(data = tabulka, aes(x = x, y = y, color = group)) +
geom_point()
ggplot(data = tabulka, aes(x = x, y = y, shape = group)) +
geom_point()
ggplot(data = tabulka, aes(x = x, y = y, shape = group, color = group)) +
geom_point()
ggplot(data = tabulka, aes(x = x, y = y, shape = group, color = group)) +
geom_point(size = 3)
ggplot(data = tabulka, aes(x = x, y = y, shape = group, color = group)) +
geom_point(size = 3) +
stat_smooth(method = "lm") # lineární regrese vč. 95% pásů
Pomocí knihovny plotly je možné z obrázků ggplot udělat interaktivní grafy s možnostmi kurzoru pro odečítání hodnot, zoomování a rolování.
library(plotly)
g <- ggplot(data = tabulka, aes(x = x, y = y, shape = group, color = group)) +
geom_point(size = 3) +
stat_smooth(method = "lm")
ggplotly(g)
ggplot(data = pressure, aes(x = temperature, y = pressure)) +
geom_line()
Stáhnout soubor s daty: vokalyhz.txt.
data <- read.table("vokalyhz.txt", header = TRUE, sep = ",")
str(data) # Je vidět, že Vowel a Sex jsou faktorové, což je dobré. SpeakerID by ale také měl být faktor.
## 'data.frame': 3000 obs. of 7 variables:
## $ Vowel : chr "a" "a" "a" "a" ...
## $ Sex : chr "f" "f" "f" "f" ...
## $ F0.mean : num 212 219 252 244 231 ...
## $ SpeakerID: int 1 2 3 4 7 8 10 11 12 13 ...
## $ F1_Hz : num 819 713 748 791 788 ...
## $ F2_Hz : num 1354 1393 1495 1754 1622 ...
## $ F3_Hz : num 2595 2634 2497 2443 2961 ...
data$SpeakerID <- factor(data$SpeakerID)
# bylo by hezké přejmenovat hodnoty faktorové proměnné Sex
data$Sex <- factor(data$Sex)
levels(data$Sex) # původní názvy
## [1] "f" "m"
data$Sex <- case_match(data$Sex, "f" ~ "žena", "m" ~ "muž") # starší funkce: recode()
### --- vsuvka pro "chytré hlavy": Pokročilejší přejmenování levels (v úplně jiné hypotetické tabulce) ---
### Hypotetická situace: V tabulce "tab" mám šikovně zakódované francouzské mluvčí (sloupec speaker)
### kódy Fr1 až Fr8. Ty bych chtěl nechat, jak jsou.
### Také tam mám ale ne příliš vhodně pojmenované české mluvčí pod různými
### číselnými kódy začínajícími písmeny HC, např. HC108, HC215, HC232 apod.
### Ty bych rád automaticky přejmenoval na styl Cz1 atd., nehledě na původní čísla.
### Jak na to?
### indHC <- grep("HC", levels(tab$speaker), fixed = TRUE) # Naleznu indexy jen těch, co obsahují HC
### levels(tab$speaker)[indHC] <- paste0("C", 1: length(indHC)) # Zjistím, kolik jich je, pomocí paste0()
### # slepím "C" s postupně rostoucími indexy,
### # jména na pozici nalezených indexů nahradím těmito novými názvy.
### --- konec vsuvky ---
# a ještě přejmenovat proměnné
data <- data %>% rename(Vokál = Vowel, Pohlaví = Sex)
str(data) # výrazně hezčí forma
## 'data.frame': 3000 obs. of 7 variables:
## $ Vokál : chr "a" "a" "a" "a" ...
## $ Pohlaví : chr "žena" "žena" "žena" "žena" ...
## $ F0.mean : num 212 219 252 244 231 ...
## $ SpeakerID: Factor w/ 75 levels "1","2","3","4",..: 1 2 3 4 7 8 10 11 12 13 ...
## $ F1_Hz : num 819 713 748 791 788 ...
## $ F2_Hz : num 1354 1393 1495 1754 1622 ...
## $ F3_Hz : num 2595 2634 2497 2443 2961 ...
ggplot(data = data, aes(x = F1_Hz)) + # volba dat a nejjednodušší aesthetics: na ose x jsou hodnoty F1_Hz
geom_histogram() # přidání geom: histogram
Pro začátek to není špatné, ale máme všechny vokály v jednom histogramu. Co je zkusit odlišit barvou? Přidáme tedy pokyn do aesthetics, ať barvu nastaví podle Vokál.
ggplot(data = data, aes(x = F1_Hz, color = Vokál)) + # ať nastaví barvu podle vokálu Vokál
geom_histogram() # histogram
Aha, tím se nastavila ale jen barva okraje. Chtělo by to výplň. Podíváme se do “Cheat Sheet” a vidíme, že lze u histogramu nastavit také fill.
ggplot(data = data, aes(x = F1_Hz, fill = Vokál)) + # raději tedy barvu výplně podle vokálu Vokál
geom_histogram() # histogram
Nebylo by hezší zobrazit spíše odhad hustoty pravděpodobnosti (tzv. kernel density function)?
ggplot(data = data, aes(x = F1_Hz, color = Vokál)) +
geom_density() # odhad hustoty pravděpodobnosti
ggplot(data = data, aes(x = F1_Hz, fill = Vokál)) + geom_density(alpha = 0.5)
Hezké, ale pořád máme muže a ženy dohromady. Udělal bych raději pro každou skupinu obrázek zvlášť, na řadu přicházejí facets.
ggplot(data = data, aes(x = F1_Hz, fill = Vokál)) +
geom_density(alpha = 0.5) +
facet_grid(. ~ Pohlaví) # formát: řádky ~ sloupce. Tečka znamená "nerozdělovat". Zde tedy říkáme, že chceme 1 řádek a sloupce podle Pohlaví.
Vidíme, že ženy jsou výše. Když to jde tak snadno, co se vrátit k histogramům a sloupce udělat podle vokálu a řádky podle pohlaví?
ggplot(data = data, aes(x = F1_Hz, fill = Vokál)) +
geom_histogram() +
facet_grid(Pohlaví ~ Vokál) # formát: řádky ~ sloupce.
ggplot(data = data, aes(x = F1_Hz, fill = Vokál)) + geom_density(alpha = 0.5) +
facet_grid(Pohlaví ~ Vokál)
Zkusme krabicové grafy. Osa x (jednotlivé krabice) budou podle Vokál, y (hodnoty v krabici) zase F1_Hz.
ggplot(data = data, aes(x = Vokál, y = F1_Hz)) +
geom_boxplot()
A opět rozdělíme podle pohlaví
ggplot(data = data, aes(x = Vokál, y = F1_Hz)) +
geom_boxplot() +
facet_grid(Pohlaví ~ .)
A co ušetřit místo a usnadnit porovnání tím, že krabice rozdělíme jen barvami?
ggplot(data = data, aes(x = Vokál, y = F1_Hz, color = Pohlaví)) +
geom_boxplot()
Nebo zase raději fill?
ggplot(data = data, aes(x = Vokál, y = F1_Hz, fill = Pohlaví)) +
geom_boxplot()
Občas narazíme také na „profesionální“ krabicové grafy (tzv. notchech box plots) znázorňující „vykousnutím“ interval spolehlivosti mediánu. Tím se dá krásně porovnávat, zda jsou středy skupin významně odlišné. Následující obrázek má ovšem jednu zásadní skrytou vadu.
ggplot(data = data, aes(x = Vokál, y = F1_Hz, fill = Pohlaví)) +
geom_boxplot(notch = TRUE)
V čem je problém? Podíváme-li se na počty vokálů od jednotlivých mluvčích, zjistíme, že jsme se v grafu dopustili problému pseudoreplikace, jelikož každý mluvčí namluvil daný vokál vždy osmkrát. S tím ovšem interval spolehlivosti nepočítá, bere totiž každý řádek tabulky jako nezávislý náhodný výběr ze základního souboru. Zde je ovšem vždy 8 řádků závislých, takže interval vyjde přehnaně optimisticky úzký.
table(data$Vokál, data$SpeakerID)
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
## a 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## e 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## i 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## o 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## u 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
##
## 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
## a 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## e 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## i 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## o 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## u 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
##
## 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
## a 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## e 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## i 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## o 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
## u 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
Řešením je pro každou osobu vypočítat průměr nebo medián, takže nová tabulka bude ve formátu, že co řádek, to jiná osoba. Interval spolehlivosti uvidí, že pozorování je osmkrát méně a nebude ani zmaten přehnaně nízkým rozptylem položek (když vokál vysloví osmkrát jedna osoba, rozptyl bude jistě menší, než kdyby se jednalo o osm různých osob).
dataMedian <- data %>% group_by(SpeakerID, Pohlaví, Vokál) %>% summarise(F1_Hz = median(F1_Hz))
ggplot(data = dataMedian, aes(x = Vokál, y = F1_Hz, fill = Pohlaví)) +
geom_boxplot(notch = TRUE)
Ovšem, kdo jednou zkusí houslové grafy, už žádné jiné nechce! Kromě úspornosti známé z krabicových grafů poskytují detailnější představu o tvaru rozdělení (zobrazují tzv. kernel density function). Že nevypadají jako houslové? To záleží na tvaru rozdělení dat.
ggplot(data = dataMedian, aes(x = Vokál, y = F1_Hz, fill = Pohlaví)) +
geom_violin()
Jedná se však o hodně moderní záležitost a než si na ně společnost zvykne, bude opatrnější zobrazovat houslové grafy dohromady s krabicovými.
ggplot(data = dataMedian, aes(x = Vokál, y = F1_Hz, fill = Pohlaví)) +
geom_violin() +
geom_boxplot(notch=TRUE, position = position_dodge(width=.9), width=.15)
Zkusme jiné zobrazení, F1 vs. F2 pomocí teček (scatter plot).
ggplot(data = data, aes(x = F2_Hz, y = F1_Hz, color = Vokál)) +
geom_point()
A co nastavit tvar bodu podle vokálu, ať to není rozlišno jen barvou? A obrátíme osy, aby to bylo jak z učebnice i-e-a-o-u zleva doprava a osa y odpovídala vertikální poloze jazyka.
ggplot(data = data, aes(x = F2_Hz, y = F1_Hz, color = Vokál, shape = Vokál)) +
geom_point() +
scale_x_reverse() +
scale_y_reverse()
Krásné. Teď už by to snad jen chtělo přidat elipsy znázorňující 95 % dat. Přidáme tedy stats.
ggplot(data = data, aes(x = F2_Hz, y = F1_Hz, color = Vokál, shape = Vokál)) +
geom_point() +
scale_x_reverse() +
scale_y_reverse() +
stat_ellipse()
A nešly by udělat vyplněné?
ggplot(data = data, aes(x = F2_Hz, y = F1_Hz, color = Vokál, shape = Vokál)) +
geom_point() +
scale_x_reverse() +
scale_y_reverse() +
stat_ellipse(geom = "polygon", aes(fill = Vokál))