Herzlich Willkommen zum dritten und letzten Teil dieses R-Crashkurses für Journalist*innen. Da du nun einige Befehle und Funktionen kennst und eine grundsätzliche Orientierung hast, wie Programmieren in R funktioniert, wollen wir dein R-Game auf das nächste Level heben.

In diesem Teil zeige ich dir, wie ich R bei meiner Arbeit im Interaktiv-Team der Berliner Morgenpost benutze. Oder anders gesagt: Ich zeige dir die Funktionen, mit denen ich und wohl auch die meisten anderen R-nutzenden Datenjournalist*innen arbeiten, wenn sie Datensätze analysieren und visualisieren. All diese Funktionen kommen aus dem Zusatzpaket Tidyverse. Das Tidyverse ist eine Sammlung von R-Paketen, die speziell für die Verarbeitung und Analyse von Daten entwickelt wurden. Alle Pakete teilen eine grundlegende Philosophie was ihr Design, ihre Grammatik und ihre Datenstruktur anbelangt. Die drei wichtigsten Pakete dieser Sammlung sind tidyr, dplyr und ggplot2.

Mit tidyr können wir Datensätze in ein ganz bestimmtes Format, das sogenannte „tidy data“-Format, transformieren. Auf dieses Format wiederum können wir die Analysefunktionen von dplyr und die Grafikwerkzeuge von ggplot2 anwenden.

Wie im vorherigen Teil habe ich ein R-Skript in dem Ordner bereit gestellt, den du hier herunter laden kannst. Das Skript tidyverse.R ist ebenso wie dieses Tutorial in drei Kapitel unterteilt: Das „tidy data“-FormatVerarbeiten und analysieren mit dplyr und Visualisieren mit ggplot2. Um die Funktionen des Tidyverse anwenden zu können, ist es vor allem wichtig, seine Prinzipien und Grammatik zu verstehen. Jedes Kapitel beginnt deshalb zunächst mit ein wenig Theorie und endet damit, dass wir die Funktionen praktisch anwenden.

 

 

Das „tidy data“-Format

Alle Funktionen aus dem Tidyverse sind darauf spezialisiert, mit einem ganz bestimmten Datenformat zu arbeiten: Dem „tidy data“-Format, auch „long data“-Format genannt. Die erste Grafik zeigt das Gegenteil des „tidy data“-Formats, das wir als „wide data“-Format bezeichnen. Es handelt sich um eine Tabelle mit vier Spalten und vier Zeilen. In der ersten Zeile sind die Spaltenüberschriften geschrieben. Die erste Spalte enthält Namen dreier verschiedener Personen. Jede weitere Spalte verrät uns, wie viele Stücke Obst die jeweilige Person hat (ja, Tomaten sind Obst. Wir müssen damit leben).

Dieses Tabellenformat kommt uns bekannt und gut strukturiert vor. Im „wide data“-Format gehören alle Werte einer Zeile zusammen, jede Beobachtung hat eine eigene Spalte. Optisch „wide“, also breit, ist dieses Format hier nur, weil die einzelnen Zellen breiter sind als hoch. Warum diese Struktur trotzdem breit ist, zeigt der Vergleich mit denselben Daten im „tidy data“- oder „long data“-Format.

Denn hier ist die Tabelle schmaler, dafür aber länger. Sie hat nun drei Spalten und 9 Zeilen. Die Menge Informationen ist noch die selbe, keine Daten sind verloren gegangen oder hinzugekommen. Es gibt nach wie vor eine Spalte mit den Namen. Doch anstatt pro Obstsorte eine eigene Spalte, haben wir nun eine Spalte mit der Obstsorte und eine mit der zugehörigen Anzahl.

Im „tidy data“-Format ist jede Variable in einer Spalte, jede einzelne Beobachtung ist eine Zeile und jeder Wert ist eine Zelle. Dieses Prinzip führt dazu, dass sich die Ausprägungen der ID-Spalte, also bei unserem Beispiel die Namen in der Spalte „Person“, für jede vorkommende Variable aus „Fruits“ wiederholen muss. Für unser Auge ist das unübersichtlicher als das „wide data“-Format, denn nun können wir nicht mehr auf einen Blick sehen, welche Person von welcher Obstsorte im Vergleich das meiste hat.

Für die Analyse-Werkzeuge vom Paket dplyr ist dieses Format aber perfekt. Und auch für uns ist es beim Programmieren ganz angenehmer, statt etlicher verschiedener Spalten nur eine Hand voll managen zu müssen. Dazu aber im nächsten Kapitel mehr. An diesem Punkt akzeptieren wir erstmal, dass wir Daten am liebsten im „tidy data“-Format haben.

