Die Grammar of graphics und ggplot2

Grammar of graphics

Hadley Wickhams Paket ggplot2 versucht, die Erstellung von Grafiken in einer einheitlichen Grammatik, der “grammar of graphics”, auszudrücken. Das Ziel hier ist es, nicht mehr in “Scatterplot” und “Boxplot” als einzelne Kategorien zu denken und diese einzeln erstellen lernen zu müssen, sondern alle Abbildungen mit derselben Logik erstellen zu können.

In Seinem Paper (Wickham, 2010) werden die folgenden Komponenten als grundlegende Bausteine einer Grafik eingeführt:

  • a default dataset and set of mappings from variables to aesthetics,
  • one or more layers, with each layer having one geometric object, one statistical trans- formation, one position adjustment, and optionally, one dataset and set of aesthetic mappings,
  • one scale for each aesthetic mapping used,
  • a coordinate system,
  • the facet specification. (Wickham, 2010, p. 8)

Komponenten eines Plots

Wir müssen für einen Plot also überlegen:

  1. welche Daten wir auf welche Aesthetics mappen
  2. welche geometrischen Objekte wir in welcher Reihenfolge auf die Grafik layer wollen und ob diese optionale andere Daten benötigen
  3. welche Skala wir für die Mappings nutzen wollen
  4. welches Koordinatensystem wir nutzen wollen
  5. in welchen Facetten wir die Daten darstellen wollen

Komponenten in ggplot2

Beispieldaten

Pinguine im Eis

Im palmerpenguins-Paket werden Pinguin-Beobachtungen der Palmer-Station in der Antarktis zur Verfügung gestellt:

palmerpenguins::penguins %>% 
  head()
# A tibble: 6 × 8
  species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
1 Adelie  Torgersen           39.1          18.7               181        3750
2 Adelie  Torgersen           39.5          17.4               186        3800
3 Adelie  Torgersen           40.3          18                 195        3250
4 Adelie  Torgersen           NA            NA                  NA          NA
5 Adelie  Torgersen           36.7          19.3               193        3450
6 Adelie  Torgersen           39.3          20.6               190        3650
# ℹ 2 more variables: sex <fct>, year <int>

1. Daten und Aesthetics - ggplot() + aes()

Wir wollen den Zusammenhang zwischen Körpergewicht und Schnabellänge über die Spezies betrachten. Dafür legen wir die “Leinwand” des Plots mit den zentralen mappings an:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species))

2. Geometrische Objekte - geom_*

Diesem Plot fügen wir Punkte als geometrische Objekte hinzu, die uns zu einem Scatterplot führen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point()
Warning: Removed 2 rows containing missing values (`geom_point()`).

Wir als weiteres geometrisches Objekt könnten wir uns zum Beispiel wünschen, die Mittelwerte pro Gruppe mit den Abweichungen auf x- und y-Achse darzustellen. Dazu müssen wir zuerst diesen neuen Datensatz berechnen:

penguin_means <- palmerpenguins::penguins %>% 
  group_by(species) %>% 
  summarise(across(c(bill_length_mm, body_mass_g), ~mean(., na.rm=T)))

…und auf den Plot in einem neuen Layer hinzufügen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point() +
  geom_point(data=penguin_means)
Warning: Removed 2 rows containing missing values (`geom_point()`).

Für den Layer können wir auch speifische geometrische Eigenschaften einfügen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point() +
  geom_point(data=penguin_means, shape = 3)
Warning: Removed 2 rows containing missing values (`geom_point()`).

Oder direkt ein neues Mapping einführen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original')) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'))
Warning: Removed 2 rows containing missing values (`geom_point()`).

Und auch beide Varianten kombinieren:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3)
Warning: Removed 2 rows containing missing values (`geom_point()`).

3. Skalen - scale_*

Die Symbole und Farben haben genau wie x- und y- Koordinaten als ästhetische Mappings eigene Skalen. Wenn uns also die Farben nicht passen, können wir einfach eine andere Skala setzen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d()
Warning: Removed 2 rows containing missing values (`geom_point()`).

Genauso können wir einfach die Skala der Symbole an unsere Wünsche anpassen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))
Warning: Removed 2 rows containing missing values (`geom_point()`).

4. Koordinatensystem coord_*

