library(tidyverse)
library(hexbin)
14 Vizualizace dat s balíkem ggplot2
V R existuje celá řada nástrojů pro vizualizaci dat, které jsou opravdu velmi heterogenní. Z úvodních lekcí znáte příklady tzv. base
grafiky, která přichází s každou instalací R. Příklad může být Vám známá funkce plot()
. Tato základní implementace nástrojů pro vizualizaci dat sleduje podobnou logiku jako je obvyklá v ostatních systémech: uživatel explicitně sděluje systému co a jak má vykreslit: “Na souřadnice [X,Y] nakresli zelený křížek”.
Součástí tidyverse je balík ggplot2, který přistupuje k vizualizaci zcela jiným způsobem – a to na mnoha úrovních. ggplot2 je založen na teorii “grammar of graphics” (Wilkinson 2005, 2010) a pracuje na mnohem vyšší úrovni abstrakce. Uživatel vlastně předává funkci jako vstup popis dat a konkrétní provedení vizualizace nechává na ni. Tento způsob práce je poměrně neobvyklý, ale po jeho zvládnutí už neexistuje cesta zpět.
ggplot2 má jedno základní omezení – kreslí pouze 2D obrázky. Na konci kapitoly však uvidíte, že nic jiného pravděpodobně ani nepotřebujete.
Co se v této lekci naučíte?
- základní logiku fungování ggplot2
- základní logiku ovládání ggplot2
- provést jednoduché vizualizace dat a modifikovat jejich vzhled
Složitější úkoly přijdou později.
Let’s lock and load…
Poznámka
ggplot2 je tak populární, že existují i jeho porty pro jiné jazyky – například pro Python. I v samotném světě R existuje balík, který s ggplot2 sdílí nejen filosofii, ale i tvůrce. Balík ggvis obsahuje oproti ggplot2 některá koncepční vylepšení. Celkově je však orientován spíše na interaktivní grafiku a proto pro naše účely není tak vhodný. Nicméně kdo pochopí ggplot2 zvládne lehce i ggvis.
Výhodou popularity ggplot2 je i obrovské množství balíků, které možnosti ggplot2 doplňují nebo rozšiřují. Pokud tedy něco neumí samotný ggplot2 je rozumné porozhlédnout se po balících, které hledanou funkcionalitu doplňují. Neúplnou galerii “rozšíření” můžete najít například zde: https://exts.ggplot2.tidyverse.org/
Poznámka 2
Podobně jako ostatní součásti tidyverse se i ggplot2 dramaticky vyvíjí. Tento materiál byl například původně vytvořen s verzí 2.x. Nyní (září 2019) je aktuální verze 3.4.0. Nové verze přinesly zásadní změny, které se však v drtivě většině odehrály “pod povrchem” a pro uživatele nejsou patrné.
Velkou změnou pro uživatele je uvedení geom_sf()
, který radikálním způsobem zjednodušuje kreslení map. Nejnovější verze ggplot2 také mění jména estetik u geom_line()
. Tloušťka linky se nyní logicky jmenuje linewidth
a nikoliv size
.
14.1 Logika fungování ggplot2
Pro vysvětlení fungování ggplot2 použijeme schématický obrázek:
ggplot2 je postaven na teorii layered grammar of graphics, každý obrázek vytvořený za jeho pomoci se skládá z několika prvků:
Od dat k vizualizaci
Vizualizace dat, která se skládá z jedné, nebo mnoha překrývajících se vrstev (layers). Každá vrstva přidává do výsledného obrázku jednu dodatečnou vizualizaci. Schématický obrázek má dvě vrstvy obsahující vizualizaci dat. První vrstva (bodový graf) obsahuje zobrazení surových dat – tedy dat, jak jsou. Druhá vrstva vykresluje statistickou transformaci surových dat – proloženou křivku. Obrázek je tedy konstruován podobně, jako byste přes sebe překládaly průsvitné fólie pokreslené fixem. Výsledný obrázek by se postupně rozšiřoval o další a další prvky. Nicméně prvky přidané později překrývají prvky přidané dříve.
Jako příklad vizualizace dat můžeme zkonstruovat následující bodový graf, který obsahuje dvě pozorování:
Z tohoto obrázku vidíme, že pozorování jsou dvě a že nejsou identická. Nic dalšího říci nemůžeme.
Zpět od vizualizace k datům
Aby byl obrázek srozumitelný, musí být k vizualizaci dat připojeny prvky, které ji umožňují porozumět. Tedy umožňují uživateli převést vizualizaci zpět do dat. Takové prvky jsou škály, legendy, osy, atp. Viz obrázek:
Vzhled obrázku
Poslední skupina prvků upravuje celkový vzhled obrázku. Nemá žádný vztah ke dvěma předchozím kategoriím. Tyto prvky upravují pouze celkový vzhled obrázku (velikost písma, barvu pozadí,…).
Následující příklad ukazuje možný vzhled obrázku:
V ggplot2 se všechny tři skupiny prvků ovládají nezávisle na sobě. Pro vytvoření každého prvku existují speciální funkce.
14.2 Základní vizualizace: vrstva po vrstvě
Celkovou konstrukci obrázku můžeme ilustrovat na tabulce diamonds
, která je součástí balíku ggplot2
. Tabulka obsahuje informace o jednotlivých kamenech (viz ?diamonds
). Tabulka obsahuje přes 50 tisíc pozorování. Jejich vykreslování by zejména na pomalejších počítačích trvalo velmi dlouho. Proto budeme pracovat pouze s 500 náhodně vybranými řádky:
<- diamonds %>% sample_n(500)
diamonds print(diamonds)
# A tibble: 500 × 10
carat cut color clarity depth table price x y z
<dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
1 1.5 Very Good H VVS1 63.8 57 11654 7.17 7.21 4.59
2 0.32 Ideal D VS1 61.8 56 736 4.39 4.41 2.72
3 0.3 Ideal F VVS1 62.4 56 868 4.28 4.31 2.68
4 0.71 Ideal F VS1 61.4 56 3710 5.75 5.78 3.54
5 0.83 Very Good D VS1 61.3 57.2 3984 6.02 6.11 3.72
6 0.32 Ideal D VVS1 61 57 1064 4.45 4.43 2.71
7 0.92 Ideal E SI2 62.2 56 4011 6.2 6.24 3.87
8 1.24 Good H VS2 58.3 58 7210 7.09 7.15 4.15
9 0.44 Ideal I VVS1 60.9 56 1145 4.9 4.96 3
10 1.26 Very Good I SI1 61.9 58 6318 6.97 6.92 4.3
# ℹ 490 more rows
Našim cílem je vytvořit podobný obrázek, který byl použit pro schématickou ilustraci fungování ggplot2: Vykreslit závislost ceny (price
), váhy (carat
) a kvality řezu (cut
).
Základ každého obrázku vytvoříme pomocí funkce ggplot()
:
ggplot(data = NULL, mapping = aes())
Funkce má dva základní argumenty: data
obsahují tabulku s daty, která se mají vizualizovat. Parametr mapping
pak přijímá základní pravidla, podle kterých se má vizualizace řídit. Přesněji řečeno pravidla vytvořená funkcí aes()
, která určují, jaká proměnná se má mapovat na kterou estetiku.
Pro pochopení těchto pojmů je užitečné zamyslet se nad tím, jak vlastně funguje bodový graf. Jeho podstatou je to, že jedno pozorování, je reprezentováno jedním bodem. Jaké vlastnosti (estetiky) však tento bod může mít?
- Pozici v rovině obrázku: souřadnice na ose x (
x
) a y (y
) - Tvar (
shape
): kolečko, čtvereček, srdíčko, listy, káry,… - Velikost (
size
) - Barvu (
color
/colour
) - Barvu výplně (
fill
) - Typ ohraničující linky (
stroke
) - Průhlednost (
alpha
)
Všechny estetiky je možné nastavit (set) nebo namapovat (map). V případě nastavení bude estetika u všech pozorování stejná. V případě namapování se hodnota estetiky bude řídit podle hodnoty přiřazené proměnné.
Pokud tedy chceme vykreslit bodový graf závislosti, ceny a váhy, potom tyto proměnné chceme namapovat na vybrané estetiky. Stejného výsledku jako u úvodního obrázku dosáhneme pomocí aes(x = carat, y = price)
. Základní volání ggplot()
tedy může vypadat následovně:
%>%
diamonds ggplot(
aes(x = carat, y = price)
)
Výsledkem je prázdná formátované plocha. Volání ggplot()
totiž pouze vytvoří prázdné “plátno”, na které je potřeba přidávat jednotlivé vrstvy s obsahem.
Nové vrstvy se typicky přidávají voláním funkcí geom_*()
. Tyto funkce nejsou nic jiného, než aliasy pro různé nastavení funkce layer()
:
layer(geom = NULL, stat = NULL, data = NULL, mapping = NULL,
position = NULL, params = list(), inherit.aes = TRUE,
check.aes = TRUE, check.param = TRUE, subset = NULL, show.legend = NA)
Funkce layer()
má mnoho parametrů. Zásadní jsou tyto:
data
…tabulka s daty, která se má použít v dané vrstvě. Pokud tento parametr není zadán, potom se použije tabulka dat specifikované ve voláníggpplot()
. Každá vrstva tak může pracovat s jinými daty. (A takéggplot()
nemusí obsahovat specifikaci dat.)geom
…typ geometrického objektu, který se má použít pro vizualizaci dat. Může se jednat o bod (point
), polygon (polygon
), úsečku (line
) a mnoho dalších.mapping
…obsahuje namapování proměnných na estetiky pomocí funkceaes()
. Pokud není parametr specifikován, potom se použije specifikace zggplot()
. Pokud je specifikace v konfliktu se specifikací vggplot()
, potom se použije specifikace zlayer()
. Pokud je specifikována estetika, která není přiřazena vggplot()
, potom se použije jako doplnění ke specifikaci zggplot()
.stat
…statistická transformace dat, která se má provést před vykreslením. Vykreslují se až transformované data.position
…umožňuje upravit pozici hrubých dat před jejich vykreslením
Funkce layer()
není zpravidla nikdy volána přímo. V podstatě vždy se používají funkce geom_*()
. Pokud například chceme přidat vrstvu s bodovým grafem, potom použijeme geom_point()
. Tato funkce je přesným ekvivalentem volání:
layer(
data = NULL,
mapping = NULL,
geom = "point",
stat = "identity",
position = "identity"
)
Pokračujme v příkladu konstrukcí bodového grafu:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point()
Vrstvu obsahující body jsme vytvořili voláním funkce geom_point()
. Tato nová vrstva je k původnímu volání ggplot()
připojena funkcí +
. Pomocí této funkce můžeme do obrázku přidávat další vrstvy – například vrstvu, která vykreslí proloženou křivku:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point() +
geom_smooth()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Jak vlastně funguje ggplot2
vevnitř? Základní volání ggplot()
vytvoří “prázdnou” datovou strukturu. Funkce +
potom R říká, že má tuto datovou strukturu modifikovat. Jakým způsobem se to má stát určuje funkce volaná za +
. Například + geom_point()
přidá k původní datové struktuře vrstvu s body, atd.
Primárním výsledkem volání ggplot()
je tedy datová struktura. Tu můžeme přiřadit do proměnné:
<- diamonds %>%
p ggplot(
aes(x = carat, y = price)
+
) geom_point()
…a dále modifikovat…
<- p + geom_smooth() p
…nebo “vytisknout”:
p
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
14.3 Mapování a nastavování estetik
V předchozím textu byl vysvětlen pojem estetika a mapování. Nyní se tomuto tématu budeme věnovat podrobněji. Každý geom
má několik estetik – dimenzí, jejichž podobu lze řídit podle určité proměnné. V případě bodového grafu (point
) jde o: x
, y
, shape
, size
, color
/colour
, fill
, stroke
a alpha
. Například geom
line
(spojnicový graf) má estetiky jiné – nerozumí stroke
, shape
a fill
. Namísto toho umí pracovat s linetype
.
Výše uvedený příklad demonstruje mechanismus mapování. S pomocí funkce aes()
je estetikám přiřazena proměnná. Některé estetiky je v závislosti na geomu nutné přiřadit. V případě geom_point()
je to například x
a y
:
%>%
diamonds ggplot(
aes(x = carat)
+
) geom_point()
Error in `geom_point()`:
! Problem while setting up geom.
ℹ Error occurred in the 1st layer.
Caused by error in `compute_geom_1()`:
! `geom_point()` requires the following missing aesthetics: y
Pomocí aes()
je možné mapovat i další estetiky:
%>%
diamonds ggplot(
aes(x = carat, y = price, size = x, color = cut)
+
) geom_point()
Pro každou namapovanou estetiku ggplot2
vytvoří vodítko, které čtenáři umožní překlad z vizualizace do dat. V případě x
a y
vykreslí osy grafu. U zbývajících estetik vykreslil legendu.
Jednu proměnnou jde mapovat i na více estetik:
%>%
diamonds ggplot(
aes(x = carat, y = price, size = cut, color = cut)
+
) geom_point()
ggplot2
takovou kombinaci zohlední i v legendě. Tento příklad ukazuje i další vlastnost ggplot2
. Pokud uživatel požaduje provedení něčeho, co Tvůrce (H. Wickham) nepovažuje za dobrý nápad, vrátí ggplot2
upozornění (via message()
).
ggplot2
poskytuje obrovské množství možností a částečně uživateli radí. Nicméně je na uživateli, aby vše nastavil tak, aby výsledné obrázky splňovaly svůj účel – jasně čtenáři předávaly určitou informaci. Obrázek použitý v příkladu například trpí zásadní vadou, která se říká “overplotting”. Pozorování se vzájemně překrývají a ve výsledný obrázek čtenáři mnoho neřekne. Nemůže vědět, zda se v některém místě vzájemně překrývá málo, nebo mnoho pozorování. Jednoduchou a často dostačující cestou jak bojovat s tímto problémem je nastavení průhlednosti (estetiky alpha
). Tuto estetiku lze samozřejmě mapovat na proměnnou, ale v tomto případě ji použijeme pro ilustraci nastavení estetiky:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point(alpha = 0.1)
Pokud chce uživatel estetiku nastavit, je potřeba ji deklarovat v příslušné geom_*()
funkci a mimo funkci aes()
. Příklad ukazuje, že nastavení průhlednosti problém s overplottingem minimálně zmírnilo.
Kontrola mapování
Ve všech příkladech jsme dosud pouze mapovali estetiku na určité proměnné. Mapování předává ggplot2
informaci typu: “barvu puntíků urči podle proměnné cut
”. Jak konkrétně to ggplot2
provede (t.j. bude mít špatný řez červenou, modrou, zelenou nebo jinou barvu) jsme zatím nijak neovlivňovali.
V této kapitole si ukážeme, jak lze s pomocí funkcí scale_*_*()
měnit škálování a prvky, které pomáhají čtenáři převést vizualizaci zpět do dat.
Pro tyto účely budeme používat jednoduchou datovou tabulku se 3 pozorováními a 4 sloupci x
, y
, z
a w
…
<- tibble(
xtable x = c(1,2,3),
y = c(1,1,1),
z = c("A","B","C"),
w = c(-1,0,1)
)
…a jednoduchý bodový graf:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point()
ggplot2
namapoval data na estetiky (pozice) a vykreslil osy, které čtenáři umožňují číst data. Obrázek je takový, jaký asi všichni očekávají. Nicméně to nemusí být to, co uživatel chce. Co může chtít změnit? Například:
- jméno škály (
name
) - popisky na ose (
labels
) - pozice “hlavních” popisků na ose (
breaks
) - pozice “pomocných” popisků/čar na ose (
minor_breaks
) - interval zobrazený na obrázku – “jak dlouhá” má být osa (
limits
) - pozici osy (
position
) - transformaci hodnot na ose – například zobrazení logaritmu pozorovaných hodnot (
trans
) - zobrazení sekundární osy (
sec.axis
)
Úplný výčet možností se samozřejmě liší podle estetiky.
Mapování se řídí pomocí funkcí scale_*_*()
, která se podobně jako geom_()
připojují k volání ggplot()
pomocí funkce +
. Jména funkcí scale_*_*()
se mohou zdá komplikovaná, ale opak je pravdou. Ve jejích jménech je totiž systém. Jméno funkce se sestává ze tří částí, spojených znakem “_“:
- Všechna jména začínají
scale
- Druhá část jména je jméno estetiky, kterou chceme kontrolovat
- Poslední část jména je jméno konkrétní škály (to je potřeba si občas pamatovat nebo najít)
Pokud bychom například chtěli změnit jméno osy x (estetika x
) na našem obrázku, potom budeme chtít použít funkci:
scale_x_continuous()
První část jména je daná, druhá je jasná a třetí odkazuje na povahu proměnná x
v tabulce xtable
, která je spojitá. (Takto pěkně ale jména škály nefungují vždy. Občas je třeba hledat.)
Pokud známe jméno, je z poloviny vyhráno:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point() +
scale_x_continuous(name = "Tohle je osa x")
Jak bylo zmíněno výše, pomocí škál je možné nastavit velké množství parametrů:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point() +
scale_x_continuous(
name = "Tohle je osa x",
breaks = c(1,2,3),
labels = c("málo","víc","málo"),
limits = c(0,4),
position = "top"
)
Poněkud specifické je nastavení transformace. V praxi se často používá logaritmická transformace:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point() +
scale_x_continuous(trans = "log10")
Řada transformací je pro uživatele připravená (viz ?scale_x_continuous()
). Uživatel má navíc možnost vytvořit vlastní transformace. Pro nejčastější transformace má ggplot2
předpřipravené škály – například log10
nebo reverse
:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point() +
scale_x_log10()
V předchozích příkladech jsme volali funkci scale_x_*()
pro nastavení mapování estetiky x
. Nicméně ani jednou jsme neupravovali estetiku y
a taky to fungovalo. Jak je to možné? Pokud není funkce scale_*_*()
explicitně volána uživatelem, ggplot2
odhadne, která funkce je nejvhodnější a zavolá ji interně sám. Volání:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point()
Je tak ve výsledku ekvivalentní k:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_point() +
scale_x_continuous() +
scale_y_continuous()
Pokud se změní povaha podkladových dat, potom se i ggplot2
rozhodne volat jinou funkci scale_*_*()
. V případě volání:
%>%
xtable ggplot(
aes(x = factor(x), y = y)
+
) geom_point()
kde proměnná x
je konvertována na faktor. Vrátí ggplot2
obrázek ekvivalentní volání:
%>%
xtable ggplot(
aes(x = factor(x), y = y)
+
) geom_point() +
scale_x_discrete() +
scale_y_continuous()
ggplot2
v tomto případě volá škálu vytvořenou pro mapování diskrétních proměnných.
Mapování barev (estetiky colour
a fill
)
Pomocí funkcí scale_*_*()
lze měnit mapování všech estetik. Mezi nejčastější upravované škály patří bezesporu barvy, které se mapují v estetikách color
/colour
a fill
.
Poznámka: ggplot2
neumí a podle jeho tvůrců nikdy nebude umět vybarvovat texturou (“šrafováním”).
Pro ilustraci těchto estetik použijeme následující podobu bodového grafu:
%>%
xtable ggplot(
aes(x = x, y = y, color = z, fill = w)
+
) geom_point(
shape = 21,
stroke = 3,
size = 10
+
) scale_x_continuous(
limits = c(0.5,3.5)
+
) theme(
legend.direction = "horizontal"
-> p
)
p
Výsledná podoba obrázku je ovlivněna funkcí theme()
. Její fungování je popsáno níže. Celá struktura je přiřazená do proměnné p
. S tou budeme nadále pracovat.
Tento graf mapuje proměnné z
a w
na estetiky color
a fill
. Ostatní estetiky jsou nastaveny a jsou pro všechny body v grafu shodné.
Proměnná z
a w
se liší ve své povaze. w
je spojitá proměnná a z
je kategoriální. Práce s barvami by se obecně měla řídit podle povahy zobrazovaných dat:
- Spojité proměnné se typicky zobrazují barevným přechodem – jak vidíte na obrázku, základní volbou
ggplot2
je přechod z tmavě do světle modré. - Kategoriální proměnné, jejichž hodnoty jsou “na stejné úrovni”. Příkladem takové proměnné mohou být například značky automobilů. V takovém případě je zvolit barvy, které jsou pro vnímání člověka “stejně daleko” od sebe. Příkladem takového výběru barev může být proměnná
z
v ukázkovém obrázku. - Posledním typem jsou kategoriální proměnné, které vyjadřují sekvenci (např.: malá, střední, velká). V takovém případě je vhodné volit barevný přechod, který je také rozdělen do kroků, které musí být tak velké, aby byly pro člověka jasně odlišitelné.
Při volbě barev je také užitečné myslet na barvoslepé a na tisk na černobílé tiskárně. Jiné barvy jsou také vhodné pro plochy a jiné pro body. Ve výsledku je výběr barev nesmírně složitý. Najít sekvenci barev pro 10 úrovní je těžké, pro 20 úrovní je to nemožné. Moudrým postupem je proto používat uměřený počet barev (úrovní) a v jejich volbě vycházet z doporučení odborníků.
Spojité proměnné
Typickým postupem zobrazování spojité proměnné v barvě je použití barevného přechodu (gradientu) mezi dvěma nebo více barvami. ggplot2
nabízí hned 4 škály založené na gradientech:
scale_*_gradient()
(identické se scale_*_continuous()
) je základní volbou, po které u spojitých proměnných ggplot2
sáhne, pokud uživatel neporoučí jinak. Vykresluje přechod mezi dvěma barvami, které jsou nastavené parametry low
a high
:
+ scale_fill_gradient(low = "hotpink", high = "#56B1F7") p
V příkladu je nastaven přechod z tmavě poníkové do modré. Barva je vždy definována jako řetězec (nebo funkcí, která řetězec nebo jejich vektor vytvoří) – a to buď svým jménem ("hotpink"
) nebo RGB kódem ("#56B1F7"
).
scale_*_gradient2()
je derivátem scale_*_gradient()
umožňuje vytvořit přechod přes tři barvy (low
, mid
, high
) a specifikovat pozici středního bodu parametrem midpoint
. (Parametr midpoint
je defaultně nastaven na hodnotu 0.)
+ scale_fill_gradient2(midpoint = 0.5) p
scale_*_gradientn()
umožňuje použít přechod přes tolik barev, kolik si jen srdce uživatele přeje. Z tohoto důvodu neobsahuje žádné základní nastavení:
+ scale_fill_gradientn() p
Error in scale_fill_gradientn(): argument "colors" is missing, with no default
Uživatel musí prostřednictvím parametru colours
zadat vektor barev, které se mají použít. Domnívám se, že pro uživatele – neodborníka – se jedná o nemožný úkol. Je však možné použít předpřipravené palety, které jsou součástí mnoha balíků. Následující příklad využívá paletu inferno
z balíku viridis:
+ scale_fill_gradientn(colours = viridis::inferno(3)) p
Využití scale_*_gradientn()
je vhodné spíše v plochách. Nicméně i tam je dobré spolehnout na rady odborníků. Příklad “plošného” obrázku:
Na radách odborníků je zcela postaven scale_*_distiller()
. Pracuje s ručně vybranými diskrétními paletami, které jsou dostupné na http://colorbrewer2.org/. scale_*_distiller()
tyto diskrétní palety adaptuje i pro spojité proměnné. Rozumný způsob práce je na webu zvolit vhodnou barevnou škálu a její jméno specifikovat v parametru palette
. Ten um pracovat s číslem palety i s jejím jménem:
+ scale_fill_distiller(palette = "YlGnBu") p
Parametrem direction
, který nabývá hodnot 1
nebo -1
, lze pořadí barev obrátit:
+ scale_fill_distiller(palette = "YlGnBu", direction = 1) p
Používání http://colorbrewer2.org/ lze doporučit v maximální rozumné míře.
Diskrétní proměnné
Pro diskrétní proměnné je výchozí volbou scale_*_hue()
. Tato škála vybírá od sebe stejně vzdálené barvy na “HCL wheel” (viz např. Wikipedie). Teoreticky je tak schopna vytvořit barevné schéma pro prakticky neomezený počet kategorií. V praxi od sebe budou barvy při vyšším počtu kategorií nerozpoznatelné.
+ scale_color_hue() p
scale_*_hue()
umožňuje vybrat výchozí bod na HCL wheel a parametrem direction
směr, jakým se na něm výběr pohybuje:
+ scale_color_hue(direction = -1) p
Zejména při finalizaci grafických výstupů je vhodné poohlédnout se po předpřipravených paletách. V samotném ggplot2
jde o scale_*_brewer()
– bratra scale_*_distiller()
pro diskrétní veličiny.
+ scale_color_brewer(palette = "Set1") p
Speciální škálou je scale_*_grey()
. Ta, jak název napovídá, mapuje do odstínů šedé:
+ scale_color_grey() p
Speciálními případy škál jsou scale_*_manual()
a scale_*_identity()
. scale_*_manual()
umožňuje uživateli vytvořit si vlastní škálu. U barev bude tato možnost využívána asi jen zřídka. Za určitých okolností se však může hodit u jiných estetik – například u shape
.
scale_*_identity()
dokáže vzít proměnnou z datové tabulky a použít ji “tak jak je” pro vykreslení barev, tvarů a podobně.
Co jsme zamlčeli…
Všechny krásné škálovací funkce scale_*_*()
v posledku využívají služeb konstruktorových funkcí discrete_scale()
a continuous_scale()
. S těmito funkcemi komunikujete ze scale_*_*()
přes argument ...
. Pokud tedy budete chtít provádět něco složitějšího, tak se vyplatí projít si nápovědu právě k těmto konstruktorovým funkcím.
14.4 Grupování
U některých funkcí geom_*()
je mezi estetikami uvedena jedna s názvem group
. Lehko si můžete ověřit, že se nijak nepřekládá do vizuální podoby. Její funkce je totiž jiná. Sděluje ggplot2
, že určitá skupina pozorování “patří k sobě” – tvoří jednu skupinu.
Praktické využití lze ilustrovat na proložení křivky v tabulce diamonds
:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point() +
geom_smooth()
V tomto případě se proložila křivka přes všechny pozorování. Můžeme se ale chtít podívat, jestli je závislost stejná pro různé druhy kamenů – například pro skupiny definované kvalitou řezu (cut
). V tomto případě použijeme estetiku group
:
%>%
diamonds ggplot(
aes(x = carat, y = price, group = cut)
+
) geom_point() +
geom_smooth()
V tomto případě geom_smooth()
, který estetice group
rozumí, pracuje s každou skupinou vymezenou hodnotou kategoriální proměnné cut
zvlášť. Fakticky každou skupinu proloží její vlastní křivkou.
V praxi je často užitečné pro zpřehlednění využít možnosti mapování jedné proměnné na více estetik:
%>%
diamonds ggplot(
aes(x = carat, y = price, group = cut, color = cut)
+
) geom_point() +
geom_smooth()
Všiměte si že v tomto případě dostanete stejný výsledek i při namapování kategorické proměnné na estetiku color
:
%>%
diamonds ggplot(
aes(x = carat, y = price, color = cut)
+
) geom_point() +
geom_smooth()
14.5 Statistické transformace
ggplot2
umožňuje nejen vykreslovat samotná data, ale i jejich interně vytvořené statistické transformace. Příkladem může být například proložení křivkou (geom_smooth()
). Jejich tvorbu mají na starosti funkce stat_*()
. Vztah mezi funkcemi geom_*()
a stat_*()
není úplně jasný. Každá transformace (stat
) má svůj výchozí (přednastavený) geometrický tvar (geom
) a každý geom
má svůj výchozí stat
. V některých případech tak může být jedno, jestli uživatel volá geom
nebo ekvivalentní stat
, Viz například použití stat_smooth()
:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point() +
stat_smooth()
Tato nejednoznačnost vymezení toho, co vlastně dělá stat_*()
a co geom_*()
je velmi otravnou nedokonalostí v návrhu ggplot2
(a v ggvis
je již vyřešena). Pokud není nevyhnutelné volat přímo stat_*()
nutné, je lepší vždy používat funkce geom_*()
.
Pro přímé volání stat_*()
existovaly v podstatě dva důvody:
- potřeba specifických nastavení statistických transformací
- potřeba použití jiného než standardního geomu pro vykreslení statistické transformace
Pohledem do nápovědy zjistíte, že stat_smooth()
umožňuje mnohem více nastavení vyhlazování:
geom_smooth(mapping = NULL, data = NULL, stat = "smooth",
position = "identity", ..., method = "auto", formula = y ~ x,
se = TRUE, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
stat_smooth(mapping = NULL, data = NULL, geom = "smooth",
position = "identity", ..., method = "auto", formula = y ~ x,
se = TRUE, n = 80, span = 0.75, fullrange = FALSE, level = 0.95,
method.args = list(), na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE)
V novějších verzích ggplot2
se však již dají zpravidla předávat hodnoty parametrů pro stat_*()
prostřednictvím ...
v geom_*()
. Tato možnost prakticky eliminuje potřebu volat stat_*()
funkce přímo kvůli nastavení statistických transformací.
Druhý důvod pro přímé použití stat_*()
funkce však stále trvá. V některých případech můžete potřebovat použít pro vyreslení transformace netypický geom. Příkladem může být například zobrazování jádrové hustoty rozdělení dvou proměnných nikoliv pomocí vrstevnic (standardní zobrazení), ale pomocí barevných polygonů. (Výsledkem by byly například barevné plochy s barvou sytější tam, kde je odhadnuta vyšší hustota.) Tyto úlohy jsou však bezpochyby z kapitoly pro pokročilé.
14.6 Pozicování
V ggplotu2
existuje hned několik způsobů, jak je možné ovlivnit pozici vykresleného geomu (tj. geometrického objektu reprezentujícího data) na stránce. První možností bylo použití škál – například jejich logaritmická transformace.
ggplot2
však v tomto ohledu nabízí mnohem více možností. Místem kde začít hledat je parametr position
ve funkci layer()
. Smyslem jeho použití je předejít překryvu pozorování ve výsledném obrázku. Parametr může nabývat následujících hodnot:
identity
…je základní volba pro většinu funkcígeom_*()
. Prakticky znamená, že z hlediska modifikace pozice geomu předává instrukci “nedělej nic a kresli na skutečnou pozici”.jitter
…ke skutečné pozici přidává náhodnou chybudodge
…geomy skládá vedle sebestack
…staví komínky – skládá geomy nad sebenudge
…u překrývajících se geomů přidává ke skutečné pozici definovanou vzdálenost na osex
ay
jitterdodge
…kombinujejitter
adodge
V následujícím textu jsou blíže ukázány nejčastěji používané varianty změny pozic: jitter
, dodge
, stack
a navíc poněkud specifická varianta fill
. Varianty nudge
a jitterdodge
jsou specifické pro určité méně geomy a jejich přímé volání je v podstatě výjimečné.
jitter
position = "jitter"
je populární volba zejména u bodových grafů, ve kterých je velký problém s overplottingem. Toto použití je tak typické, že existuje dokonce speciální geom_*()
funkce: geom_jitter()
. Ta je ekvivalentní k geom_point(position = "jitter")
.
geom_jitter()
přidává ke skutečné pozici pozorování náhodnou složku, která je generovaná z uniformního rozdělení:
%>%
xtable ggplot(
aes(x = x, y = y)
+
) geom_jitter(color = "red") +
geom_point()
V obrátku vidíte skutečnou pozici pozorování vykreslenou pomocí geom_point()
a červené tečky ukazující posunuté hodnoty vykreslené pomocí geom_jitter()
. Protože je chyba náhodná, bude obrázek při každém vykreslení vypadat jinak.
Pomocí parametrů width
a height
je možná nastavit šířku a výšku obdélníku, ve kterém se posunutá pozorování budou nacházet. Protože se náhodná složka generuje z uniformního rozdělení, je každý posun stejně pravděpodobný. Skutečná pozorovaná hodnota je pak právě ve středu obdélníku. To ilustruje následující obrázek:
<- tibble(
xtable2 x = rep(1:2,1000),
y = rep(1:2,1000)
)
%>%
xtable2 ggplot(
aes(x = x, y = y)
+
) geom_jitter(
alpha = 0.05,
color = "red"
+
) geom_point()
Posunutá pozorování (červené tečky) tvoří mračno rovnoměrně vyplňující celý obdélník za pozorovanou hodnotou (černá tečka), která je přesně v jeho středu.
stack
Typické použití této změny této modifikace pozice je u sloupcových grafů. Ty se v ggplot2
tvoří pomocí geom_bar()
a jejich základní nestavení je, že mají být použity k vykreslení rozdělení diskrétní proměnné.
Pro použití geom_bar()
vytvoříme novou tabulku occupation
:
<- tibble(
occupation sex = rep(c("male","female"),100),
occupation = sample(c("A","B","C","D"), 200, replace = TRUE, prob = abs(rnorm(4))),
age = sample(c("old","young"), 200, replace = TRUE)
)
Tabulka obsahuje povolání (A, B, C nebo D), pohlaví a věkovou kohortu 200 lidí. Zajímá nás rozdělení povolání ve vzorku:
%>%
occupation ggplot(
aes(x = occupation)
+
) geom_bar()
geom_bar()
v základním nastavení, tedy se stat = "count"
, provádí statistickou transformaci dat. Zjistí absolutní četnost jednotlivých variant proměnné v estetice x
a tyto četnosti následně vykreslí formou sloupcového grafu.
Obrázek momentálně nijak nezohledňuje pohlaví, ale to se dá změnit. Lze ho namapovat například (a typicky) na estetiku fill
:
%>%
occupation ggplot(
aes(x = occupation, fill = sex)
+
) geom_bar()
Ve výsledku dodá statistická transformace provedená stat_count()
pro každé povolání dvě hodnoty: počet mužů a počet žen. Ty by se v logicky měly kreslit přes sebe – patří v obrázku na jedno místo. Očekávali bychom tedy tento obrázek:
Takový obrázek však není příliš užitečný, protože poskytuje spolehlivou informaci pouze o maximálních hodnotách. Proto již v základním nastavení geom_bar()
používá position = "stack
, která počet žen a mužů postaví nad sebe – do jednoho komínku.
Poznámka: “Špatný” obrázek můžete vykreslit s geom_bar(position = "identity)
.
dodge
Dalším typickým řešením u sloupcových grafů je vykreslení hodnot vedle sebe. Tento způsob čtenáři lépe předává informaci o počtu mužů a žen napříč povoláními. Horizontální posunutí sloupců zajišťuje position = "dodge"
:
%>%
occupation ggplot(
aes(x = occupation, fill = sex)
+
) geom_bar(position = "dodge")
fill
Poněkud specifickou úpravou pozice je fill
, která upravuje nejen pozici, ale vlastně provádí i další statistickou transformaci. Nevrací totiž absolutní, ale relativní četnost:
%>%
occupation ggplot(
aes(x = occupation, fill = sex)
+
) geom_bar(position = "fill")
Facetování: na půli cesty mezi grupováním a pozicováním
Výše popsané pozicování mělo pozici geomů vlastně v rámci jednoho obrázku. Facetování k problému přistupuje jinak – rozlamuje původně jeden obrázek na více dílčích obrázků (facet). ggplot2
umožňuje vytvářet buď matice dílčích obrázků pomocí facet_grid()
nebo prosté rozlomení do více obrázků pomocí facet_wrap()
.
Nejjednodušší příklad je použití facet_wrap()
. Řekněme, že budeme chtít rozdělit obrázek s rozdělením povolání podle pohlaví:
%>%
occupation ggplot(
aes(x = occupation)
+
) geom_bar() -> p
+ facet_wrap("sex") p
facet_wrap()
rozdělil celkový obrázek na dva dílčí podle hodnot proměnné sex
. Ta je v příkladu určena svým jménem (character). Další možností je její určení pomocí jednostranné rovnice (formula, blíže viz přednáška o ekonometrii v R):
+ facet_wrap(~ sex) p
Vytvářet dílčí obrázky lze i podle kombinace více proměnných – můžeme chtít například samostatné dílčí obrázky pro kombinaci pohlaví a věku. V tomto případě se pro specifikaci proměnných použije vektor jejich jmen:
+ facet_wrap(c("sex","age")) p
Nebo alternativně:
+ facet_wrap(~ sex + age) p
Použít pro tyto aplikace facet_wrap()
je možné, ale často je vhodnější sáhnout po facet_grid()
, který je přímo navržen pro rozlomení obrázku podle kombinace více proměnných. facet_grid()
vyžaduje specifikaci facetování pomocí objektu třídy formula
:
+ facet_grid(sex ~ age) p
Na levé straně rovnice (na levé straně ~
) je proměnná (nebo jejich kombinace spojená +
), která má tvořit řádky a na levé straně je proměnná nebo jejich kombinace, která má tvořit sloupce.
Funkce vytvářející facetování mají i další užitečné parametry. Pro facet_wrap()
jsou specifické parametry ncol
a nrow
, které určují formát výstupní tabulky dílčích obrázků – kolik má mít sloupců a kolik řádků. Stačí přitom zadat jen jeden z těchto parametrů:
+ facet_wrap(c("sex","age"), nrow = 1) p
Užitečný, ale pro správné čtení dat velmi ošemetný, parametr je scales
. Ten umožňuje uvolnit škály v jednotlivých dílčích obrázcích. Základní hodnotou je fixed
. V tomto případě jsou měřítka na všech dílčích obrázcích stejná. Varianty free
, free_y
a free_x
umožňují uvolnit měřítka na všech osách nebo jen na ose x
popřípadě y
:
+ facet_wrap(c("sex","age"), nrow = 1, scales = "free_y") p
Příklad ilustruje, že uvolnění os může být pro čtenáře velmi zavádějící. Stejná výška sloupce totiž v různých dílčích obrázcích znamená jinou pozorovanou hodnotu.
14.7 Souřadnicové systémy
ggplot2 podporuje řadu souřadnicových systémů. Základní volbou je lineární coord_cartesian()
, ale podporovány jsou i nelineární souřadnicové systémy, které se ovšem používají pouze ve velmi specifických případech jako je například vykreslování map (coord_map()
). Dalším příkladem nelineárního systému je coord_polar()
.
Základní volbou aplikovanou v téměř všech voláních ggplot2 je bezpochyby lineární systém, ve kterém je pozice prvků určena jejich souřadnicemi na ose x
a y
. Základní funkcí je coord_cartesian()
, ale ggplot2 obsahuje i její různé mutace a transformace:
coord_fixed()
udržuje konstantní zadaný poměr stran.coord_equal()
je zkratka procoord_fixed()
s poměrem stran 1.coord_flip()
prohazuje osyx
ay
.coord_trans()
umožňuje provést transformaci os.coord_sf()
je speciální funkce pro práci s mapami.
Fungování coord_flip()
je možné ilustrovat pomocí jednoduchého bodového grafu. V definici mapování je váha (carat
) namapována na osu x
a cena na osu y
:
%>%
diamonds ggplot(
aes(x = carat, y = price)
+
) geom_point() -> p
Použití coord_flip()
prohodí osy x
a y
– výsledek tedy odpovídá použití aes(y = carat, x = price)
:
+ coord_flip() p
coord_flip()
je užitečné u některých specifických geomů – například u boxplotů, která zobrazují základní charakteristiky rozdělení spojité proměnné.
coord_trans()
umožňuje transformovat osy podobným způsobem jako scale_*_*()
funkce. Stejně jako ostatní funkce odvozené od coord_cartesian()
umožňuje nastavit interval zobrazený na osách. Podobnou možnost mají opět i scale_*_*()
funkce.
Nabízí se tedy otázka, na co jsou vlastně funkce coord_*()
užitečné. Pro demonstraci odpovědi nasimulujeme velmi specifickou tabulku:
<- tibble(
cltable x = c(
rnorm(300, sd = 2),
rnorm(40, sd = 0.5) + 10
),y = c(
rnorm(300, sd = 1),
rnorm(40, sd = 0.5) + 5
)
)
%>%
cltable ggplot(
aes(x = x, y = y)
+
) geom_point() +
geom_smooth(method = "lm", se = FALSE) -> p
p
Simulovaná data obsahují dva shluky pozorování. Pokud je proložíme regresní přímkou (tj. vykreslíme statistickou transformaci dat) získáme (falešné) zdání existence rostoucího vztahu mezi veličinou na ose x
a na ose y
.
Řekněme, že z nějakých důvodů budeme chtít omezit zobrazené hodnoty na ose x
na interval \([-5,5]\). To lez provést buď pomocí funkcí scale_x_*()
nebo coord_*()
:
+ scale_x_continuous(limits = c(-5,5)) p
+ coord_cartesian(xlim = c(-5,5)) p
Rozdíl je zjevný. Při použití scale_x_*()
je regresní přímka prakticky vodorovná. Naopak při použití coord_*()
je stále rostoucí. Rozdíl je v tom, kdy je aplikované statistická transformace (v tomto případě vyhlazení). Při použití scale_*_*()
funkce je nejprve omezen rozsah dat a následně je aplikována transformace. Výsledek tak může být zavádějící. Naproti tomu při použití coord_*()
je nejprve aplikována transformace a následně je omezen rozsah zobrazených dat. Transformace tak reflektuje i ta pozorování, která nejsou zobrazená.
coord_*()
tak nabízí možnost skutečně “zazoomovat” část obrázku, aniž by to ovlivnilo obrázek samotný.
14.8 Vzhled obrázků
Vzhled obrázků, nebo přesněji prvky, které nemají vztah k datům se v ggplot2 ovládají pomocí funkce theme()
. Ta umožňuje změnit vzhled obrázků k nepoznání: můžete mít obrázek, který vypadá, jako by byl vytvořen v Excelu, Stata, nebo jako by vyšel v The Economist.
Funkce theme()
má obrovské množství parametrů a nebylo by praktické je nastavovat vždy u každého obrázku. Proto v ggplot2 existuje řada předpřipravených kompletních témat (vzhledů). Například theme_bw()
, theme_classic()
nebo theme_void()
. Základní vzhled obrázků potom odpovídá theme_gray()
. Nápověda ggplot2 obsahuje hrubá doporučení, kdy je vhodné použít které téma.
Ukázky předpřipravených témat
Pro ukázky použijeme již známý sloupcový graf:
%>%
occupation ggplot(
aes(x = occupation, fill = sex)
+
) geom_bar() -> p
Populární téma je theme_bw()
, které ggplot2 doporučuje například pro prezentace:
+ theme_bw() p
Klasicky vypadající téma s minimem čar je theme_classic()
:
+ theme_classic() p
Například pro kreslení map oceníte velmi speciální téma theme_void()
, které zahodí všechny čáry:
+ theme_void() p
Sbírku předpřipravených témat obsahuje například balík ggthemes. Kromě různých theme_*()
obsahuje balík i řadu scale_*_*()
a několik geom_*()
funkcí.
Například minimalistické téma vytvořené podle Tufteho doporučení obsahuje ggthemes::theme_tufte()
:
+ ggthemes::theme_tufte() p
Modifikace témat
ggplot2 umožňuje vytvářet vlastní předpřipravená témata, ale většinou bohatě postačuje použít předpřipravené a upravit ho, to lze zařídit následujícím způsobem:
+
p theme_classic() +
theme(
panel.grid.major = element_line(linewidth = 0.2)
)
U theme()
funguje vrstvení stejně, jako u všeho ostatního. Volání jednotlivých funkcí modifikuje podkladovou datovou strukturu. Zavolání funkce theme_classic()
tedy nastaví všechny parametry tak, jak to odpovídá theme_classic
– to znamená, žádné linky na pozadí. Následné volání theme()
přepíše to, co před tím nastavilo volání theme_classic()
a linky přidá. Žádný další parametr nebyl v theme()
zadán a proto se nic jiného nezmění.
Pokud by funkce byly volány v opačném pořadí, potom by byl výsledek následující:
+
p theme(
panel.grid.major = element_line(linewidth = 0.2)
+
) theme_classic()
Výsledek by přesně odpovídal theme_classic
. theme_classic()
totiž bez milosti přepíše veškeré formátování vytvořené v předchozích voláních theme()
nebo theme_*()
.
Samotné volání theme()
ve výše uvedeném příkladu vypadá skutečně velmi krypticky, ale není to tak, protože, podobně jako (skoro) ve všem v ggplot2
, za ním stojí promyšlený systém.
Funkce theme()
má opravdu mnoho parametrů (viz ?theme
), jejich jména odpovídají jménům jednotlivých grafických prvků. Jména jsou naštěstí často velmi výstižná, intuitivní, a navíc je RStudio zvládá (u novějších verzí ggplot2) našeptávat.
Formátování grafických prvků se ve většině případů provádí pomocí volání jedné z následujících funkcí:
element_text(family = NULL, face = NULL, colour = NULL, size = NULL,
hjust = NULL, vjust = NULL, angle = NULL, lineheight = NULL,
color = NULL, margin = NULL, debug = NULL)
element_line(colour = NULL, linewidth = NULL, linetype = NULL,
lineend = NULL, color = NULL)
element_rect(fill = NULL, colour = NULL, size = NULL, linetype = NULL,
color = NULL)
element_blank()
Tu správnou lze uhodnout. Pokud chcete upravit formátování prvku, který je ve své podstatě text, potom chcete použít element_text()
. Příkladem může být modifikace titulku (plot.title
) obrázku:
+
p labs(
title = "Just Another Bar Plot"
+
) theme(
plot.title = element_text(colour = "red")
)
Pokud je prvek svým charakterem čára, potom se jeho formátování nastavuje pomocí element_line()
(viz modifikace čar na pozadí výše). Prvky, které jsou ve své podstatě čtyřúhelník se potom modifikují pomocí element_rect()
. Příkladem může být změna barvy pozadí grafu na poníkovou:
+
p theme(
panel.background = element_rect(fill = "pink")
)
Speciální funkce je potom element_blank()
, která způsobí, že se daný prvek z obrázku kompletně vypustí:
+
p theme(
legend.title = element_blank()
)
Například toto volání vypustilo jméno legendy a to zcela. Špinavé triky typu scale_fill_discrete(name = "")
sice způsobí, že jméno “nebude vidět”. Respektive se vykreslí prázdný řetězec. Místo pro titulek však bude alokováno a bude tak rozhazovat uspořádání obrázku. element_blank()
je správná cesta.
Některé parametry v theme()
se nastavují pomocí speciálních funkcí – typicky jde o nastavení velikostí pomocí funkce unit()
. Další parametry se nastavují prostým zadáním hodnoty. Příkladem může být pozice a orientace legendy:
+
p theme(
legend.position = "bottom",
legend.direction = "horizontal"
)
14.9 Vizualizaci prostorových map
V R existuje řada nástrojů, které umožňují analýzu a vizualizaci různých typů prostorových dat. Dva základní balíky jsou sp a hlavně podstatně modernější sf. Balík sf umožňuje načítání prostorových dat z mnoha různých zdrojů včetně OSM a shapefilů. Uvažujme typický příklad, kdy chceme nkreslit tématickou mapu okresů ČR.
Nejdříve potřebujeme pomocí funkce st_read()
načíst datový podklad. V tomto případě budeme načítat data ze shapfilu, který může obsahovat více vrstev – načíst se dá však vždy jen jedna. Seznam vrstev získáme pomocí funkce st_layers()
:
library(sf)
Linking to GEOS 3.12.0, GDAL 3.7.1, PROJ 9.2.1; sf_use_s2() is TRUE
st_layers("data/ggplot2/SPH_SHP_JTSK/JTSK/")
Driver: ESRI Shapefile
Available layers:
layer_name geometry_type features fields crs_name
1 SPH_ORP Polygon 206 4 <NA>
2 SPH_ZSJ Polygon 22654 5 <NA>
3 SPH_OBEC Polygon 6258 6 <NA>
4 SPH_OBLAST Polygon 8 3 <NA>
5 SPH_OPU Polygon 393 5 <NA>
6 SPH_KRAJ Polygon 14 3 <NA>
7 SPH_KU Polygon 13075 7 <NA>
8 SPH_STAT Polygon 1 3 <NA>
9 SPH_OKRES Polygon 77 4 <NA>
10 SPH_SO Polygon 22 5 <NA>
11 SPH_MC Polygon 142 6 <NA>
Jméno vrstvy použijeme pro načtení správné “části” shapefilu:
# První parametr: shapefile
# Druhý parametr: jméno vrstvy
<- st_read("data/ggplot2/SPH_SHP_JTSK/JTSK/","SPH_OKRES") okresy
Reading layer `SPH_OKRES' from data source
`/home/qasar/vyuka/aved/qbook/data/ggplot2/SPH_SHP_JTSK/JTSK'
using driver `ESRI Shapefile'
Simple feature collection with 77 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -904585.3 ymin: -1227296 xmax: -431724.3 ymax: -935237
CRS: NA
okresy
Simple feature collection with 77 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -904585.3 ymin: -1227296 xmax: -431724.3 ymax: -935237
CRS: NA
First 10 features:
ID KOD_NUTS3 KOD_LAU1 NAZEV_LAU1 geometry
1 3100 CZ010 CZ0100 Hlavní město Praha MULTIPOLYGON (((-736538 -10...
2 3201 CZ020 CZ0201 Benešov MULTIPOLYGON (((-746500.6 -...
3 3202 CZ020 CZ0202 Beroun MULTIPOLYGON (((-791181 -10...
4 3203 CZ020 CZ0203 Kladno MULTIPOLYGON (((-776276.7 -...
5 3204 CZ020 CZ0204 Kolín MULTIPOLYGON (((-671965.2 -...
6 3205 CZ020 CZ0205 Kutná Hora MULTIPOLYGON (((-675684 -10...
7 3206 CZ020 CZ0206 Mělník MULTIPOLYGON (((-747304.2 -...
8 3207 CZ020 CZ0207 Mladá Boleslav MULTIPOLYGON (((-697337.2 -...
9 3208 CZ020 CZ0208 Nymburk MULTIPOLYGON (((-688299.8 -...
10 3209 CZ020 CZ0209 Praha-východ MULTIPOLYGON (((-722769.3 -...
Vypsání objektu na obrazovku ukazuje, co vlastně sf
objekty jsou:
%>% class() okresy
[1] "sf" "data.frame"
Jedná se o tabulky, které mají speciání sloupec geometry
(občas se jmenuje SHAPE
) a atribut, který v podstatě dává prostorovým datům potřebný kontext – například deklaruje použité CRS. V tomto případě je však objekt poškozen a CRS není deklarováno. To se dá opravit:
<- okresy %>%
okresy # Deklaruje správné CRS (S-JTSK / Krovak East North - SJTSK) -- mění se pouze atribut, ale ne data
st_set_crs(5514) %>%
# Transformuje data (do WGS84)
st_transform(4326)
okresy
Simple feature collection with 77 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 12.09057 ymin: 48.55181 xmax: 18.85925 ymax: 51.0557
Geodetic CRS: WGS 84
First 10 features:
ID KOD_NUTS3 KOD_LAU1 NAZEV_LAU1 geometry
1 3100 CZ010 CZ0100 Hlavní město Praha MULTIPOLYGON (((14.52826 49...
2 3201 CZ020 CZ0201 Benešov MULTIPOLYGON (((14.42653 49...
3 3202 CZ020 CZ0202 Beroun MULTIPOLYGON (((13.797 49.8...
4 3203 CZ020 CZ0203 Kladno MULTIPOLYGON (((13.92115 50...
5 3204 CZ020 CZ0204 Kolín MULTIPOLYGON (((15.41584 50...
6 3205 CZ020 CZ0205 Kutná Hora MULTIPOLYGON (((15.37964 50...
7 3206 CZ020 CZ0206 Mělník MULTIPOLYGON (((14.33082 50...
8 3207 CZ020 CZ0207 Mladá Boleslav MULTIPOLYGON (((14.95977 50...
9 3208 CZ020 CZ0208 Nymburk MULTIPOLYGON (((15.18524 50...
10 3209 CZ020 CZ0209 Praha-východ MULTIPOLYGON (((14.74597 49...
Objekt sf
je ale pořád tabulka a také s ním tak můžeme pracovat. Například k němu přidat data, která chceme vykreslit do tématické mapy.
# CZSO data
library(czso)
<- czso_get_codelist("cis109")
codes
<- czso_get_table("170242") %>%
migration group_by(uzemiz_kod) %>%
summarise(
hodnota = sum(hodnota),
.groups = "drop"
%>%
) left_join(
by = c("uzemiz_kod" = "kodrso")
.,codes, )
<- migration %>%
migration left_join(okresy,., by = c("KOD_LAU1" = "chodnota"))
migration
Simple feature collection with 77 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 12.09057 ymin: 48.55181 xmax: 18.85925 ymax: 51.0557
Geodetic CRS: WGS 84
First 10 features:
ID KOD_NUTS3 KOD_LAU1 NAZEV_LAU1 uzemiz_kod hodnota kodjaz
1 3100 CZ010 CZ0100 Hlavní město Praha <NA> NA <NA>
2 3201 CZ020 CZ0201 Benešov 40169 7774 CS
3 3202 CZ020 CZ0202 Beroun 40177 7986 CS
4 3203 CZ020 CZ0203 Kladno 40185 16367 CS
5 3204 CZ020 CZ0204 Kolín 40193 8265 CS
6 3205 CZ020 CZ0205 Kutná Hora 40207 5423 CS
7 3206 CZ020 CZ0206 Mělník 40215 11082 CS
8 3207 CZ020 CZ0207 Mladá Boleslav 40223 4278 CS
9 3208 CZ020 CZ0208 Nymburk 40231 10023 CS
10 3209 CZ020 CZ0209 Praha-východ 40240 26579 CS
akrcis kodcis zkrtext text admplod admnepo
1 <NA> <NA> <NA> <NA> <NA> <NA>
2 OKRES_LAU 109 Benešov Benešov 2008-01-01 9999-09-09
3 OKRES_LAU 109 Beroun Beroun 2008-01-01 9999-09-09
4 OKRES_LAU 109 Kladno Kladno 2008-01-01 9999-09-09
5 OKRES_LAU 109 Kolín Kolín 2008-01-01 9999-09-09
6 OKRES_LAU 109 Kutná Hora Kutná Hora 2008-01-01 9999-09-09
7 OKRES_LAU 109 Mělník Mělník 2008-01-01 9999-09-09
8 OKRES_LAU 109 Mladá Boleslav Mladá Boleslav 2008-01-01 9999-09-09
9 OKRES_LAU 109 Nymburk Nymburk 2008-01-01 9999-09-09
10 OKRES_LAU 109 Praha-východ Praha-východ 2008-01-01 9999-09-09
geometry
1 MULTIPOLYGON (((14.52826 49...
2 MULTIPOLYGON (((14.42653 49...
3 MULTIPOLYGON (((13.797 49.8...
4 MULTIPOLYGON (((13.92115 50...
5 MULTIPOLYGON (((15.41584 50...
6 MULTIPOLYGON (((15.37964 50...
7 MULTIPOLYGON (((14.33082 50...
8 MULTIPOLYGON (((14.95977 50...
9 MULTIPOLYGON (((15.18524 50...
10 MULTIPOLYGON (((14.74597 49...
Výslednou tabulku můžeme vykreslit pomocí speciálního geomu: geom_sf()
%>%
migration ggplot(
aes(fill = hodnota)
+
) geom_sf() +
coord_sf(
datum = NA
+
) theme_void()
Použití geom_sf()
se liší od standardního použití ggplot2 v jednom detailu – geom_sf()
si sám ze sf
objektu určí, co má kreslit – body, linky nebo polygony. Správně nadefinované CRS také zajišťuje, že mapa je správně vykreslená. Kromě toho je ovládání stejné jako u ostatních geomů.
14.10 Ukládání obrázků
Vytvořené obrázky je možné ukládat s použitím funkce ggsave()
:
ggsave(filename, plot = last_plot(), device = NULL, path = NULL,
scale = 1, width = NA, height = NA, units = c("in", "cm", "mm"),
dpi = 300, limitsize = TRUE, ...)
V základním nastavení plot = last_plot()
funkce ggsave()
ukládá poslední vykreslený obrázek. Do parametru plot
je však možné přiřadit i obrázek (datovou strukturu) uloženou v proměnné. To je velmi užitečné například při tvorbě obrázků, jejichž vykreslování je velmi náročné – komplikovaných map, bodových grafů s opravdu velkým počtem pozorování a podobně.
Jméno souboru, do kterého se má obrázek uložit se definuje v parametru filename
jako řetězec. ggsave()
řetězec analyzuje a podle přípony zvolí exportní formát obrázku. ggsave()
umožňuje formáty do vektorových formátů i bitmap:
- vektorové formáty: eps, ps, tex (pictex), pdf, svg a wmf (pouze ve Windows)
- bitmapy: jpeg, tiff, png, bmp
Formát (výstupní zařízení) lze zadat přímo pomocí parametru device
.
První rozhodnutí je, zda použít vektorový formát nebo bitmapu. Uložení do bitmapy znamená uložení “fotografie” obrázku. Takto uložený obrázek může být menší (přesněji nikdy nebude velký) a lze očekávat, že půjde vložit do libovolného dokumentu. Na druhou stranu nepůjde zvětšovat bez různých zkreslení a šumů a výstup nikdy nebude kvalitnější než v případě použití vektorové grafiky.
Vektorové formáty ukládají data, která popisují, kde co na obrázku je. Na rozdíl od bitmap je lze libovolně škálovat bez ztráty kvality a občas přijde vhod i možnost je upravovat (např. posunovat popisky u geom_label()
/geom_text
). Schopnost různých WYSIWYG editorů (např. MS Word atp.) pracovat s vektorovými formáty je navíc omezená a je lepší ji řádně otestovat. Nevýhodou je také jejich velká velikost v případě, že objektů v obrázku je mnoho nebo mají složité tvary.
Příkladem obrázku, u kterého se dramaticky liší velikost podle použitého formátu je následující mapa ČR s katastrálními územími obcí. Zobrazená bitmapa (png) má přibližně 0,1 MB. Stejná mapa ve vektorovém formátu má téměř 11,6 MB.
Další parametry ggsave()
umožňují přesné nastavení výstupu co do rozměrů a rozlišení (pouze pro bitmapy). Při vhodném nastavení těchto parametrů vzhledem k výsledné publikaci lze dosáhnout dobrých výsledků i při používání bitmap. Ve většině případů je však doporučeníhodné používání vektorových formátů.
Poznámka
Pokud opravdu záleří na velikosti výstupních souborů, je možné zvážit dvě cesty:
- Zejména v případě map není zpravidla nutné pro vizualizace používat podrobnou mapu, která naprostu přesně kopíruje hranice (například) obce. Přesnost znamená spoustu bodů, které musí být ve výstupním formátu uloženy. Zkušenost ukazuje, že pro většinu aplikací postačí ponechat v mapě pouze 1 % bodů – tedy drasticky změnšit rozlišení mapy. Zjednodušení map velmi dobře umí
rmapshaper::ms_simplify()
. - Pokud ukládáte výstup jako bitmapu, je možné optimalizovat barevnou paletu. Typicky není nutné mít dokonalé barevné přechody, které jsou opět datově náročné. Například formát png umožňuje indexování barevné palety, které může vést k drastickému nížení velikosti výstupního souboru.
14.11 Co dělat a co nedělat
ggplot2 a jeho rozšíření nabízí ohromnou škálu možností. Před tím než začnete kreslit je dobré se rozmyslet, zda je vůbec rozumné kreslit. Často je užitečnější a pro čtenáře srozumitelnější prezentovat data v podobě tabulky – a to zejména pokud je jich málo. Pokud se rozhodnete kreslit, potom se vždy musíte snažit, aby obrázek čtenáři srozumitelně komunikoval nezkreslenou informaci.
Tufte (2001) formuluje jednoduché doporučení, které byste měli mít vždy v paměti:
Graphical excellence is that which gives to the viewer the greatest number of ideas in the shortest time with the least ink in the smallest space.
14.12 Aplikace
Aplikace I
V tomto příkladu využijeme tabulku VGAMdata::oly12
, která obsahuje údaje o účastnících Olympijských her v roce 2012.
<- VGAMdata::oly12 %>%
oly12 as_tibble()
print(oly12)
# A tibble: 10,384 × 14
Name Country Age Height Weight Sex DOB PlaceOB Gold Silver
<fct> <fct> <int> <dbl> <int> <fct> <date> <fct> <int> <int>
1 Lamusi A People… 23 1.7 60 M 1989-02-06 "NEIMO… 0 0
2 A G Kruger United… 33 1.93 125 M NA "Sheld… 0 0
3 Jamale Aar… France 30 1.87 76 M NA "BEZON… 0 0
4 Abdelhak A… Morocco 24 NA NA M 1988-09-02 "AIN S… 0 0
5 Maria Abak… Russia… 26 1.78 85 F NA "STAVR… 0 0
6 Luc Abalo France 27 1.82 80 M 1984-06-09 "" 0 0
7 Maria Laur… Argent… 30 1.82 73 F NA "" 0 0
8 Mohamed Ab… Morocco 23 1.87 75 M 1989-03-05 "" 0 0
9 Emanuele A… Italy 27 1.9 80 M 1985-08-07 "Genov… 0 0
10 Ilyas Abba… Algeria 19 1.7 NA M NA "MEDEA… 0 0
# ℹ 10,374 more rows
# ℹ 4 more variables: Bronze <int>, Total <int>, Sport <fct>, Event <fct>
Z tabulky oly12
vytvoříme tabulku s průměrnou váhou a výškou pro skupiny vymezené sportem a pohlavím:
<- oly12 %>%
oly12_stats mutate(
Sport = str_extract(Sport, "[:alnum:]*")
%>%
) group_by(Sex,Sport) %>%
summarise(
Height = mean(Height, na.rm = TRUE),
Weight = mean(Weight, na.rm = TRUE)
%>%
) ungroup() %>%
drop_na()
`summarise()` has grouped output by 'Sex'. You can override using the `.groups`
argument.
Nyní data z tabulky oly12
vizualizujte. Chceme vidět vztah mezi váhou a výškou. Základním nástrojem pro zobrazení vztahu je bodový graf:
%>%
oly12 ggplot(
aes(x = Height, y = Weight, color = Sex)
+
) geom_point(alpha = 0.3)
Warning: Removed 1346 rows containing missing values (`geom_point()`).
Tato vizualizace naznačuje, že v datech jsou dvě vzájemně posunutá mračna – ženy a muži. Úplně jasno o vztahu ale nemáme, kvůli obrovskému overplottingu.
V tomto případě může pomoci něco jako vícerozměrný histogram – např. geom_hex()
:
%>%
oly12 ggplot(
aes(x = Height, y = Weight)
+
) geom_hex()
Warning: Removed 1346 rows containing non-finite values (`stat_binhex()`).
Pro pohled na muže a ženy zvlášť je potom možné a užitečné použít facetování:
%>%
oly12 ggplot(
aes(x = Height, y = Weight)
+
) geom_hex() +
facet_wrap("Sex", scales = "free")
Warning: Removed 1346 rows containing non-finite values (`stat_binhex()`).
Nyní můžeme rozšířit obrázek o dodatečnou datovou vrstvu – pozici průměrů pro jednotlivé sporty:
%>%
oly12 ggplot(
aes(x = Height, y = Weight)
+
) geom_hex() +
geom_point(
data = oly12_stats,
aes(shape = Sport),
size = 3,
color = "red"
+
) scale_shape_manual(values = c(65:90)) +
facet_wrap("Sex", scales = "free")
Warning: Removed 1346 rows containing non-finite values (`stat_binhex()`).
Alternativně můžeme použít kernel density:
%>%
oly12 ggplot(
aes(x = Height, y = Weight, color = Sex)
+
) # geom_point(
# alpha = 0.1
# ) +
geom_density2d()
Warning: Removed 1346 rows containing non-finite values (`stat_density2d()`).
Aplikace II
Alternativní technikou zobrazení distribuce je dramatické snížení množství peředávaných informací. Na této myšlence je postaven tzv. boxplot. Porovnejte distribuci mužů a žen pomocí boxplotu.
load("data/ggplot2/village.RData")
%>%
village ggplot(
aes(x = gender, y = age)
+
) geom_boxplot()
Alternativou je využítí tzv. violin plotu.
%>%
village ggplot(
aes(x = gender, y = age)
+
) geom_violin()
Aplikace III
Základním nástrojem pro vizualizaci vztahů dvou spojitých proměnných je bodový graf. Použijte tabulku VGAMdata::oly12
a vykreslete vztah váhy a výšky sportovců z Francie. V obrázku také zobrazte věk a pohlaví sportovců.
<- VGAMdata::oly12 %>% as_tibble()
oly12
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
ggplot(
aes(x = Height, y = Weight, size = Age, color = Sex)
+
) geom_point(
shape = 1
+
) theme_classic()
Aplikace IV
Nyní na stejném vzorku rozlište sport, ve ktrém atleti závodí.
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
ggplot(
aes(x = Height, y = Weight, fill = Sport)
+
) geom_point(
shape = 21,
color = "white"
+
) theme_classic()
Toto je klasický příklad zobrazení, ve kterém je zobrazeno příliš mnoho informací a výsledek je potom těžko srozumitelný. Méně je někdy více. Zkuste snížit počet kategorií.
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
mutate(Sport = as.character(Sport)) %>%
mutate(
Sport = case_when(
str_detect(Sport,"Cycling") ~ "Cycling",
str_detect(Sport,"Canoe") | Sport == "Rowing" | Sport == "Sailing" ~ "Stuff with a boat",
%in% c("Basketball","Badminton","Football","Handball") ~ "Collective",
Sport %in% c("Archery","Fencing","Judo","Shooting","Weightlifting","Wrestling") ~ "Fighting",
Sport %in% c("Diving","Swimming") ~ "Watersports",
Sport str_detect(Sport,"Tennis") ~ "Tennis",
%in% c("Equestrian","Triathlon") ~ "Endurance",
Sport TRUE ~ Sport
)%>%
) ggplot(
aes(x = Height, y = Weight, fill = Sport)
+
) geom_point(
shape = 21,
color = "white",
size = 4,
alpha = 0.75
+
) scale_fill_brewer(
palette = "Paired"
+
) theme_classic()
Aplikace V
S pomocí stejných dat zkuste vykreslit vztah výška, váha, věk:
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
ggplot(
aes(x = Height, y = Weight, size = Age)
+
) geom_point(
shape = 1
+
) theme_classic()
Vztah s kategorií věk není příliš zřejmý… Zkusíme s tím něco udělat. Jednou možností je opět snížit množství informací – konkrétně rozdělit věk do jednotlivých kategorií a ty použít k obarvení.
Při nastavování kategorií pomůže pohled na histogram:
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
ggplot(
aes(x = Age)
+
) geom_histogram() +
theme_classic()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Jako celkem rozumné se zdá mít kategorie 0-20, 21-25, 26-30, 31-35 a 36+. Hint: cut()
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
mutate(
age_cat = cut(Age, breaks = c(0,20,25,30,35,Inf))
%>%
) ggplot(
aes(x = Height, y = Weight, fill = age_cat)
+
) geom_point(
shape = 21,
color = "white",
size = 4,
alpha = 0.75
+
) scale_fill_brewer(
name = "Age",
palette = "OrRd"
+
) theme_classic()
Věnujte, prosím, pozornost nastavení barev. Ty by měly odrážet charakter dat. Například *_brewer()
funkce pracují s třemi druhy palet_
- sequential (to je tento případ – kategorie tvoří logickou sekvenci, která jde v jednom směru)
- diverging (typicky přechod přes 3 barvy – představte si klasickou teplotní mapu)
- qualitative (mazi kategoriemi není žádný vztah)
Aplikace VI
Zůstaneme u stejné tabulky. Zkuste vykreslit distribuci váhy v jednotlivých věkových kategoriích.
Varianta 1
Můžete použít “zjednodušující” techniky jako boxplot nabo violinplot. Tyto techniky však mohou zkreslovat informaci co do počtu pozorování v každé kategorii. Někdy bývá užitečné kombinovat je s bodovým grafem
Zkuste následující obrázek. Pozor! Obrázek musí přesně komnikovat informaci čtenáři! (A zároveň pozor na overplotting.)
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
mutate(
age_cat = cut(Age, breaks = c(0,20,25,30,35,Inf))
%>%
) ggplot(
aes(x = age_cat, y = Weight)
+
) geom_violin(
color = NA,
fill = "grey75"
+
) geom_jitter(
height = 0,
width = 0.2
+
) scale_x_discrete(
name = "Age"
+
) theme_classic()
Varianta 2
Tohle už samotný ggplot jendoduše nezvládne. Můžete se zkusit porozhlédout po rozšířeních: https://exts.ggplot2.tidyverse.org/gallery/
library(ggridges)
%>%
oly12 filter(Country == "France") %>%
drop_na(Height,Weight,Age,Sex) %>%
mutate(
age_cat = cut(Age, breaks = c(0,20,25,30,35,Inf))
%>%
) ggplot(
aes(x = Weight, y = age_cat)
+
) geom_density_ridges() +
scale_y_discrete(
name = "Age"
+
) theme_classic()
Picking joint bandwidth of 5.37