Daten kommen allerdings selten in diesem Format bei uns an. Und manchmal möchte man die Daten auch vom „tidy“ ins „wide data“-Format transformieren. Das Paket tidyr hat für beide Transformationen die passenden Funktionen. Mit gather() können wir einen Datensatz vom „wide“ in das „long“-Format übertragen, mit spread() können wir die Prozedur rückgängig machen.

Die Funktion gather() braucht dafür nur die Angabe, welchen Teil des Datensatzes du transformieren möchtest, und wie die neuen Spalten heißen sollen. Der Befehl, um aus dem Fruits-Beispieldatensatz im „wide data“-Format die Tabelle aus der zweiten Grafik zu machen, müsste so aussehen:

 

# R
gather(data, key="Fruits", value="Amount", 2:4)

 

Das Argument „key“ steht für die Schlüsselspalte, welche die Namen oder ID’s der Spalten beinhalten wird, „value“ für die neue Spalte mit den Werten. Der Vektor 2:4 sagt gather(), dass die zu transformierenden Spalten die Spalten zwei bis vier sind.

Die Funktion spread() hingegen, benötigt nur die Namen der beiden Spalten, die sie in die Breite transformieren soll. Um aus der „tidy data“-Tabelle wieder eine „wide data“-Tabelle zu machen, würden wir folgende Zeile Code brauchen:

 

# R
spread(data, 2:3)

 

Bevor du nun das Skript tidyverse.R öffnest und unsere Wahldaten aus dem Projektordner in das „tidy data“-Format überführst, will ich dir noch schnell erklären, was Piping ist.

Ein weiteres Paket, dass sich mit tidyverse lädt, ist magrittr. Leider wird es im Tidyverse nicht in seinem gesamten Umfang geladen, weshalb ich das Paket immer noch einzeln dazu lade. Der Name des Pakets spielt auf den belgischen Maler René Magritte an. Von ihm stammt unter anderem das Kunstwerk „La trahison des images“, auf dem eine Pfeife und auf Französisch der Satz „Dies ist keine Pfeife“ zu sehen sind. Das nach ihm benannte R-Paket enthält passenderweise den Pipe-Operator %>%, eine super praktische Hilfsfunktion, um Funktionen miteinander zu verknüpfen.

Die Idee des Pipe-Operators: Statt Funktionen zu verschachteln, um sie in einem Rutsch auf den gleichen Datensatz anzuwenden, werden die Funktionen nacheinander als eine Funktionen-Kette geschrieben. Das nimmt zwar mehr Platz ein, gibt dem Ganzen aber mehr Struktur.

Auch, wenn das Zeichen %>% etwas ungewohnt ist: Piping macht unsere Anweisungen übersichtlicher.

 

# R
function3(function2(function1(data))) # drei Funktionen nacheinander auf den Datensatz anwenden, ohne piping
data %>% function1() %>% function2() %>% function3() # drei Funktionen nacheinander auf den Datensatz anwenden, mit piping

 

Führen wir den Beispielcode mit echten Funktionen aus, bekommen wir zwar das Ergebnis der Anweisung ausgegeben, doch der Datensatz „data“ bleibt unverändert. Wollen wir ihn mit dem Ergebnis der Anweisungen überschreiben, können wir das auf zwei Arten machen:

 

# R
data <- data %>% function1() %>% function2() %>% function3() 
data %<>% function1() %>% function2() %>% function3()

 

Der abgewandelte Pipe-Operator %<>% funktioniert genau, wie der normale Pipe-Operator, nur, dass er zusätzlich die Funktion des Zuweisungspfeils übernimmt.

Nun aber ohne weitere Verzögerungen ab ins Skript! Als erstes laden wir alle nötigen Pakete und lesen den Datensatz ein. Die Funktionen dafür haben wir im vorherigen Teil des Tutorials kennengelernt.

 

# R
# Crashkurs: Programmieren in R für Journalist*innen | Teil 3: Das Tidyverse
# ---------------------------------------------------------------------------

# install.packages("needs")
# library(needs)
needs(tidyverse, magrittr, rgdal) # Das Paket tidyverse enthält die Pakete tidyr, dplyr und ggplot2
# magrittr laden wir, damit wir vollen Zugriff auf die Funktionen der Magrittr-Pipe haben, die ich im Tutorial erklärt habe
# rgdal brauchen wir, um später die Shape-Datei zu laden

