Große Datenmengen visualisieren mit R, ggplot2 und trelliscopejs

Visualisierung der gapminder-Daten mit R, ggplot2 und trelliscope

Wie kann man große Datenmengen in R so darstellen, dass sie gut lesbar sind und viele Informationen preisgeben? „Große Datenmengen“ verstehen wir hier im Sinne von „viele Untergruppen“, nicht unbedingt im Sinne von vielen Gigabyte.

Wer versiert ist, denkt vielleicht an eine Shiny App, die große Flexibilität und viele Nutzereinstellungen erlaubt. Wir suchen heute jedoch eine Möglichkeit, die wir mit wesentlich weniger Aufwand umsetzen können.

Die Gapminder-Daten

Wir arbeiten mit den Gapminder-Daten, die Hans Rosling bekannt gemacht – seine Videos sind auch heute noch sehenswert (120.000 Datenpunkte in vier Minuten, dabei eine Geschichte erzählen – über 9 Mio. Youtube-Aufrufe Stand April 2020!). Unser Beispieldatensatz aus dem gapminder-R-Paket von Jenny Bryan enthält 1704 Zeilen: Daten zu 142 Ländern, mit je 12 Zeitpunkten, von 1952 bis 2007 in Fünf-Jahres-Schritten. Verzeichnet sind Land, Kontinent, Jahr, Lebenserwartung, Bevölkerungszahl sowie ein Wohlstandsindikator: das Bruttoinlandsprodukt pro Kopf (das wir in diesem Beitrag vernachlässigen).

library(gapminder)
library(tidyverse)

# Daten kennen lernen
data(gapminder)
str(gapminder)

Lebenserwartung in Europa im Zeitverlauf: Erster Versuch mit ggplot2

Für den ersten Versuch nehmen wir ggplot2, um die Lebenserwartung im Zeitverlauf darzustellen. Wir beschränken uns auf Europa:

