Storytelling mit R und ggplot2: Länderfinanzausgleich

ggplot2 ist ein mächtiges Werkzeug, um ansprechende Grafiken zu erstellen. Will man Zuhörer oder Leser „mitnehmen“, empfiehlt es sich, nicht nur Daten zu präsentieren, sondern auch eine Geschichte damit zu erzählen. Unser Storytelling-Beispiel bezieht sich auf den Länderfinanzausgleich. Unter Storytelling verstehe ich hier: Bestimmte Aspekte hervorheben, die ich als Bearbeiter wichtig finde, um den Blick der Betrachter gezielt auf diese Details zu lenken. Dem steht meist eine größere Menge an Daten gegenüber, denen eine geringere Bedeutung beigemessen wird. Diese Daten sollen bei Bedarf ablesbar sein, aber optisch unauffälliger präsentiert werden.

TL; DR

(Too long, didn’t read: Zu lang, nicht gelesen, worum geht’s?)

Es geht mir vor allem um Code-Beispiele (ggplot2-Tricks) und die Herangehensweise: Was will ich dem Betrachter zeigen? Gebe gerne zu, dass mir dieses Denken eine ganze Weile fremd war: Ich wählte „einfach“ einen passenden Diagrammtyp aus und speiste ihn mit „den“ Daten. Sichtweise: Mein Blick des Daten-Analytikers, der seine Werkzeuge einsetzt. Es fiel und fällt mir gar nicht so leicht, den Blickwinkel des Betrachters einzunehmen und / oder zu überlegen, welche Informationen besonders wichtig oder interessant sind, und die Mühe aufzuwenden, die Darstellung dahingehend zu verfeinern.

Als Video:

Hinweis: Durch den Fokus auf Code / Umsetzung verzichte ich auf eine inhaltliche Diskussion zum Länderfinanzausgleich – das können andere besser.

Daten laden und aufbereiten

Die Daten stammen von der Wikipedia-Seite zum Länderfinanzausgleich. Ich habe sie zunächst in Excel übertragen, dort leicht bearbeitet (Fußnoten etc.) und dann in R eingelesen.

library(tidyverse)
library(DT)
library(plotly)

daten <- readxl::read_excel("Länderfinanzausgleich.xlsx",
   na = c("", "NA", "./.")) daten <- daten %>% 
   mutate(Volumen = str_remove_all(Volumen, "± ")) %>% 
   mutate_if(is.character, str_remove_all, "\.") %>% 
   mutate_if(is.character, as.numeric)
datatable(daten)

Mit DT::datatable() erhält man im HTML-Format (R Markdown) eine interaktive Tabelle, d. h. man kann sie sortieren und filtern.

Im Original weisen sie dieses Format auf:

Das Original-Datenformat für den Länderfinanzausgleich: Eine Spalte = ein Bundesland

Die Daten umfassen den Zeitraum von 1950 bis 2018. Jedes Bundesland steht in einer Spalte. So sind sie schwierig zu visualisieren, da man nicht alle Bundesländer mit einer Variable ansprechen kann. Tidy data im Sinne des tidyverse (R-Meta-Paket und Konzept zur möglichst reibungslosen Zusammenarbeit von R-Paketen) fordert, dass jede Spalte eine Variable abbilden soll und jede Zeile eine Beobachtung. Mit dem tidyr-Paket und der gather-Funktion können wir unsere Daten in das tidy-Format umformen:

daten <- daten %>% 
   gather(-Jahr, key = Bundesland, value = Finanzausgleich) %>% 
   arrange(Jahr, desc(Finanzausgleich)) %>% 
   mutate(Typ = ifelse(Finanzausgleich < 0, "Geberland", "Empfängerland")) %>% 
   mutate(Typ = fct_rev(Typ)) %>% 
   mutate(Finanzausgleich = -Finanzausgleich) %>% 
   filter(Bundesland != "Volumen") %>% 
   mutate(Bundesland = factor(Bundesland))
 datatable(daten)