btw17 <- read.csv("btw17.csv") # Datensatz einlesen

 

Sieh dir den Datensatz ruhig nochmal mit der Funktion View() an, bevor wir ihn mit gather ins „tidy data“-Format überführen. Er beinhaltet die Bundestagswahlergebnisse je Partei und Wahlkreis, außerdem Informationen über die Anzahl Wahlberechtigte und abgegebene Stimmen. An ihm wollen wir die Funktionen des Tidyverse üben.

Gather braucht von uns wie gesagt drei Informationen: Die Namen der neuen Spalten und die Positionen der Spalten, die wir transformieren wollen. In unserem Fall sind das die Spalten mit den Parteiergebnissen. Eine neue Spalte „party“ soll die Namen der Parteien enthalten, die Spalte „value“ die Stimmenanzahl. Damit du beide Formate direkt vergleichen kannst, speichern wir den tidy Datensatz in einer neuen Variable btw17_tidy. Sieh ihn dir gerne genau über den View-Befehl an. Hat alles richtig geklappt, oder musst du noch irgendwas anpassen?

 

# R
## Das "tidy data"-Format
btw17_tidy <- btw17 %>% gather(key=party, value=value, 8:13) # btw17 im "tidy data"-Format
btw17_tidy %>% spread(party, value) # und wieder im "wide"-Format

 

Mit spread und den neuen Spaltennamen kannst du den Datensatz wieder im „wide“-Format darstellen. Aber eigentlich wollen wir den Datensatz im „tidy data“-Format behalten, um ihn im nächsten Kapitel mit den Werkzeugen von dplyr zu analysieren.

 

Verarbeiten und analysieren mit dplyr

Als kleine Aufwärmübung wollen wir fix die Wahlbeteiligung je Wahlkreis berechnen. Wir erhalten sie, indem wir die abgegebenen Zweitstimmen durch die Anzahl Wahlberechtigter teilen. Wir machen das an dieser Stelle über den Weg, den wir aus den R-Grundlagen kennen. Die dplyr-Funktion, um neue Spalten zu erstellen, zeige ich dir gleich.

 

# R
btw17_tidy$Wahlbeteiligung <- btw17_tidy$Wähler.Zweitstimmen/btw17_tidy$Wahlberechtigte.Zweitstimmen # Wahlbeteiligung berechnen

 

Nun zeige ich dir ein paar der Funktionen von dplyr, mit denen wir ganz leicht Infos aus dem Datensatz holen können.

 

summarize

Wie es der Name schon verrät, können wir den Datensatz mit summarize() zusammenfassen. Beispielsweise auf die Summe einer Spalte oder auch den höchsten und niedrigsten Wert. Dazu pipen wir summarize() an den Datensatz und geben der Funktion die Information, welche Funktion auf welche Spalte angewendet werden soll. Optional können wir dem Ergebnis auch einen Ergebnisnamen mitgeben.

Was bei summarize auf der Strecke bleibt, sind die Informationen aus den anderen Spalten. Welcher Wahlkreis hatte denn die meiste oder die geringste Wahlbeteiligung, für welche Partei wurde das höchste Wahlergebnis abgegeben? Um das herauszufinden, eignet sich summarize nicht.

 

# R
# summarize
btw17_tidy %>% summarize(max(Wahlbeteiligung)) # Was ist die maximale Wahlbeteiligung gewesen?
# Wir können mit summarize auch mehrere Zusammenfassungen gleichzeitig machen
btw17_tidy %>% summarize(max = max(Wahlbeteiligung), min = min(Wahlbeteiligung), median = median(Wahlbeteiligung))

 

 

mutate

Mit summarize fassen wir Spalten zusammen, mit mutate verändern wir sie, ohne, dass Informationen verloren gehen. Diese Funktion lässt uns auch neue Spalten zu einem Datensatz hinzufügen. Das funktioniert nach demselben Prinzip wie bei summarize.

Die erste Zeile Code kreiert die Spalte value_rel, die uns das relative Wahlergebnis je Partei und je Wahlbezirk verrät. Sie ist das Ergebnis der Division von den Spalten „value“ und „Gültige.Zweitstimmen“.

Führst du die Zeile aus, bekommst du das Ergebnis zwar in der Konsole angezeigt, doch da wir nur normal pipen, verändern wir den Original-Datensatz nicht. Wenn wir die neue Spalte dem Datensatz dauerhaft hinzufügen wollen, können wir den erweiterten Pipe-Operator %<>% benutzen.

 