Das Koordinatensystem passt von der Auflösung erstmal, aber wir wollen eine direkte Zuordnung von 10 mm Schnabellänge zu 1000 g Körpermasse. Dazu fügen wir eine coord_*-Spezifikation an:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))+
  coord_fixed(ratio = 10/1000)
Warning: Removed 2 rows containing missing values (`geom_point()`).

5. Facetten - facet_*

Als letzte Komponente überlegen wir uns, dass die verschiedenen Beobachtungspunkte als Einteilung interessant sein könnten und wir diese getrennt betrachten wollen. Um diese Facettierung umzusetzen ergänzen wir zuerst den Mittelwerts-Datensatz um den Beobachtungsort:

penguin_means <- palmerpenguins::penguins %>% 
  group_by(species, island) %>% 
  summarise(across(c(bill_length_mm, body_mass_g), ~mean(., na.rm=T)))
`summarise()` has grouped output by 'species'. You can override using the
`.groups` argument.

Um dem Graphen anschließend die Facettierung anzufügen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island)
Warning: Removed 2 rows containing missing values (`geom_point()`).

APA-Styling

Aus Julias Folien1 kommt folgende Checkliste für APA-Grafiken:

Screenshot der APA-Checkliste

Wir müssen also noch

  • die Elemente klar labeln
  • sicherstellen dass der Font passt
  • die Legende unter dem Bild anordnen
  • die Beschriftung der Legende überprüfen

Daneben habe ich noch ein persönliches Problem mit dem grauen Hintergrund, damit fangen wir an:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  theme_light()
Warning: Removed 2 rows containing missing values (`geom_point()`).

In diesem Zusammenhang können wir auch gleich Base-Font und Schriftgröße setzen. theme_light setzt die kleinste Schriftgröße auf .8 * die base_size, wenn wir minimal 8pt große Schrift haben wollen.

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  theme_light(base_family = 'Helvetica',
              base_size = 10)
Warning: Removed 2 rows containing missing values (`geom_point()`).

Als nächstes die Anpassung der Achsen- und Legenden-Labels:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'mean'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(original = 20, mean = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  theme_light(base_family = 'Helvetica',
              base_size = 10) +
  labs(x = 'Schnabellänge (mm)',
       y = 'Körpergewicht (g)',
       color = 'Pinguin-Spezies',
       shape = 'Aggregations-Niveau')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Die Namen der Formen sind noch nicht title-cased, das können wir am einfachsten in der Definition ändern:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'Original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'Mittelwert'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(Original = 20, Mittelwert = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  theme_light(base_family = 'Helvetica',
              base_size = 10) +
  labs(x = 'Schnabellänge (mm)',
       y = 'Körpergewicht (g)',
       color = 'Pinguin-Spezies',
       shape = 'Aggregations-Niveau')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Die Legende muss dann noch an die Unterseite des Graphen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'Original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'Mittelwert'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(Original = 20, Mittelwert = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  theme_light(base_family = 'Helvetica',
              base_size = 10) +
  labs(x = 'Schnabellänge (mm)',
       y = 'Körpergewicht (g)',
       color = 'Pinguin-Spezies',
       shape = 'Aggregations-Niveau') +
  theme(legend.position = 'bottom')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Und damit ist die Formatierung fertig.

Convenient Standards

Die beiden theme-Funktionen müssten wir so an jede Grafik anfügen. Solche Wiederholungen sind schlechter Stil und stören beim Lesen des Skripts, deswegen bietet ggplot2 convenience-Funktionen um allgemeine Einstellungen zu setzen. Mit dem folgenden Snippet am Anfang des Skripts werden die Standards für alle Grafiken genutzt:

my_theme <-  theme_light(base_family = 'Helvetica',
              base_size = 10) +
  theme(legend.position = 'bottom')

theme_set(my_theme)

So kann ich jetzt einfach Beispielsweise ein eingefärbtes Histogramm für die Flossen-Länge mit den gesetzten Standards erstellen:

palmerpenguins::penguins %>% 
  ggplot(aes(x = flipper_length_mm,
             fill = sex)) +
  geom_histogram(binwidth = 5)
Warning: Removed 2 rows containing non-finite values (`stat_bin()`).

Export

Zum Abschluss können wir die Grafiken exportieren.

Die Textgröße ist in pt gesetzt, deswegen sollten wir nach dem Export die Größe im besten Fall nicht mehr viel ändern.