Länderfinanzausgleich im tidy-data-Format: Eine Spalte = eine Variable

Nun haben wir das Bundesland als Variable sowie den zugehörigen Wert, hier als „Finanzausgleich“ bezeichnet. Zusätzlich unterscheide ich im Typ zwischen Geber- und Empfängerland. Die ostdeutschen Bundesländer sowie Berlin wurden erst 1995 in den Länderfinanzausgleich aufgenommen und weisen vorher Fehlwerte auf.

Erstes Diagramm: Balken

Nun können wir leicht unser erstes Diagramm erstellen. Wir betrachten die aktuellsten Daten aus dem Jahr 2018.

p <- daten %>% 
   filter(Jahr == 2018) %>% 
   mutate(Bundesland = reorder(Bundesland, Finanzausgleich)) %>% 
   ggplot(aes(x = Bundesland, y = Finanzausgleich)) +
   geom_col(width = 0.6) +
   labs(y = "Finanzausgleich in Mio. Euro",
        title = "Länderfinanzausgleich 2018",
        caption = "Datenquelle: https://de.wikipedia.org/wiki/Länderfinanzausgleich#Finanzvolumen") +
   coord_flip() +
   theme(text = element_text(size = 14))
 p

Durch Verwendung des pipe operators %>% muss man den Datensatz selbst nicht verändern: Filtern, Aufbereiten und Grafikerstellung erfolgen in einer Kette, ohne Zwischenobjekte.

Länderfinanzausgleich 2018: Erstes Balkendiagramm

Die Achsen habe ich vertauscht, sodass die Bundesländer auf der y-Achse stehen und besser lesbar sind.

Erste Verbesserung: Die Unterscheidung zwischen Geber- und Nehmerländern ist zwar anhand der Null-Linie möglich, kann aber optisch unterstützt werden:

 daten %>% 
   filter(Jahr == 2018) %>% 
   mutate(Bundesland = reorder(Bundesland, Finanzausgleich)) %>% 
   ggplot(aes(x = Bundesland, y = Finanzausgleich, fill = Typ)) +
   geom_col(width = 0.6) +
   labs(x = "",
        y = "Finanzausgleich in Mio. Euro",
        title = "Länderfinanzausgleich 2018",
        caption = "Datenquelle: https://de.wikipedia.org/wiki/Länderfinanzausgleich#Finanzvolumen") +
   coord_flip() +
   scale_fill_manual(name = "", values = c("steelblue1", "peachpuff4")) +
   theme_bw() +
   theme(legend.position = "top",
         text = element_text(size = 14))

Mit der fill-Ästhetik wird nach dem Typ (Geber- oder Empfängerland) eingefärbt; mit der scale_fill_manual-Funktion werden benutzerdefinierte Farben zugewiesen; name = „“ unterdrückt die Legenden-Überschrift.

Länderfinanzausgleich 2018 mit farblicher Unterscheidung zwischen Geber- und Nehmer-Ländern

Hier habe ich noch das Theme verändert: theme_bw() statt des Standards theme_grey(). (Das Theme kann man ebenfalls benutzerdefiniert anpassen – dann sieht es nicht so nach „typisch ggplot2“ aus – das ist allerdings nicht das Anliegen dieses Beitrags.)

Dies zeigt nur die aktuellsten Daten. Nehmen wir das Jahr 1995 zum Vergleich (auf den Code verzichte ich hier; wie oben, nur das Jahr wird an zwei Stellen geändert: in der Filterfunktion und im Diagrammtitel):

Länderfinanzausgleich 1995

Auffällig ist vor allem Nordrhein-Westfalen (NRW): 1995 an der Spitze (stärkstes Geberland), liegt es 2018 nur noch an drittletzter Stelle. Bayern hingegen hatte sich 1995 zwar bereits zum Geberland emporgearbeitet (das war in den 1980er Jahren noch anders), lag aber noch hinter NRW und Baden-Württemberg.