# R
# mutate
btw17_tidy %>% mutate(value_rel = value / Gültige.Zweitstimmen) # eine neue Spalte erstellen
btw17_tidy %<>% mutate(value_rel = value / Gültige.Zweitstimmen) # Die Veränderung am Datensatz abspeichern. Du kannst mit View() überprüfen, ob es geklappt hat 😉

 

 

arrange & slice
Die Funktion arrange() macht eigentlich nichts anderes, als den Datensatz nach einem Merkmal zu sortieren. Das passiert automatisch aufsteigend. Wenn du absteigend sortieren willst, musst du noch die Funktion desc() benutzen.

Mit slice() kannst du ein Ergebnis abschneiden. Kombinieren wir die beiden Funktionen arrange() und slice(), können wir ganz leicht Ranglisten zusammenstellen. Beispielsweise zeigt dir die zweite Zeile Code die drei Zeilen mit den höchsten Wahlergebnissen. Bei allen drei handelt es sich um Ergebnisse der CDU.

 

# R
# arrange & slice
btw17_tidy %>% arrange(value_rel) # aufsteigend nach value_rel sortieren
btw17_tidy %>% arrange(desc(value_rel)) %>% slice(1:3) # absteigend sortieren und nur die ersten drei Zeilen zeigen

 

 

group_by
Mit group_by() können wir Funktionen nach Gruppen getrennt anwenden. Die Grafik zeigt, wie wir mit group_by() dieselbe Zusammenfassung von eben, diesmal jeweils pro Person, berechnen können.

Beispielsweise wollen wir nun nicht einfach das höchste Wahlergebnis insgesamt wissen, sondern das höchste Wahlergebnis je Partei. Dafür gruppieren wir erst nach Partei und wenden anschließend die Funktion summarize() an. In Kombination mit arrange() sortieren wir das Ergebnis absteigend.

 

# R
# group_by
# Eine Funktion nach Gruppen anwenden
btw17_tidy %>% group_by(party) %>% summarize(max = max(value_rel)) %>% arrange(desc(max))

 

Die Funktion group_by() kann auch nach mehreren Spalten gruppieren. Dafür musst du die Namen der Spalten als Vektor übergeben.

 

 

filter
Praktisch ist auch die Funktion filter(). Mit ihr können wir Teile des Datensatzes ausfiltern, bevor wir eine Funktion anwenden.

Zum Filtern können wir die Funktionen der logischen Vergleiche nutzen, die wir aus den Grundlagen kennen. Im Skript filtere ich den Datensatz nach allen Zeilen, bei denen das relative Wahlergebnis die 5%-Hürde übersteigt. Wir können aber auch nach Namen filtern. Beispielsweise, wenn wir nur die Ergebnisse aus einem bestimmten Bundesland betrachten wollen.

 

# R
# filter
btw17_tidy %>% filter(value_rel >= 0.05) # Zeige mir nur die Zeilen, bei denen das Ergebnis die 5%-Hürde geschafft hat.

 

 

Das waren die sechs dplyr-Funktionen, die ich am häufigsten benutze. Mit diesen wenigen Funktionen kannst du fast alle Informationen aus dem Datensatz holen. Versuch dich beispielsweise als kleine Übung an den folgenden Fragen:

  1. Füge dem Datensatz eine Spalte hinzu, welche den Anteil Nichtwähler je Wahlkreis zeigt.
  2. Was war das Gesamtergebnis der Bundestagswahl?
  3. In welchem Bundesland hat die FDP ihr bestes Gesamtergebnis geholt? Gemeint ist nicht das Bundesland, in dem der Wahlkreis mit dem höchsten Ergebnis liegt 😉

Du kommst bei einem Punkt nicht weiter? Das ist nicht weiter schlimm, du hast das Tidyverse ja auch gerade erst kennengelernt! Meine Lösungen findest du ganz am Ende des Skripts, nach der Visualisierung mit ggplot2.

 

Visualisieren mit ggplot2

Das Grafikpaket des Tidyverse ist ggplot2. Das Paket ist bei Datenjournalist*innen und Statistiker*innen gleichermaßen beliebt, weil man mit seinen Funktionen veröffentlichungsfertige Grafiken direkt aus dem Skript erzeugen kann. Die Wahlkarten auf dem Screenshot habe ich beispielsweise in der Wahlnacht direkt aus R erzeugt.