Eine Din A4-Seite ist 8.2 x 11.6 Zoll groß. Wenn wir eine Grafik auf 80% der Seitenbreite haben wollen, brauchen wir also eine 6.56 Zoll breite Grafik.

Zum Speichern setzen wir unsere Grafik und die Maße in ggsave ein:

p <- palmerpenguins::penguins %>% 
  ggplot(aes(x = bill_length_mm, 
             y = body_mass_g,
             color = species)) + 
  geom_point(aes(shape = 'Original'),
             alpha = .5) +
  geom_point(data=penguin_means,
             aes(shape = 'Mittelwert'),
             size = 3) +
  scale_color_viridis_d(end = 0.7) +
  scale_shape_manual(values = c(Original = 20, Mittelwert = 3))+
  coord_fixed(ratio = 10/1000) +
  facet_wrap(~island) +
  labs(x = 'Schnabellänge (mm)',
       y = 'Körpergewicht (g)',
       color = 'Pinguin-Spezies',
       shape = 'Aggregations-Niveau')

ggsave(plot = p,
       filename = 'imgs/penguin_scatter.png',
       width = 6.56,units = 'in')
Saving 6.56 x 5 in image
Warning: Removed 2 rows containing missing values (`geom_point()`).

Der Export sieht so aus:

Exportierte Grafik Erstens können wir schonmal feststellen dass die Grafik ruhig schmaler werden kann. Die Export-Funktion hat uns eine Höhe von 6.74 Inches mitgeteilt, das können wir ruhig auf 4 inches reduzieren.

Zweitens ist die Legende ein bisschen kaputt gegangen.

Dazu gibt es drei Möglichkeiten zur Anpassung:

p_linebreaks <- p +
  guides(color = guide_legend(nrow = 3),
         shape = guide_legend(nrow = 2))

ggsave(plot = p_linebreaks,
       filename = 'imgs/penguin_scatter_linebreaks.png',
       width = 6.56, height = 4, units = 'in')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Grafik mit Legende mit Linebreaks
p_smaller_text <- p +
  theme(legend.title = element_text(size = 8,
                                    colour = 'darkgrey'))

ggsave(plot = p_smaller_text,
       filename = 'imgs/penguin_scatter_smaller_text.png',
       width = 6.56, height = 4,units = 'in')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Grafik mit kleineren Legenden-Überschriften
p_legend_in_plot <- p + 
  theme(
    # Legende an oberer rechter Ecke orientieren
    legend.justification=c(1,1),
    # Legende an obere rechte Ecke schieben
    legend.position=c(1,0.98), 
    # Legenden-Hintergrund fast transparent machen
    legend.background = element_rect(fill = rgb(1,1,1,.5)),
    # Hintergrund der Legenden-Felder fast transparent machen
    legend.key = element_rect(fill = rgb(1,1,1,.5)))

ggsave(plot = p_legend_in_plot,
       filename = 'imgs/penguin_scatter_legend_in_plot.png',
       width = 6.56, height = 4,units = 'in')
Warning: Removed 2 rows containing missing values (`geom_point()`).

Grafik mit Legende in plot-Region

Aufgabe

  1. Lese den im Repo zu diesem Skript zur Verfügung gestellten Datensatz example.csv ein. Dazu kann einfach der folgende Chunk genutzt werden:
read_csv('https://raw.githubusercontent.com/MBrede/r_thesis_tools/main/data/example.csv')
  1. Stelle die Reaktionszeiten und Accuracies in einem Scatterplot dar.

  2. Färbe den Graphen nach Gruppen ein

  3. Füge Mittelwerte und Standardabweichungen pro Gruppe hinzu. Füge die Standardabweichungen dabei mit geom_linerange in zwei layern hinzu (einem für die x- und einem für die y-Richtung)

  4. Passe die Grafik so an, dass sie APA-konform ist

  5. Mache die Grafik so unästhetisch, wie es die APA-Richtlinien zulassen. Hier sind Fonts, Farben und Formen zu finden.

Grolemund, G., & Wickham, H. (2016). R for Data Science. https://r4ds.had.co.nz/
Wickham, H. (2010). A layered grammar of graphics. Journal of Computational and Graphical Statistics, 19(1), 3–28.

  1. Danke Julia!↩︎