Wie können wir diese Veränderung deutlich kennzeichnen? Zunächst zeigen wir zwei Jahre in einem Diagramm:

Bundeslaender <- daten %>% 
   filter(Jahr == 2018) %>%
   mutate(Bundesland = reorder(Bundesland, Finanzausgleich)) %>% 
   pull(Bundesland)

daten <- daten %>% 
   mutate(Bundesland = fct_relevel(Bundesland, as.character(Bundeslaender)))

daten %>% 
   filter(Jahr == 1995 | Jahr == 2018) %>% 
   ggplot(aes(x = Bundesland, y = Finanzausgleich, fill = Typ)) +
   geom_col(width = 0.6) +
   labs(x = "",
        y = "Finanzausgleich in Mio. Euro",
        title = "Länderfinanzausgleich 1995 vs. 2018",
        subtitle = "Sortierung absteigend für 2018",
        caption = "Datenquelle: https://de.wikipedia.org/wiki/Länderfinanzausgleich#Finanzvolumen") +
   coord_flip() +
   facet_wrap(~ Jahr) +
   scale_fill_manual(name = "", values = c("steelblue1", "peachpuff4")) +
   theme_bw() +
   theme(legend.position = "top",
         text = element_text(size = 14))

Das Diagramm für zwei Länder selbst ist einfach zu erstellen: Mit einem Aufruf, wie bisher, nur ergänzt um die facet_wrap()-Funktion, die nach Jahren aufteilt; oben wurde nach 1995 oder 2018 gefiltert.

Ein wenig mehr Mühe kostete mich die Reihenfolge. Ich wollte absteigend nach 2018 sortieren. Das geht nun nicht mehr so einfach wie bisher, da ich nicht die gefilterten Daten umsortieren kann – sie enthalten ja auch das Jahr 1995. Daher habe ich zunächst einen separaten factor erstellt, der die Bundesländer in der 2018er-Reihenfolge enthält. Diese Reihenfolge wird dann im Datensatz per factor levels codiert (fct_relevel aus dem forcats-Paket). Wer eine kürzere oder elegantere Lösung kennt, mag mir die gerne im Kommentar zeigen.

Länderfinanzausgleich 2018 im Vergleich zu 1995: Diagramm mit facet_wrap()

ggplot2 sorgt für identische Skalenbereiche, sodass die Volumina 1995 und 2018 nun besser vergleichbar sind.

NRW fällt bereits etwas auf, mit dem blauen Geber-Balken links 1995 und dem bräunlichen Empfänger-Balken rechts 2018. Nun könnte man im Text / Vortrag auf die beiden Bundesländer Bayern und NRW näher eingehen. Erleichtern wir dem Betrachter, unseren Gedanken zu folgen, indem wir diese beiden Bundesländer hervorheben und die restlichen Länder „unauffälliger“ machen:

daten %>% 
   filter(Jahr == 2018 | Jahr == 1995) %>% 
   ggplot(aes(x = Bundesland, y = Finanzausgleich,
              fill = ifelse(Bundesland == "Nordrhein-Westfalen" | Bundesland == "Bayern", "Ja", "Nein"))) +
   geom_col(width = 0.6) +
   labs(x = "",
        y = "Finanzausgleich in Mio. Euro",
        title = "Länderfinanzausgleich 1995 vs. 2018",
        subtitle = "Hervorgehoben: Bayern und Nordrhein-Westfalen",
        caption = "Datenquelle: https://de.wikipedia.org/wiki/Länderfinanzausgleich#Finanzvolumen") +
   coord_flip() +
   facet_wrap(~ Jahr) +
   scale_fill_manual(name = "", values = c("Ja" = "steelblue1", "Nein" = "grey70")) +
   theme_bw() +
   theme(legend.position = "none",
         text = element_text(size = 14))

Die farbliche Hervorhebung erfolgt in zwei Schritten. Oben wird die fill-Ästhetik definiert. „Ja“ und „Nein“ sind nur Codierungen, die nicht im Diagramm auftauchen, aber im scale_fill_manual-Aufruf aufgegriffen werden, um die Farben zuzuordnen.