Ähnlich wie bei dplyr werden dafür Funktionen an einander gesteckt, welche die Grafik Stück für Stück erstellen und stylen. Je mehr du stylen willst, desto mehr Funktionen musst du der Kette hinzufügen. Außerdem ist fast alles möglich: Du kannst Farben manuell zuweisen, einen Farbgradienten erzeugen oder nur die Spalte festlegen, nach der ggplot dann mit eigenen Farbskalen einfärbt. Du kannst die Schriftart der Beschriftung ändern, eine gestrichelte Linie einzeichnen oder sogar ein eigenes Style-Theme schreiben, dass du immer wieder verwenden kannst. Je genauer deine Vorstellung von dem Resultat ist, desto mehr wirst du rumgooglen müssen.

Deshalb ist ggplot2 am Anfang auch recht schwierig und fühlt sich unübersichtlich an, obwohl es eigentlich extrem sinnvoll strukturiert ist.

Die gute Nachricht: Das Paket hat eine extrem steile Lernkurve. Learning by doing ist die Devise! In diesem Crashkurs zeige ich dir deswegen nur ganz basic, wie ggplot2 strukturiert ist. Für mehr Infos kannst du hier die Dokumentation anschauen oder auch diese Master-Liste der Top 50 ggplot2-Visualisierungen von Selva Prabhakaran.

ggplot()

Eine Grafik mit ggplot2 beginnt immer mit der Initialfunktion ggplot(). Diese sagt noch nichts über den Grafiktypen aus, den wir erstellen wollen. Die Initialfunktion ist sozusagen nur das Aufstellen der Malerstaffelei. Geben wir ihr bereits Informationen dazu mit, welchen Datensatz wir visualisieren wollen und welche Spalten auf der x- und y-Achse des Plots dargestellt werden sollen, wird sich jede weitere Funktion in der Kette diese Argumente aus der Initialfunktion holen.

geom_xxx

Was bei dplyr der Pipe-Operator ist, ist bei ggplot2 ein simples Pluszeichen. Mit ihm verbinden wir die Initialfunktion ggplot() mit der Grafiktyp-Funktion. ggplot hat eine Hand voll von Grafiktyp-Funktionen. Sie beginnen alle mit „geom“, gefolgt von einem Unterstrich und dann dem Begriff, der für den Grafiktypen steht. Mit geom_point können wir Punktewolken erstellen, mit geom_line Liniendiagramme. Wir können diese beiden Funktionen sogar kombinieren, um die Punkte mit einer Linie zu verbinden.

Die Crux an diesen Grafikfunktionen: Je nachdem, welche wir wählen, müssen wir auf unterschiedliche Benennungen der gefühlt gleichen Dinge achten. Ein kleines Beispiel: Die Farbe einer Linie können wir mit dem Argument color ändern, bei einem Punkt ändert dieses Argument allerdings nur die Farbe des Umrisses. Die Füllung wird mit fill angepasst. Genauso wichtig ist die Platzierung der Argumente: Setzt du color innerhalb der Klammern der Ästhetik-Funktion aes(), färbt ggplot zwar nach den Gruppen in der Spalte, allerdings mit der eigenen Farbskala ein. Setzt du color außerhalb der Klammern, erwartet ggplot eine Farbe oder einen Farbvektor. Wenn du etwas an deinem Plot ändern willst, achte beim googlen nach einer Lösung also immer darauf, dass du auch den richtigen Grafiktypen mit eingibst und ob wirklich genau das gemeint ist, was du meinst. Ich habe auch die Erfahrung gemacht, dass man bei StackOverflow sehr schnell Antworten auf seine Fragen bekommt. 

aes

Die Funktion aes() liegt innerhalb von ggplot() oder auch geom_xxx() und ist der Platz für alle Funktionen, mit denen wir einstellen, welches Merkmal durch welche Dimension der Grafik dargestellt wird. Also: Was kommt auf die x-, was auf die y-Achse, nach welcher Variable wird eingefärbt, wie groß oder dick sollen die Punkte oder Linien sein?

 

Als Beispiel zeige ich dir im Skript, wie wir aus den Bundestagswahlergebnissen ein Balkendiagramm erstellen können und wie es aussieht, wenn wir ein paar Anpassungen daran vornehmen. Viele Argumente und Styling-Funktionen von ggplot2 kenne ich mittlerweile auswendig, aber es ist ganz normal, immer mal wieder nachschauen oder rumgooglen zu müssen.

In geom_bar müssen wir darauf achten, stat = „identity“ zu setzen. Über die Hilfefunktion kannst du sehen, dass geom_bar das Argument stat standardmäßig auf „count“ setzt. Die Balkenhöhe wäre dann die Häufigkeit, mit der die Parteien im Datensatz vorkommen. Das Argument „identity“ weist ggplot dazu an, stattdessen den Wert aus einer Spalte zu nehmen.

 

