---
title: Wprowadzenie do ggplot2
format:
html:
embed-resources: true
code-copy: true
lang: pl
theme: cosmo
format-links: false
pdf:
pdf-engine: xelatex
lang: pl
engine: knitr
editor: source
execute:
error: false
---
```{r include=FALSE}
if (knitr::is_latex_output()) {
knitr::opts_chunk$set(dev = 'cairo_pdf')
}
```
`ggplot2` jest jednym z najczęściej wykorzystywanych pakietów do tworzenia grafiki w R. Pozwala tworzyć w łatwy sposób dość skomplikowane wykresy, posiada również dość rozsądne ustawienia standardowe, dzieki którym możemy uzyskać estetycznie wyglądające ilustracje małym nakładem pracy.
Nie jest on dołączy do podstawowej dystrybucji R i należy zainstalować go za pomocą polecenia `install.packages`.
```{r}
# install.packages('ggplot2') # instalacja ggplot2
library(ggplot2)
```
Praca z `ggplot2` różni się od pracy z grafiką w standardowym R. Podstawowe różnice to:
1. Preferowana jest praca z ramkami danych, raczej nie zajdziemy za daleko pracując na pojedynczych wektorach.
2. Przestaje obowiązywać metafora płótna. Rysując w standardowym R mogliśmy podchodzić do tego jak do nakładania kolejnych linii, punktów, segmentów itp. za pomocą funkcji niskiego poziomu. W przypadku `ggplot2` raczej definiujemy wykres, nie przejmujemy się najczęściej niskopoziomowymi szczegółami.
3. Opiera się na teoretycznej koncepcji *grammar of graphics* i stara się ją implementować.
4. Wykresy tworzymy dodając (`+` - `ggplot2` przeciąża operator dodawania) do siebie obiekty reprezentujące "warstwy" albo "mapowania" w specyficznej "gramatyce grafiki" implementowanej przez `ggplot2`.
5. Inaczej zapisujemy grafikę - używamy funkcji `ggsave`.
**Co będziemy omawiać:**
1. Zarys możliwości pakietu.
2. Tworzenie prostych wykresów oraz podstawy gramatyki grafiki (mapowanie).
3. Różne rodzaje wykresów dla jednej lub dwóch zmiennych nominalnych i liczbowych.
4. Kilkupanelowe wykresy.
**Co nie będzie omawiane, ale jest ważną czescią pakietu `ggplot2`:**
1. Funkcje niskiego poziomu do rysowania kształtów.
2. Osie, skale.
3. Podpisy, napisy, przypisy (elementy tekstowe).
4. Drobnoziarnistwa kontrola.
Innymi słowy - w `ggplot2` możemy zmieniać detale tworzonych przez nas wykresów, ale nie będziemy omawiać każdej służącej do tego funkcji i jej argumentu. Informacje można łatwo znaleźć w Internecie, dokumentacja rozmaitych funkcji bogata jest też w przykłady.
# Kilka prostych wykresów w ggplot2
Zacznijmy od kilku prostych wykresów. Przypomnijmy sobie jak wyglądały dane ze zbioru `beaver1` — będziemy na nich pracować.
```{r}
head(beaver1)
```
Na początek narysujemy histogram temperatury. Funkcja `ggplot` wskazuje dane, w `aes` ustalamy mapowanie, a `geom_histogram` określa rodzaj wykresu. Zmienne przekazujemy **bez cudzysłowiów** i są nimi **nazwy kolumn** w naszej ramce danych. Za chwilę przyjrzymy się dokładniej każdej z tych części.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram()
```
Dla dwóch zmiennych — `x` oraz `y` — możemy narysować wykres punktowy (*scatterplot*).
```{r}
ggplot(data = beaver1[beaver1$day == 346,], aes(x = time, y = temp)) +
geom_point()
```
Mapując kolumnę z wektorem typu `factor` na `color`, możemy ustalić inne kolory ze względu na wartość tej kolumny. W poniższym przypadku punkty dotyczące obserwacji w obu dniach oznaczone są innymi kolorami. Warto zauważyć, że `ggplot2` automatycznie stworzył legendę (czego nie robią funkcje wbudowane w R!).
```{r}
beaver1$dzien <- as.factor(beaver1$day)
ggplot(data = beaver1, aes(x = time, y = temp, color = dzien)) +
geom_point()
```
Zmieniając *geom*, możemy zmienić typ wykresu. Listę wszystkich możliwości z łatwością znajdą Państwo w internecie. Pokażemy sobie jednak kilka z nich. Za pomocą `geom_line` możemy stworzyć wykres liniowy.
```{r}
ggplot(data = beaver1[beaver1$day == 346,], aes(x = time, y = temp)) +
geom_line()
```
`geom_step` stworzy wykres schodkowy.
```{r}
ggplot(data = beaver1[beaver1$day == 346,], aes(x = time, y = temp)) +
geom_step()
```
Jeśli jedna ze zmiennych jest kategorialna (`factor`), możemy narysować na przykład wykres pudełkowy. Jak jednak widzimy, mapowanie `color` nie zadziałało tak, jak byśmy chcieli — pokolorowało tylko linie na wykresie.
```{r}
ggplot(data = beaver1, aes(x = dzien, y = temp, color = dzien)) +
geom_boxplot()
```
Jeżeli chcielibyśmy pokolorować również wypełnienie, musielibyśmy użyć `fill`. Pokażemy to na wykresie skrzypcowym.
```{r}
ggplot(data = beaver1, aes(x = dzien, y = temp, fill = dzien)) +
geom_violin()
```
# Tworzenie wykresów w języku grafiki `ggplot2`
Widzieliśmy już kilka gotowych wykresów. Spróbujmy teraz zbudować wykres krok po kroku i zobaczyć, za co odpowiadają jego poszczególne części.
## Jedna zmienna (lub dwie zmienne i jedna dyskretna)
Spróbujmy na początku stworzyć obiekt reprezentujący nasz wykres. Robimy to za pomocą funkcji `ggplot`.
```{r}
ggplot(data = beaver1)
```
Otrzymaliśmy puste płótno. Dlaczego? Oczywiście nie ustaliliśmy co ma być na osi rzędnych, co na odciętych. Stwórzmy więc obiekt, w którym przekażemy nasze mapowanie. Odpowiada za to argument `mapping` (będziemy go dalej pomijać i przekazywać wartości pozycyjnie), w którym przekazujemy obiekt stworzomy za pomocą funkcji `aes`.
```{r}
ggplot(data = beaver1, mapping = aes(x = temp))
```
Mamy już wykres, mamy już mapowanie - nawet dostaliśmy odpowiednie oznaczenia na osi. Nie mamy jednak na nim żadnego kształtu! Spróbujmy dodać do naszego wykresu jakiś kształt. Robimy to za pomocą funkcji `geom_rodzaj_kształtu`, którą "dodajemy" do już istniejącego obiektu.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram()
```
Stworzyliśmy histogram! Jak Państwo widzą wygląda chyba troszkę ładniej niż standardowy histogram w R.
No dobrze, ale czy możemy dodać jakiś inny kształt? Spróbujmy dodać małe kreseczki reprezentujące poszczególne obserwacje za pomocą `geom_rug`.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram() +
geom_rug()
```
Wygląda to całkiem nieźle!
Mogą się Państwo zastanawiać skąd `ggplot2` wie, co i gdzie powinien narysować wówczas, gdy "dodajemy" kolejne kształty? `ggplot2` uwzględnia dorysowując do wykresu kolejne kształte mapowanie (`aes`) odziedziczone z głównego wykresu (= obiektu stworzonego przez funckję `ggplot`). Dlatego właśnie możemy po prostu dodawać kolejne kształty.
Nie musimy godzić się na mapowanie "odziedziczone" z głównego wykresu. Dla każdego kształtu (*geoma*) możemy mapowanie zmienić lub uzupełnić o dodatkowe elementy. W tym celu używamy funkcji `aes`. W poniższym przykładzie użyliśmy `after_stat(density)`, żeby na osi y pokazać gęstość zamiast liczby obserwacji.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram(aes(y = after_stat(density))) +
geom_rug()
```
Chociaż kolejne geomy dziedziczą mapowanie po pierwszym obiekcie, to możemy je oczywiście nadpisać. W tym przypadku podobnie jak w poprzednich dodaliśmy do naszego histogramu kreseczki za pomocą `geom_rug`, tym razem jednak dodaliśmy dodatkowe mapowanie - zmienna `dzien` ma być mapowana na kolor kreseczek.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram(aes(y = after_stat(density))) +
geom_rug(aes(color = dzien))
```
Bez trudu możemy dodać jądrowy estymator gęstości dodając po prostu kolejny *geom* - tym razem `geom_density`. Proszę zwrócić uwagę, że zmieniliśmy jeszcze mapowanie kreseczek w `geom_rug`. `time` jest zmienną ciągłą (w miarę), więc `ggplot2` automatycznie dobrał ciągłą skalę kolorów i stworzył odpowiednią legendę.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_histogram(aes(y = after_stat(density))) +
geom_rug(aes(color = time)) +
geom_density()
```
`ggplot2` udostępnia oczywiście również rodzaje geomów bardziej wymyślne niż zwykłe histogramy. Na poniższym wykresie mają Państwo inny geom - `geom_dotplot`, który zamiast prostokątów rysuje kropki.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_dotplot() +
geom_rug()
```
Zwróćmy teraz uwagę na to, że w naszym zbiorze danych mamy dane z okresów aktywności i braku aktywności. Chcielibyśmy je rozdzielić, żeby lepiej zobrazować różnicę między nimi. Użyjemy do tego funkcji `facet_grid`, która w sposób automatyczny dzieli nasz wykres na panele według zadanego czynnika.
```{r}
beaver1['aktywnosc'] <- as.factor(beaver1$'activ')
ggplot(data = beaver1, aes(x = temp, fill = aktywnosc)) +
geom_dotplot() +
geom_rug() +
facet_grid(.~aktywnosc) # możemy wybrać dwie zmienne, składnia to `zmienna1 ~ zmienna2`
```
Możemy próbować uatrakcyjnić nasz wykres. Proszę zauważyć, że każdy *geom* ma tutaj inne mapowanie - wysokosc słupków kropek reprezentuje liczbe zliczeń w danym "koszyku" hisgoramu, kolor kropek - aktywność bobra, kreseczki - dokładna wartość każdej obserwacji. Z kolei kolor kreseczek reprezentuje dzień, w którym ich dokonywaliśmy. Każda z tych rzeczy była osobną "warstwą", którą definiowaliśmy w gramatyce grafiki `ggplot2`.
```{r}
ggplot(data = beaver1, aes(x = temp)) +
geom_dotplot(aes(fill = aktywnosc, color = aktywnosc),
binpositions="all", stackgroups = TRUE) +
geom_rug(aes(color=dzien))
```
Jeżeli chodzi o inne *geomy* w stylu histogramów to mamy jeszcze `geom_freqpoly`.
```{r}
ggplot(data = beaver1, aes(x = temp, fill = dzien, color = dzien)) +
geom_freqpoly()
```
Oraz `geom_area` (ten jest szczególnie efektowny).
```{r}
ggplot(data = beaver1, aes(x = temp, fill = dzien, color = dzien)) +
geom_area(stat='bin')
```
Jeżeli chodzi o wykresy słupkowe przedstawiające zliczenia, `geom_bar` sam policzy obserwacje w poszczególnych grupach. Nie musimy wcześniej przygotowywać tabeli liczebności:
```{r}
ggplot(data = beaver1, aes(x = dzien, fill = aktywnosc)) +
geom_bar()
```
Domyślnym zachowaniem jest ustawianie słupków na sobie, ale możemy je zmienić tak, by ustawiane były obok siebie.
```{r}
ggplot(data = beaver1, aes(x = dzien, fill = aktywnosc)) +
geom_bar(position= 'dodge')
```
Czasami jednak nie chcemy reprezentować na wykresie słupkowym liczby zliczeń czegoś, ale np. średnią, odchylenie standardowe, medianę. Wówczas chyba najprościej jest stworzyć sobie nową ramkę danych, w której będą obliczone te wartości i przekazać jako argument `stat` funkcji `geom_bar` wartość `"identity"`.
```{r}
df <- data.frame(sd = tapply(beaver1$temp, beaver1$dzien, sd), dzien = levels(beaver1$dzien))
df
ggplot(data = df, aes(y = sd, x = dzien, fill = dzien)) +
geom_bar(stat = 'identity')
```
Biblioteka `ggplot2` daje możliwość łączenia ze sobą kilka rodzajów wykresów. Na poniższym rysunku połączyliśmy wykres punktowy (z *jitterm* dla większej przejrzystosci) z wykresem skrzypcowym. Dodaliśmy również podpisy pod osiami oraz tytuł wykresu, zmieniliśmy też domyślne kolory (żeby mniej się zlewały).
```{r}
ggplot(data = beaver1) +
geom_violin(aes(x = dzien, y = temp, fill = dzien),
quantiles = c(0.25, 0.5, 0.75), quantile.linetype = 1) +
geom_jitter(aes(x = dzien, y = temp, color = aktywnosc)) +
scale_fill_brewer(palette='Set1') +
ggtitle('Wykres skrzypcowy') +
labs(x='Dzień pomiarów', y='Temperatura')
```
## Dwie zmienne ciągłe.
W przypadku sytuacji, w której mamy dwie zmienne ciągłe postępujemy dokładnie tak samo. Do ilustracji posłużymy się zbiorem `iris`, który dotyczy kwiatków.
```{r}
head(iris)
```
Znów, samo wywołanie funkcji `ggplot` nic nie daje.
```{r}
ggplot(data = iris)
```
Nawet ustawienie mapowania nic nie daje, chociaż ustala nam osie i siatkę.
```{r}
ggplot(data = iris) +
aes(x = Petal.Length, y = Petal.Width)
```
Pierwszy *geom* jaki omowimy to `geom_point` odpowiadający znanemu już nam wykresowy punktowemu.
```{r}
ggplot(data = iris) +
geom_point(aes(x = Sepal.Width, y = Sepal.Length))
```
Kstzałt punktów jest jedną z możliwościa mapowania zmiennej dyskretnej.
```{r}
ggplot(data = iris) +
geom_point(aes(x = Sepal.Width, y = Sepal.Length, shape = Species))
```
Podobnie jak poprzednio możemy dodać dodatkowe mapowanie, które trzecią zmienną - gatunek - mapuje jako kolor. Dodatkowo zmieniliśmy wygląd całego wykresu dodając jeden z kilku wbudowanych motywów (`theme_minimal`)
```{r}
ggplot(data = iris) +
geom_point(aes(x = Sepal.Width, y = Sepal.Length, col = Species)) +
theme_minimal()
```
Możemy oczywiście umieścić nasze punkty na trzech osobnych panelach w znany nam już sposób.
```{r}
ggplot(data = iris) +
geom_point(aes(x = Sepal.Width, y = Sepal.Length, col = Species)) +
facet_grid(. ~ Species) +
theme_bw()
```
Panele możemy umieścić w innej orientacji zmieniając kolejnośc zmiennych w funkcji `facet_grid`. Warto zwrócić uwagę, że podpisy pod kategoriami są prawidłowo w pozycji poziomej. Pokazuję również inny motyw (`theme_dark`)
```{r}
ggplot(data = iris) +
geom_point(aes(x = Sepal.Width, y = Sepal.Length, col = Species)) +
facet_grid(Species~.) +
theme_dark()
```
Bardzo przydatne może być dla Państwa dodawanie linii regresji do wykresu punktowego. Pokazuje więc jak zrobić to za pomocą `stat_smooth`. Dopasowuje ona model liniowy do danych. Zacienione pole do przedział ufności (domyślnie 95%) dla linii regresji.
```{r}
ggplot(data = iris) +
facet_grid(.~Species) +
stat_smooth(aes(x = Sepal.Width, y = Sepal.Length, col = Species), method = "lm", fullrange= TRUE)+
geom_point(aes(x = Sepal.Width, y = Sepal.Length, col = Species))
```
Należy jednak pamiętać, że domyślnie dopasowywane jest nie model liniowy, ale ten: https://en.wikipedia.org/wiki/Local_regression
Linia jest więc pogięta, podobnie przedziały ufności.
```{r}
ggplot(data = iris, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) +
geom_point() +
geom_smooth() +
facet_grid(.~Species)
```
Oczywiście nic nie stoi na przeszkodzie dodawać znane już nam geomy takie jak `geom_rug`.
```{r}
ggplot(data = iris, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) +
geom_point() +
geom_smooth() +
geom_rug() +
facet_grid(.~Species)
```
# Zakończenie - bardziej skomplikowany przykład.
W kolejnym przykładzie tworzymy bardziej skomplikowany wykres dotyczący rozmaitych parametrów diamentów. Dane znajdują się w zbiorze `diamonds`, który wbudowany jest w R.
```{r}
head(diamonds)
```
```{r}
ggplot(data = diamonds, aes(y=carat, x = cut, size = price, color = cut)) +
geom_jitter()
```
```{r}
ggplot(data = diamonds, aes(x=carat, y = price)) + # podstawowe mapowanie
geom_point(aes(color = color)) + # mapowanie dla punktów: kolor diamentów -> kolor punktów
facet_grid(clarity~cut) + # dzielimy na panele ze względu na przejrzystość i szlif
stat_smooth(method = 'lm') + # dopasowujemy do danych linię regresji
ggtitle('Diamenty przyjacielem studenta') +
theme_minimal()
```
## Uwagi:
1. Proszę dobierać rodzaj wykresu do celu - inny będzie w specjalistycznym czasopiśmie, inny na prezentacji, inny na plakacie, inny w artykule prasowym, inny dla siebie do pracy.
2. Nie wszystkie mapowania są sobie równe - kwestie poznawcze.
3. Nie należy przesadzać, należy dobrze zaplanować co i dlaczego chcemy pokazać.
4. Ale nie warto bać się eksperymentowania.
# Przydatne linki:
1. Plotly + `ggplot2` = wygryw: https://plot.ly/ggplot2/
2. Dokumentacja https://ggplot2.tidyverse.org/
3. Dodatki: https://exts.ggplot2.tidyverse.org/
5. Galeria grafik wg koncepcji Tufte'a - niektóre z wykorzystaniem `ggplot2`: http://motioninsocial.com/tufte/