Länderfinanzausgleich 2018 vs. 1995 mit Hervorhebung von Bayern und Nordrhein-Westfalen

Perfekt ist das noch nicht: Es macht Mühe, vom farbigen Balken nach links zu gehen, die Zeile zu halten und das zugehörige Bundesland abzulesen. Den Betrachter hier weiter zu unterstützen, erfordert ein wenig mehr Arbeit:

farben <- ifelse(levels(Bundeslaender) == "Nordrhein-Westfalen" | levels(Bundeslaender) == "Bayern",
                  "steelblue1", "grey30")
fett <- ifelse(farben == "steelblue1", "bold", "plain")

p <- daten %>% 
   filter(Jahr == 2018 | Jahr == 1995) %>% 
   ggplot(aes(x = Bundesland, y = Finanzausgleich,
              fill = ifelse(Bundesland == "Nordrhein-Westfalen" | Bundesland == "Bayern", "Ja", "Nein"))) +
   geom_col(width = 0.6) +
   labs(x = "",
        y = "Finanzausgleich in Mio. Euro",
        title = "Länderfinanzausgleich 1995 vs. 2018",
        subtitle = "Bayerns Aufstieg und Nordrhein-Westfalens Abstieg",
        caption = "Datenquelle: https://de.wikipedia.org/wiki/Länderfinanzausgleich#Finanzvolumen") +
   coord_flip() +
   facet_wrap(~ Jahr) +
   scale_fill_manual(name = "", values = c("Ja" = "steelblue1", "Nein" = "grey70")) +
   theme_bw() +
   theme(legend.position = "none",
         text = element_text(size = 14),
         axis.text.y = element_text(color = farben, face = fett))
 p 

Die y-Achsen-Beschriftung können wir über die theme-Einstellungen ansteuern: theme(axis.text.y). Dort werden der element_text()-Funktion zwei Vektoren übergeben, die Farben und Fett- bzw. Normalschrift (bold / plain) enthalten. Das sieht so aus:

Länderfinanzausgleich 2018 vs. 1995 mit Hervorhebung von Bayern und Nordrhein-Westfalen sowohl im Diagramm als auch in der y-Achsen-Beschriftung

Nun sind die beiden Bundesländer, auf die wir die Aufmerksamkeit des Betrachters lenken wollen, auch in der y-Achsen-Beschriftung markiert.

Eine weitere Verbesserung funktioniert nur im HTML-Format: Interaktivität mit Mouse-Over-Effekt – kein Problem dank ggplotly. Hier als statisches Bild (mit eingeblendeter Info), da die Integration in einen WordPress-Blog nicht so einfach ist. Unter diesem Link gibt es die interaktive Version (Dokument erstellt mit R Markdown, enthält aufklappbaren Code).

Länderfinanzausgleich 2018 vs. 1995: Mit plotly::ggplotly gibt es (fast) ohne Zusatzaufwand genauere Infos per Mouse-Over

Ich hoffe, es waren nützliche Anregungen dabei. Ein Balkendiagramm ist schnell erstellt – zumindest wenn die Daten in einem brauchbaren Format vorliegen. Das Diagramm zu verfeinern, kann wesentlich mehr Zeit in Anspruch nehmen.

Natürlich kann man über den Diagrammtyp diskutieren. Punkte statt der Balken hätten hier Vorteile: Null-Werte sind ebenso gut erkennbar wie stark positive und stark negative Werte und man braucht weniger Fläche für die gleiche Information (ink-to-information-ratio). Ich habe mich für die Balken entschieden, weil ich diese in einer Animation weiterverarbeiten wollte. Darin wird der Länderfinanzausgleich als GIF dargestellt, mit einem Bild pro Jahr.

Hier noch Literatur-Tipps. Das Buch von Claus Wilke (Fundamentals of Data Visualization, rechts) ist neu.

Freue mich über Kommentare!