# R
## Visualisieren mit ggplot2
plot_data <- btw17_tidy %>% group_by(party) %>% summarize(result = sum(value)/sum(Gültige.Zweitstimmen))

# Dotchart
ggplot(plot_data, aes(x = party, y=result)) + 
  geom_point()

# Barchart
ggplot(plot_data, aes(x = party, y=result)) + 
  geom_bar(stat = "identity")

# Barchart absteigend sortieren mit der Funktion reorder
?reorder
ggplot(plot_data, aes(x = reorder(party, -result), y=result)) + 
  geom_bar(stat = "identity") 

# Barchart mit angepasster Beschriftung
ggplot(plot_data, aes(x = reorder(party, -result), y=result)) + 
  geom_bar(stat = "identity") +
  labs(title="Die Bundestagswahl",  # Grafik-Titel
     subtitle="Eine ggplot2-Grafik",  # Untertitel
     caption="Quelle: Bundeswahlleiter", # Beschreibung
     x="Partei", y="Ergebnis in %") + # Label für die Achsen
  scale_x_discrete(labels = c("Union", "SPD", "AfD", "FDP", "Linke", "Grüne")) # Vektor mit neuen Label-Namen

 

Kannst du noch eigene Veränderungen hinzufügen? Am meisten lernst du, wenn du einfach an den Grafiken rumdokterst und sie Stück für Stück versuchst, sie selbst anzupassen. Probiere dich auch an den Beispielen, die du in der Master-Liste der Top 50 ggplot2-Visualisierungen von Selva Prabhakaran finden kannst!

 

Als kleinen Ausblick möchte ich dir noch gerne zeigen, wie ich diese Gewinner-Karte der Bundestagswahl mit R erstellt habe. Dafür müssen wir die Daten mit den Geodaten verknüpfen und je Partei einen Farbgradienten vergeben.

Wenn du ggplot2 gerade zum ersten Mal benutzt hast, kann das auf den ersten Blick sehr komplex wirken. Wenn du meinem Code an dieser Stelle nicht richtig folgen kannst, gehe nochmal einen Schritt zurück. Vielleicht arbeitest du erstmal ein bisschen mit den anderen Grafiken weiter, und siehst dir diesen Part zu einem späteren Zeitpunkt an, wenn du schon etwas sicherer in R bist. Aber ich ermuntere dich trotzdem weiterzulesen – mindestens, um zu sehen, was mit ggplot2 alles möglich ist.

Die Karte, die wir erstellen wollen heißt Choroplethen-Karte. Dabei werden die Flächen nach einem Wert eingefärbt. Dazu benutzen wir die Funktion geom_polygon(). Als Polygone werden die Flächen bezeichnet, in unserem Fall die Flächen der deutschen Wahlkreise. Die Grafik zu erstellen, ist der leichteste Part an diesem Vorhaben. Die einzige Herausforderung ist es, die Daten in die richtige Form zu bringen.

Auch Geodaten müssen für ggplot2 in ein „tidy data“-Format gebracht werden. Das geht ganz leicht mit der Funktion tidy aus dem Paket broom. Im Skript speichern wir den Geodatensatz einmal ganz normal ab und einmal im „tidy“-Format, damit du die beiden Formate vergleichen kannst. Im normalen Format hat der Datensatz mehrere Teile, auf die wir mit dem @-Zeichen zugreifen können. Beispielsweise einen tabellarischen Datensatz mit Informationen zu den einzelnen Polygonen, und einen Teil mit den geografischen Informationen der Polygone. Im „tidy“-Format hat jedes Koordinatenpaar eine Zeile. Die Spalte „id“ gibt Auskunft darüber, welche Koordinaten zu einem Polygon, also einem Wahlkreis, zusammengehören. Die Spalte „order“ gibt an, in welcher Reihenfolge die Koordinatenpaare miteinander verbunden werden müssen, um den Umriss des Wahlkreises richtig abzubilden.

Das einzige Problem: Wenn wir einen Geodatensatz in das „tidy“-Format bringen, verliert dieser alle Informationen, die nicht relevant sind für die richtige Darstellung der Polygone. Alle Daten, die wir unter shp@data sehen können, sind im „tidy“-Datensatz nicht mehr vorhanden. Das stellt uns vor die Herausforderung, unsere Wahldaten dem richtigen Polygon zuzuordnen. Je nach Datensatz gibt es dafür mehrere Möglichkeiten. Beispielsweise kann man über den normal abgespeicherten Geodatensatz die Reihenfolge der Wahlkreise herausfinden und den Wahldatensatz dann in dieselbe überführen.