gapminder %>%
 filter(continent == "Europe") %>%
 ggplot(aes(x = year, y = lifeExp, color = country)) +
   geom_line() +
   labs(title = "Europe", subtitle = "Life Expectancy by Year",
   caption = "Source: Gapminder project /\n
   gapminder R package by Jenny   Bryan") +
   scale_color_discrete(name = "")
Gapminder: Lebenserwartung im Zeitverlauf - Europa
Gapminder-Daten: Lebenserwartung im Zeitverlauf – Europa. Zu viele Länder, nicht gut lesbar …
(Klicken zum Vergrößern)

Die Farben sind kaum unterscheidbar, es sind zu viele Länder. Kein gutes Diagramm.

ggplot2 und facets

Zweiter Versuch mit ggplot2: Wir können sog. facets nutzen, um mehrere Diagramme für Untergruppen zu erstellen. Diesmal wagen wir uns an alle Länder und teilen nach Kontinenten auf:

gapminder %>%
 ggplot(aes(x = year, y = lifeExp, color = country)) +
   geom_line() +
   facet_wrap(~ continent) +
   labs(title = "Life Expectancy by Year",
   subtitle = "Facets by continent",
   caption = "Source: Gapminder project /\n
   gapminder R package by Jenny Bryan") +
   scale_color_discrete(guide = NULL)
Gapminder: Lebenserwartung im Zeitverlauf, facets nach Kontinent
Gapminder: Lebenserwartung im Zeitverlauf, facets nach Kontinent
(Klicken zum Vergrößern)

Auch hier tun wir uns äußert schwer – wir erhalten einen groben Überblick, können aber kaum Details ablesen. Die Legende habe ich diesmal ausgeschaltet.

Besser: plotly für Mouse-Over-Informationen

Eine wesentliche Verbesserung bietet uns das plotly-Paket von Carson Sievert an: Wir können Mouse-Over-Effekte einbauen, sodass wir, je nach Geduld, Informationen für einzelne Länder ablesen können, die an der Position des Mauszeigers eingeblendet werden:

library(plotly)

p <- gapminder %>%
ggplot(aes(x = year, y = lifeExp, color = country)) +
   geom_line() +
   facet_wrap(~ continent) +
   labs(title = "Life Expectancy by Year",
        subtitle = "Facets by    continent",
        caption = "Source: Gapminder project /\n
        gapminder R package by Jenny Bryan") +
   theme_bw() +
   theme(legend.position = "none")

ggplotly(p)

plotly ist dabei sehr anwenderfreundlich: Wir können wir bisher ein ggplot2-Diagramm erstellen, speichern es jedoch als Objekt (hier: p) und übergeben es der Funktion ggplotly. Per Javascript wird das Diagramm interaktiv, die Ästhetiken werden als Informationen eingeblendet. (Das funktioniert nur im HTML-Format, auch offline von lokaler Datei im Browser, aber nicht in statischen Dokumenten wie Word, PDF, Powerpoint. Mit dem Argument tooltip in ggplotly() kann man beeinflussen, welche Informationen bei Mouse-Over angezeigt werden.)

Visualisierung der gapminder-Daten mit plotly
Visualisierung der gapminder-Daten mit ggplot2 und plotly. Anklicken zum Vergrößern.
Dies ist ein statisches Bild – hier geht’s zur interaktiven Version

Nun können wir zum Beispiel herausfinden, dass der Einbruch in Afrika in Ruanda passierte (Völkermord) und der auffällige Knick in Asien (im Bild eingeblendet) sich auf Kambodscha bezieht. Dem Leser sind nun viele Informationen zugänglich und er kann entscheiden, wie viel Mühe er aufwinden will, um sie abzurufen.

Das ist bereits gut – geht es noch besser? Schön wäre es, genaue Infos zu jedem Land zugänglich zu machen. 142 Unterdiagramme wären jedoch ziemlich unübersichtlich als Facetten (wie oben die Kontinente) – oder?

Elegante interaktive Darstellung mit trelliscopejs

trelliscopejs von Ryan Hafen erlaubt es uns, mit relativ wenig R-Code noch deutlich über die Möglichkeiten von plotly hinauszugehen: Wir können nun jedes Land separat darstellen, die Ergebnisse auf mehrere Bildschirmseiten verteilen, zwischen den Seiten navigieren und nach beliebigen Merkmalen sortieren und filtern. Wir können sogar eigene Berechnungen ergänzen und interaktiv nutzbar machen:

# Compute cognostics:
# delta life exp

gapminder_cog <- gapminder %>%
group_by(country) %>%
   mutate(delta_lifeExp = max(lifeExp) - min(lifeExp)) %>%
   ungroup()

# R²

getrsq <- function(data) {
   model <- lm(lifeExp ~ year, data = data)
   summary(model)$r.squared
}

gapminder_cog <- gapminder_cog %>%
   group_by(country) %>%
   nest() %>%
   mutate(rsq = round(map_dbl(data, getrsq), 3)) %>%
   unnest(cols = data) %>%
   ungroup() %>%
   mutate(rsq = cog(val = rsq, desc = "R-Quadrat",
      default_label = TRUE),
   delta_lifeExp = cog(val = delta_lifeExp,
   desc = "delta lifeExp", default_label = TRUE))

Hier habe ich mit einer benutzerdefinierten Funktion getrsq in Verbindung mit genisteten Daten (nest aus dem tidyr-Paket) sowie map_dbl aus dem purrr-Paket mit wenigen Codezeilen für jedes Land ein separates simples Regressionsmodell aufgestellt. (Genauer dazu siehe den Beitrag Elegante R-Programmierung mit purrr::map und genisteten Datensätzen. Mit der neuen dplyr-Version 1.0, die in Kürze auf CRAN erscheinen soll, soll das noch einfacher gehen, ohne purrr.)

Die Regression versucht ganz einfach, die Lebenserwartung aus dem Zeitverlauf zu erklären. Wir entnehmen dem Modell simpler Weise nur das R-Quadrat. Das wollen wir gleich nutzen, um danach zu sortieren: Dann haben wir ein Kriterium, nach dem wir Länder herausfinden können, in denen es keinen klaren linearen Trend gab im Sinne kontinuierlich steigender Lebenserwartung – wie oben schon angedeutet an den „Knicken“.

Zusätzlich habe ich noch delta_lifeExp berechnet: Die Differenz zwischen maximaler und minimaler Lebenserwartung je Land – anders gesagt: wie sehr hat sich die Lebenserwartung je Land zwischen 1952 und 2007 verändert.

Der Diagramm-Code orientiert sich an der ggplot2-Syntax, enthält jedoch facet_trelliscope statt facet_wrap und aktiviert gleich noch plotly, um Mouse-Over-Effekte einzubauen. Ich verwende jetzt Punkte statt Linien und habe noch die Bevölkerungszahl in der Punktgröße codiert:

gapminder_cog %>%
 mutate(population = scales::number(pop)) %>%
 ggplot(aes(x = year, y = lifeExp, size = pop, label = population)) +
   geom_point() +
   facet_trelliscope(~ country + continent, nrow = 2, ncol = 5,
     as_plotly = TRUE,
     plotly_args = list(tooltip = c("x", "y", "label")),
     thumb = FALSE) +
   theme_bw() +
   theme(legend.position = "none")
Visualisierung der gapminder-Daten mit R, ggplot2 und trelliscope
Visualisierung der gapminder-Daten mit R, ggplot2 und trelliscope
Statisches Bild – hier geht es zur interaktiven Version (erstellt mit RMarkdown)

Hier im Bild habe ich aufsteigend nach R² sortiert (über den Menübalken links). Es erscheinen Länder mit deutlich nichtlinearem Verlauf: Ruanda (siehe oben, Effekte des Völkermords), Botswana, Zimbabwe und Zambia – im südlichen Afrika dürfte HIV einen starken Einfluss auf die Lebenserwartung ausgeübt haben.

Mit relativ wenigen Codezeilen können wir so sehr informative Grafiken erstellen, die den Leser einladen, je nach Lust und Laune Informationen gezielt abzurufen: sei es durch Filtern (z. B. nur continent: Europa), sei es durch Sortieren nach verschiedenen Merkmalen. Beispielfragen (beantwortbar mit der oben verlinkten interaktiven Version): Welche Länder in Europa hatten die geringste bzw. größte Veränderung in der Lebenserwartung im Zeitverlauf (Variable delta_lifeExp; Trick: auf- oder absteigend danach sortieren)? Welche Länder in Europa weisen das höchste bzw. niedrigste R² auf?

Praxistaugliche Visualisierungen?

Wie man derartige Darstellungen in der Praxis nutzen kann, hängt sicher vom Leser / Betrachter / Kunden ab. Nicht jeder Kunde wird sich auf dieses HTML-Format einlassen oder Zeit und Lust haben, sich intensiver damit zu befassen – manche benötigen schnelle Erkenntnisse. Dann halte ich diese Darstellungsform dennoch für nützlich, und sei es nur intern, zum eigenen Gebrauch, um viele Fragen mit einer informativen Grafik beantworten zu können, anstatt zahlreiche Einzeldiagramme zu erstellen und darin oder in Zahlentabellen zu blättern.

Freue mich über Kommentare!