In unserem Fall haben wir Glück, dass die Reihenfolge schon die gleiche ist. Die ID der Wahlkreise im Geodatensatz ist eigentlich genau dieselbe, wie die Wahlkreisnummer in unserem Wahldatensatz. Mit dem Unterschied, dass der eine bei 0 startet und der andere bei 1. Deshalb fügen wir mit mutate() eine neue Spalte „id“ zu dem Wahldatensatz hinzu, welche die Wahlkreisnummer-1 ist. Dann können wir die beiden Datensätze mit der Funktion merge() zusammenführen.

 

# R
## Karte mit ggplot2
needs(broom) # Das Paket broom beinhaltet die Funktion tidy, mit der wir den Geodatensatz in ein Format bringen können, das gut mit ggplot2 zusammen passt
# Shapefile ohne tidy als Vergleich
shp <- readOGR("wahlkreise_small/wahlkreise_small.shp", "wahlkreise_small", stringsAsFactors=FALSE, encoding="latin1")
head(shp@data) # kleiner Ausschnitt der Daten in diesem Shapfeil
head(shp@polygons) # kleiner Ausschnitt der Polygone in diesem Shapefile

# tidy Shapefile
tidy_shp <- readOGR("wahlkreise_small/wahlkreise_small.shp", "wahlkreise_small", stringsAsFactors=FALSE, encoding="latin1") %>% # Shapefile laden
  tidy() # broom 
head(tidy_shp) # kleinen Ausschnitt der Daten ansehen

btw17_tidy %<>% mutate(id= Wkrnr-1) # Dem Datensatz eine ID-Spalte hinzufügen, die zu der vom Shapefile passt

tidy_shp <- merge(tidy_shp, btw17_tidy, by="id", all.y=T) # Über die ID Shapefile und Wahldaten zusammenführen
head(tidy_shp) # kleinen Ausschnitt der Daten ansehen

 

Dieser Teil wird, wie gesagt, nicht immer so funktionieren. Manchmal sind die Datensätze in einer ganz unterschiedlichen Reihenfolge sortiert. Dann muss man gucken, wie man garantieren kann, dass es keine falschen Zuweisungen gibt.

Als nächstes möchten wir die Farben festlegen. Das Knifflige hierbei: Für die Gewinner-Karte möchten wir nicht nur für jede Partei die richtige Grundfarbe haben. Wir möchten innerhalb jeder Partei auch einen Farbgradienten haben, der uns zeigt, wie stark das jeweilige Ergebnis im Vergleich zu den Ergebnissen in anderen Wahlkreisen ist.

Bei so vielen Extrawünschen können wir leider auf keine fertige ggplot-Funktion zurückgreifen. Deshalb erzeugen wir den Farbgradienten außerhalb von ggplot und weisen jedem Wahlergebnis den passenden Farbwert in einer neuen Datensatzspalte zu. Dafür fügen wir dem Datensatz die Spalte „fill“ hinzu, die erstmal nur mit der Farbe „Pink“ gefüllt wird.

Für jede Partei führen wir dann eine Zuweisung aus, die ich kurz an dem Beispiel der SPD erkläre:

 

# R
# Farbgradienten hinzufügen
tidy_shp$fill <- "pink" # erstmal eine Splate für die Farbe hinzufügen

tidy_shp[tidy_shp$party %in% "Sozialdemokratische.Partei.Deutschlands.Zweitstimmen",]$fill <- 
  colorRampPalette(c('#F0BAA5','#f40502'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "Sozialdemokratische.Partei.Deutschlands.Zweitstimmen",]$value_rel, breaks = 10))]

 

Der erste Teil bedeutet „Weise allen Zeilen des Datensatzes, bei denen in der Spalte party die SPD steht, den folgenden Wert für die Spalte fill zu“. Der zweite Teil bedient sich der Funktion colorRampPalette(). Diese bekommt von mir die Hexadezimalcodes der beiden Farbwerte, in denen Minimum und Maximum der SPD-Wahlergebnisse eingefärbt werden sollen. Die Zehn sagt der Funktion, dass ich insgesamt zehn verschiedene Farbtöne in der Palette haben möchte. Diese sollen abhängig von dem Wahlergebnis der SPD den Zeilen zugeordnet werden.

Führst du die Funktion aus, siehst du im Datensatz, dass alle Zeilen der SPD nun statt „pink“ einen Hexadezimalcode in der Spalte fill stehen haben.

 

# R
tidy_shp[tidy_shp$party %in% "UNION.Zweitstimmen",]$fill <-
  colorRampPalette(c('#959595','#000000'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "UNION.Zweitstimmen",]$value_rel, breaks=10))]

tidy_shp[tidy_shp$party %in% "Alternative.für.Deutschland.Zweitstimmen",]$fill <-
  colorRampPalette(c('#CEE9FF','#009de0'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "Alternative.für.Deutschland.Zweitstimmen",]$value_rel, breaks=10))]

tidy_shp[tidy_shp$party %in% "DIE.LINKE.Zweitstimmen",]$fill <-
  colorRampPalette(c('#E0A7C3','#8b1b62'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "DIE.LINKE.Zweitstimmen",]$value_rel, breaks=10))]

tidy_shp[tidy_shp$party %in% "Freie.Demokratische.Partei.Zweitstimmen",]$fill <-
  colorRampPalette(c('#FFFBD1','#feed01'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "Freie.Demokratische.Partei.Zweitstimmen",]$value_rel, breaks=10))]

tidy_shp[tidy_shp$party %in% "BÜNDNIS.90.DIE.GRÜNEN.Zweitstimmen",]$fill <-
  colorRampPalette(c('#A4D78F','#42a62a'))(10)[as.numeric(cut(tidy_shp[tidy_shp$party %in% "BÜNDNIS.90.DIE.GRÜNEN.Zweitstimmen",]$value_rel, breaks=10))]

 

Da wir nur nach den Wahlkreis-Gewinnern, also der Partei, die das höchste Ergebnis im Wahlkreis eingefahren hat, einfärben wollen, können wir alle anderen Zeilen aus dem Datensatz herausschmeißen. Das machen wir erst nach der Farbvergabe, weil der Gradient sonst falsch berechnet worden wäre. Dann erstellen wir die Karte mit ggplot. Diesmal weisen wir dem Plot sogar einen Namen zu.

 

# R
# Bis auf die Gewinner-Parteien alle Parteien aus dem Datensatz herausfiltern
tidy_shp %<>% group_by(id) %>% # Daten nach ID / Wahlkreis gruppieren
  filter(party %in% party[value %in% max(value, na.rm=T)]) %>% # dann jeden Wert rausfiltern, der nicht der Maximalwert je ID ist
  arrange(id,order)

# Karte erstellen mit ggplot
win <- ggplot(data=tidy_shp, aes(x=long, y=lat, group=group)) + # Initialisierung
  geom_polygon(aes(fill=fill), show.legend = T, size=0.2, color="white") + # Polygone plotten und nach Spalte "fill" einfärben
  scale_fill_identity() + # diese Funktion weißt das Polygon an, die Farben aus der Spalte fill zu verwenden
  theme_void() + # Achsen und Beschriftung entfernen
  coord_map() # Karte umprojezieren
win

 

Wenn wir den Namen der Grafik ansprechen, wird sie uns im Plot-Fenster angezeigt. Über den Zoom-Button kannst du sie dir genauer ansehen. Neben dem Zoom-Button findest du außerdem den Export-Button, mit dem du die Grafik in verschiedenen Formaten herunterladen kannst. Das selbe kannst du auch über Code im Skript machen:

 

# R
# Abspeichern als JPEG
jpeg(file = "gewinnerkarte.jpg", bg="transparent", width=750, height=1010, units="px", quality=75)
win
dev.off()

 

Damit sind wir auch schon am Ende des Crashkurses angelangt. Ich hoffe, dass du Spaß hattest und etwas daraus mitnehmen kannst! Wie gesagt, kann der Kurs nur ein Start in die Programmierung mit R sein. Um es richtig beherrschen zu können, musst du es benutzen. Der Rest kommt dann von alleine 🙂

Wenn du Anmerkungen, Feedback oder Fragen hast, kannst du mich gerne kontaktieren, beispielsweise über Twitter. Wenn du Probleme oder offene Fragen zum Tidyverse hast, die dir das Internet nicht richtig beantworten kann, dann hau am besten Journocode, die Datenjournalismus-Initiative, zu der ich auch gehöre, via Slack, Twitter oder Facebook an. Wir versuchen gerne, dir weiterzuhelfen!

Happy coding!

 

Bildquelle La trahison des images: Wikipedia

Ein fettes Dankeschön an Sophie Rotgeri, die das gesamte Tutorial redigiert hat.