tidy R: Transformation, Zusammenfassen und Visualisieren von Daten mit tidy

, , 2021

```{r setup, include=FALSE} ## include this at top of your RMarkdown file for pretty output ## make sure to have the printr package installed: install.packages('printr') knitr::opts_chunk$set(echo = TRUE, results = TRUE, message = FALSE, warning = FALSE) #library(printr) ``` # 1. Einleitung Das Ziel dieses Tutorials ist euch mit dem *Tidyverse* vertraut zu machen. Außerdem soll das Tutorial euch eine Einführung in die Arbeit mit Daten, deren Verarbeitung und Visualisierung geben. Das *Tidyverse* ist eine Sammlung von Paketen, die auf der Basis von klar definierten Prinzipien entwickelt wurden. *Tidy* propagiert klare Vorstellungen davon, wie Daten aussehen sollten und wie wir mit Daten arbeiten sollten. Das *tidyverse* wird in dem kostenlosen (Online-) Buch *R for Data Science* vorgestellt. Dieses Tutorial behandelt die Inhalte aus den Kapitel 3, 5 und 7: * [Kapitel 3](https://r4ds.had.co.nz/data-visualisation.html) * [Kapitel 5](https://r4ds.had.co.nz/transform.html) * [Kapitel 7](https://r4ds.had.co.nz/exploratory-data-analysis.html) Ein zentraler Aspekt des `tidyverse` ist das Paket `dplyr` (data-pliers), das die meisten der Funktionen enthält, die wir im Folgenden verwenden. # 2. Installieren von tidyverse Wie zuvor verwenden wir `install.packages()`, um das Paket herunterzuladen und zu installieren. Anschließend nutzen wir `library()`, damit wir die Funktionen aus dem Paket auch nutzen können. **Tipp**: schreibt die `library()`-Funktion für die Pakete, die ihr in eurem `R`-Skript verwendet, untereinander an den Anfang eures jeweiligen Skripts. Mit mehr Erfahrungen in `R` werdet ihr Anfangen für jede Aufgabe bzw. jeden Analyseschritt für eine Hausarbeit (z.B. deskriptive Statistik, Visualisierung, multivariate Analyse) ein einzelnes Skript zu verwenden. Jeder dieser Schritte benötigt meistens unterschiedliche Datenformate, Pakete etc. Im Gegensatz zu `install.packages()`, das ich pro Computer und pro Paket nur einmal ausführen müsst, ist die `library()`-Funktion bei jedem Start von `R` notwendig. ```{r eval=F} install.packages("tidyverse") # nur einmal notwendig. Beachtet: beim Installieren von Paketen müsst ihr Anführungszeichen verwenden. R ist case-sensitive und die Pakete müssen spezifisch angesprochen werden. ``` ```{r eval=T} library(tidyverse) # bei jedem Start von R notwendig ``` **Hinweis**: Nach dem Aufruf des *tidyverse* oder anderen Paket-Bibliotheken sind manchmal eine rote Meldung zu sehen. Das ist nicht weiter schlimm und kann in den meisten Fällen ignoriert werden. Eine Operation ist dann fehlerhaft, wenn in der Meldung auch wirklich das Wort "Fehler" auftaucht. Das passiert zum Beispiel bei einem falsch geschriebenen Paket (`R` ist case-sensitive und nimmt Groß- und Kleinschreibung sehr, wirklich sehr, ernst.) ```{r eval=F} library(tidyvers) ``` `Fehler in library(tidyvers) : es gibt kein Paket namens ‘tidyvers’` # 3. Grundlagen des Tidyverse Die Funktionalität von `R`-Paketen definiert sich durch die Funktionen die sie beinhalten. Umgangssprachlich ausgedrück ist eine Funktion ein Befehl an den Computer, eine Handlung durchzuführen und und das Ergebnis dieser Handlung zu berichten/zurückzugeben. Die Funktionalität von `dyplyr` basiert vor allem auf den Umgang mit Datensätzen, wie zum Beispiel das Filtern und Sortieren von Variablen. Datensätze bestehen aus Reihen (die Fälle bzw. Beobachtungen wie Länder, Probantinnen, Parteiprogramme, Gerichtsurteile) und Spalten (die Variablen, wie z.B. welcher Senat des Bundesverfassungsgerichts eine Entscheidung zu verantworten hat oder von welcher Partei ein Programm verfasst wurde und wann). Ihr habt ja bereits `data.frames` als `R`-Objekte für Datensätze kennengelernt. Das *tidyverse* verwendet einen eigenen Datensatztyp: `tibble` `tibble` sind funktional äquivalent zu `data.frame`, aber deutlich effizienter und einfacher zu verwenden. Im Beispiel erstellen wir einen Datensatz über Verfassungsgerichte (drei Gerichte, deren rechtlichte Tradition und ob sie die Kompetenz zur abstrakten Normenkontrolle haben): ```{r eval=T} courts <- tibble(id=c(1,2,3), gericht=c("Bundesverfassungsgericht","Österreichischer Verfassungsgerichtshof","US Supreme Court"), rechtstradition=c("civil law","civil law","common law"), abstrakte_NK=c(1, 1, 0) ) courts ``` Die Struktur und die Erstellung verläuft analog zur Struktur und Erstellung mit der `data.frame`-Funktion. Wir definieren den Namen das Datensatzes ("courts"), verwenden die `tibble`-Funktion und definieren innerhalb dieser Funktion die einzelnen Variablen (id = Identifikationsnummer, gericht = Namen der drei Gerichte, rechtstradition = die Tradition in welcher das jeweilige Rechtssystem steht, abstrakte_NK=hat das jeweilige Gerichte die Kompetenz für abstrakte Normenkontrolle). Im weiteren Verlauf des Tutorials gehen wir noch etwas mehr auf die Arbeit mit Datensätzen ein. ### 3.1. Daten lesen: `read_csv` In dem Beispiel oben haben wir einen Datensatz manuell erstellt. In den allermeisten Fällen werdet ihr aber externe Datensätze nutzen, wie zum Beispiel die Datensätze von [V-Dem](https://www.v-dem.net/en/data/data/) oder vom [Comparatives Agendas Project](https://www.comparativeagendas.net). Solche externen Datensätze sind meistens als csv-Datei erhältlich (natürlich finden sich auch Excel-, SPSS-, Stata-, oder auch `R` eigene RDat-Datensätze). Tidyverse enthält die Funktion `read_csv`, mit der ihr eine csv-Datei direkt als Datensatz einlesen könnt. Dafür gebt ihr den Speicherort der Datei an, entweder mittels eures lokalen Datenpfads um auf euer Laufwerk zuzugreifen oder aus dem Internet. Im folgenden Beipsiel laden wir zwei Datensätze herunter. Zum einen meinen eigenen, den ich für meine Dissertation erstellt habe. Er enthält Senatsentscheidungen des Bundesverfassungsgerichts für die Jahre 2015 - 2020. Er beinhaltet vielfältige Meta-Daten wie den Volltext der Entscheidungen, den Verfahrenstyp etc. Zum anderen laden wir einen Datensatz von [fivethirtyeight](https://fivethirtyeight.com/) herunter. Dieser Datensatz enhält Umfragedaten über die private Nutzung von Feuerwaffen in den USA. Beide Datensätze stammen aus dem Internet. Aber ihr könnt die URL meines Datensatzes nutzen, um diesen direkt herunterzuladen. Falls ihr das macht, dann versucht doch einmal die Daten von eurem lokalen Laufwerk aus einzulesen. ```{r eval=T} library(readr) # zur Sicherheit laden wir das Paket "readr", in welchem die read_csv-Funktion implementiert ist # Schritt 1: Lest die URL in R ein (die Internetseiten verweisen direkt auf die hochgeladenen csv.Dateien. Das ist nicht überall der Fall, weshalb der hier präsentierte nur einer von vielen ist) url_courts <- "https://phimeyer.github.io/teaching/FCC_senate_decisions.csv" url_weapons <- "https://raw.githubusercontent.com/fivethirtyeight/data/master/poll-quiz-guns/guns-polls.csv" # Schritt 2: Mittels der Funktion read_csv wird nun der im Web gespeicherte csv.Datensatz eingelesen court <- read_csv(url_courts) head(court) # head() zeigt die ersten zehn beobachtungen des datensatzes weapons <- read_csv(url_weapons) tail(weapons) # tail() zeigt die letzten zehn beobachtungen des datensatzes ``` (ihr könnt die (roten) Meldungen ignorieren, sie sagen nur, wie die einzelnen Spalten *geparst* wurden) Wenn ihr "manuell" durch eure Datensätze blättern wollen, dann müsst ihr auf den Namen des Datensatzes im oberen rechten Fenster "Umgebung/Environment" klicken oder aber den Befehl `View(court)` als Code schreiben und ausführen. ### 3.2. Subsetting mit `filter()` Die Filterfunktion `filter()` kann verwendet werden, um Datensätze zu *subsetten*, also um Teilmengen eines Datensatzes zu erstellen. In den Umfragedaten über die Waffennutzung gibt die Spalte "Question" an, welche Frage gestellt wurde. Wir können jetzt nur die Reihen (die einzelnen Fragen der Umfragen) auswählen, die gefragt haben, ob das Mindestkaufalter für Waffen auf 21 Jahre angehoben werden soll. Für den Gerichtsdatensatz können wir zum Beispiel die Spalte "bvf" auswählen und damit die Gerichtsentscheidungen auswählen, die eine Verfassungsbeschwerde zum Streitgegenstand hatten (dummy variable, 1 für ja und 0 für nein): ```{r eval=T} age21 <- filter(weapons, Question == 'age-21') # "Question" ist die Variable und "age-21" die Ausprägung age21 verfb <- filter(court, bvf == 1) verfb ``` Das erste Argument sind die zu verwendenden Daten (z.B. weapons), und das/die verbleibende(n) Argument(e) definieren was genau mit den Daten geschehen soll. Beachtet hier bitte die Verwendung von ==. Andere Möglichkeiten wären > (größer als), <= (kleiner als oder gleich) und != (nicht gleich). Ihr könnt auch mehrere Bedingungen mit logischen (booleschen) Operatoren kombinieren: & (und), I (oder), und ! (nicht), und ihr könnt Klammern verwenden, um `R` zu sagen welche Befehle wann ausgeführt werden sollen (also ähnlich der Funktion von Klammern in der Mathematik). So können wir alle Umfragen finden, bei denen die Unterstützung für die Anhebung des Waffenalters bei mindestens 80 % lag: ```{r eval=T} filter(weapons, Question == 'age-21' & Support >= 80) ``` Beachtet bitte, dass wir bei diesem Befehl kein neues Objekt erzeugt haben. Das Ergebnis wird also direkt in der Console angezeigt, aber nicht gespeichert. So könnt ihr eure Daten schnell inspizieren. Wenn ihr aber diese Datenteilmenge weiter analysieren möchtet, dann müsst ihr sie einem Objekt zuweisen. ### 3.3. Nebenbei: Hilfe zu einer (tidy) Funktion erhalten Wie bereits erklärt, könnt ihr bei Fragen über eine bestimmte Funktion die `?`-Funktion in der Konsole eingeben (z.B. `?filter`) um die Dokumentation der gesuchten Funktion angezeigt zu bekommen. Wenn ihr euch die Hilfeseite anseht, dann findet ihr, wie immer, zuerst die allgemeine Beschreibung. Danach folgt die Verwendung, die zeigt, wie die Funktion aufgerufen werden soll. Der Rest gibt zusätzliche Informationen darüber, was genau die Funktion macht, die Ausgabe, die sie erzeugt (Value), und Links zu anderen nützlichen Paketen, Funktionen und schließlich eine Reihe von Beispielen. Auch wenn es anfangs einschüchternd erscheinen mag, ist es wichtig, sich an den Stil der R-Dokumentation zu gewöhnen. Das ist der erste Anlaufpunkt für Fragen über Funktionen und Pakete! **Aber vergesst nicht**: ihr könnt für jedes Problem in `R` eine Lösung im Internet finden (z.B. indem ihr einfach die Fehlermeldung die `R` ausgibt kopiert und als Suchanfrage eingebt)! ### 3.4. Auswählen bestimmter Spalten Wo ihr mit `filter()` bestimmte Reihen auswählen könnt, könnt ihr mit `select()` bestimmte Spalten auswählen. Am einfachsten ist es, die Spalten einfach so zu benennen, dass sie in dieser bestimmten Reihenfolge abgerufen werden: ```{r eval=T} select(age21, Population, Support, Pollster) ``` Ihr könnt auch einen Bereich von Spalten angeben, z. B. alle Spalten von Support bis Democratic Support: ```{r eval=T} select(age21, Support:`Democratic Support`) ``` Beachtet die Verwendung von 'Backticks' (umgekehrten Anführungszeichen) zur Angabe des Spaltennamens, da `R` normalerweise keine Leerzeichen in Namen zulässt. `select()` kann auch verwendet werden, um Spalten beim Auswählen umzubenennen, z. B. um die Leerzeichen loszuwerden: ```{r eval=T} select(age21, Pollster, rep=`Republican Support`, dem=`Democratic Support`) ``` Wenn ihr nur Spalten umbenennen möchten, könnt ihr die Funktion `rename()` verwenden: ```{r eval=T} rename(age21, start_date = Start, end_date = End) # hier nennen wir die Variablen Start und End in start_date und end_date um ``` Natürlich könnt ihr Variablen auch löschen. Dazu müsst ihr Minuszeichen vor die Variablennamen setzen: ```{r eval=T} select(age21, -Question, -URL) ``` ### 3.5. Sortieren mit `arrange()` Ihr könnt einen Datensatz ganz einfach mit der Funktion `arrange()` sortieren. Dazu gebt ihr zuerst den Datensatz an und dann die Spalte(n), nach denen sortiert werden soll. Um absteigend zu sortieren, müsst ihr ein Minus vor eine Variable setzen. Zum Beispiel wird im Folgenden nach Bevölkerung und dann nach Unterstützung für die Anhebung des Waffenalters (absteigend) sortiert: ```{r eval=T} age21 <- arrange(age21, Population, -Support) age21 ``` Beachtet bitte, dass ich das Ergebnis der Sortierung wieder dem `age21`-Objekt zugewiesen habe, d.h. ich ersetze das Objekt durch seine sortierte Version. Wenn ich das Ergebnis nicht zuweisen würde, würde es zwar in der Console angezeigt, aber nicht gespeichert werden. Einem Ergebnis den gleichen Namen zuzuweisen bedeutet, dass ich kein neues Objekt anlege, wodurch die Umgebung nicht unübersichtlich wird (und ich mir die Mühe erspare, mir einen weiteren Objektnamen auszudenken). Im Falle des neu arrangierens von Daten ist das im Allgemeinen in Ordnung, da die sortierten Daten die gleichen Daten wie zuvor enthalten. Im Falle des Subsetting bedeutet das, dass die Daten tatsächlich aus dem Datensatz (im Speicher) gelöscht werden. In diesem Fall müsstet ihr die Daten erneut einlesen (oder mit einem früheren Objekt beginnen), wenn ihr die "alten" Reihen oder Spalten später benötigen solltet (hier zeigen sich die Vorteile eines `R`-Skripts, in dem ihr, im Gegensatz zur Console, einfach zum vorherigen Schritt scrollen und diesen nochmals ausführen könnt). Ihr werdet merken: `R` ist zu großen Teilen **trial and error**! ### 3.6. Hinzufügen oder Verändern von Variablen mit `mutate()` Die Funktion `mutate()` macht es einfach, neue Variablen zu erstellen oder bestehende Variablen zu verändern. Wenn ihr euch die Dokumentationsseite anschaut (`?mutate`), dann seht ihr, dass mutate ähnlich wie `filter()` und `select()` funktioniert. Auch hier (wie *fast* überall, auch außerhalb des tidyverse) ist das erste Argument der Datensatz, gefolgt von einer Anzahl von zusätzlichen Argumenten. Im folgenden werden wir zuerst einige Variablen erstellen und uns dann die Variablen ansehen (mit Hilfe von `select()`, um den Fokus auf die Änderungen zu legen). Konkret werden wir eine Spalte für die Differenz zwischen den Unterstützungswerten für Republikaner und Demokraten erstellen. Damit haben wir ein Maß, welches die Uneinigkeit der Befragten (entlang von Parteilinien) misst. ```{r eval=T} age21 <- mutate(age21, party_diff = abs(`Republican Support` - `Democratic Support`)) select(age21, Question, Pollster, party_diff) age21 <- arrange(age21, Population, -Support) age21 ``` Um eine Variable in derselben Spalte zu transformieren (umzukodieren), könnt ihr einfach einen vorhandenen Namen in `mutate()` verwenden, um ihn zu überschreiben. ### 3.7. Arbeiten mit Pipes Schaut ihr euch den obigen Code an, dann werdet ihr bemerken, dass das Ergebnis jeder Funktion als Objekt gespeichert wird und dass dieses Objekt als erstes Argument für die nächste Funktion verwendet wird. Die temporären Objekte sind meistens nicht von Interesse, da das eigentliche Ziel, wie in diesem Beispiel die Summentabelle, eine Datensatztransformation, eine neue Variable oder ähnliches ist. Man kann die einzelnen Schritte als eine Pipeline von Funktionen betrachten, bei der die Ausgabe jeder Funktion die Eingabe für die nächste Funktion ist. Tidyverse bietet hier Möglichkeiten den obigen Code zu vereinfachen. Hierfür nutzt ihr den Pipe-Operator `%>%`. Jedes mal wenn ihr eine Funktion `f(a, x)` schreibt, könnt ihr das durch ein `%>% f(x)` ersetzen. Wenn ihr dann das Ergebnis von `f(a, x)` für eine zweite Funktion verwenden wollt, dann hängt ihr das einfach an eine Pipeline an: `a %>% f(x) %>% f2(y)`. Das ist äquivalent zu `f2(f(a,x), y)` bzw. `b=f(a,x); f2(b, y)`. Einfach gesagt nehmen Pipes das Ergebnis einer Funktion und verwenden es als Dateneingabe für eine zweite Funktion. Da alle dplyr-Funktionen als erstes Argument ein Tibble voraussetzen und alle Funktionen ein Tibble als Ergebnis zurückgeben, haben wir die Möglichkeit alle Funktionen miteinander zu verknüpfen. Zur Verdeutlichung schreiben wir alle Code-Elemente dieses Tutorials nochmals: ```{r eval=F} weapons <- read_csv(url_weapons) age21 <- filter(weapons, Question == 'age-21') age21 <- mutate(age21, party_diff = abs("Republican Support" - "Democratic Support")) age21 <- select(age21, Question, Pollster, party_diff) arrange(age21, -party_diff) # Hier haben wir insgesamt vier Mal Werte einem Namen zugewiesen (zwei Überschreibungen von age21), außerdem haben wir vor jede Funktion den Datensatznamen geschrieben (z.B. select(age21,....)) ``` Um es noch einmal zusammenzufassen: Die csv wird eingelesen, nach bestimmten Fragen gefiltert, die Differenz der Parteianhänger berechnet, andere Variablen entfernt und sortiert. Diesen ganzen Block können wir als eine einzige Pipeline schreiben: ```{r eval=F} age21 <- read_csv(url_weapons) %>% filter(Question == 'age-21') %>% mutate(party_diff = abs("Republican Support" - "Democratic Support")) %>% select(Question, Pollster, party_diff) %>% arrange(-party_diff) age21 # Hier haben wir lediglich einmal einen Namen zugewiesen (age21) und den Datensatznamen sonst nicht mehr verwendet (z.B. select(Question,...)) ``` Das Elegante an Pipes ist, dass sie eindeutig zeigen welche Schritte durchführt werden. Außerdem müssen nicht viele Zwischenobjekte erstellt werden. Damit können dann bestimmte Teile der Analyse von der Roheingabe bis zu den Ergebnissen durchgeführt werden, einschließlich statistischer Modellierung oder Visualisierung. Natürlich werdet ihr nicht euer ganzes Skript durch eine einzige Pipe ersetzen. Oft ist es hilfreich die Zwischenschritte einzeln auszuführen, um die Zwischenwerte speichern zu können. Zum Beispiel könnt ihr einen Datensatz herunterladen, bereinigen und untergliedern, bevor ihr die gewünschten Analysen durchführt. In diesem Fall wollt ihr wahrscheinlich die Ergebnisse des Bereinigens und Subsettings als einzelne Variable speichern und diese dann in euren Analysen (die ihr als Pipes organisieren könnt) verwenden. # 4. Aggregation von Daten und Datensätzen Die bisher in diesem Tutorial verwendeten Funktionen für die Datenaufbereitung haben mit einzelnen Reihen gearbeiten. In den meisten Fällen werdet ihr aber eine Vielzahl von Reihen (Beobachtungen, Fälle) untersuchen und nicht nur einzelne Werte. Das wird als Aggregation (Zusammenfassung) bezeichnet. Im tidyverse nutzen wir hierfür die Funktionen `group_by()`, `summarize()` und/oder `mutate()`. ### 4.1. Einfache Aggregation Zur Wiederholung und Einübung laden und bearbeiten wir unsere Waffenumfragedaten: ```{r eval=T} library(tidyverse) url_weapons <- "https://raw.githubusercontent.com/fivethirtyeight/data/master/poll-quiz-guns/guns-polls.csv" weapons <- read_csv(url_weapons) %>% select(-URL) %>% rename(Rep= "Republican Support", Dem= "Democratic Support") # hier haben wir zusätzlich die URL aus dem Datensatz gelöscht und die Parteinamen verkürzt (Rep, Dem) weapons ``` #### Datensatzreihen gruppieren Jetzt können wir die Funktion `group_by()` verwenden, um z. B. die Daten in Bezug auf die verschiedenen Fragetypen (die Variable "Question") zu gruppieren (als kleine Erinnerung: die Bedeutung von Variablen eines Datensatzes findet ihr in dem zum Datensatz gehörigen Codebuch): ```{r eval=T} weapons %>% group_by(Question) weapons ``` Wie ihr sehen könnt, haben sich die Daten nicht geändert. Wir haben lediglich eine Gruppierung (8 Fragen, 8 Gruppen) vorgenommen. #### Aggregieren/Zusammenfassen Um Daten zusammenzufassen, nutzen wir die Funktionen `group_by()` und `summarize()`. Die Syntax von `summarize()` ähnelt sehr stark der von `mutate()` (schaut euch die Dokumentation der Funktion mit `?summarize` an!): `summarize(column=calculation, ...)`. Der zentrale Unterschied ist, dass wir immer eine Funktion zur Berechnung verwenden müssen und erst dann eine Zusammenfassung durchführen könnnen. Gebräuchliche "Zusammenfassungsfunktionen" sind die Berechnung der Summe (`sum()`), des Mittelwerts (`mean()`) oder der Standardabweichung (`sd()`). Im Folgenden Beispiel wird die durchschnittliche Unterstützung pro unterschiedliche Frage in den Umfragen (Question) berechnet und nach absteigender Unterstützung sortiert: ```{r eval=T} weapons_sum <- weapons %>% group_by(Question) %>% summarize(Support=mean(Support)) %>% arrange(-Support) # die aggregierten Daten werden dem Namen weapons_sum zugeordnet und damit wird ein neuer Datensatz erstellt weapons_sum ``` Wie ihr sehen können, ändert `summarize()` die Daten. Die Reihen enstprechen jetzt der Anzahl der unterschiedlichen Fragen in den Umfragen (8), und die einzigen Spalten, die übrig geblieben sind, sind die Gruppierungsvariablen und die zusammengefassten Werte. Natürlich können wir auch Zusammenfassungen von mehreren Werten berechnen und auch weiterführende Berechnungen durchführen: ```{r eval=T} weapons_sum <- weapons %>% group_by(Question) %>% summarize(n=n(), mean=mean(Support), sd=sd(Support)) weapons_sum ``` Wie ihr jetzt sehen könnt, hat einer der Werte einen fehlenden Wert (NA) für die Standardabweichung. Warum? #### Verwenden von `mutate()` mit `group_by()` Die obigen Beispiele reduzieren die Anzahl der Beobachtungen/Fälle auf die Anzahl der von uns definierten Gruppen (also die unterschiedlichen Fragen). Eine weitere Möglichkeit ist die Verwendung von `mutate()`. Damit werden den Reihen die Zusammenfassungswerte hinzugefügt. Nehmen wir zum Beispiel an, wir möchten sehen, ob eine bestimmte Frage eine andere gesellschaftliche Unterstützung besitzt als der Durchschnitt. Wir können `group_by()` und dann `mutate()` verwenden und berechnen so die durchschnittliche Unterstützung: ```{r eval=T} weapons_avgSup <- weapons %>% group_by(Question) %>% mutate(avg_support=mean(Support), diff=Support - avg_support) # hier erstellen wir die neue Variable "avg_support", also die durchschnittliche Unterstützung und die neue Variable "diff" für die wir die Unterstützung (Support) mit der durchschnittlichen Unterstützung (avg_support) substrahieren weapons_avgSup ``` Es wir hier also deutlich, dass `summarize()` den Datensatz auf die Gruppen und Zusammenfassungen reduziert, während `mutate()` eine neue Spalte hinzufügt, die für alle Zeilen innerhalb einer Gruppe identisch ist. #### Gruppierungen aufheben Schließlich können wir mit `ungroup()` alle Gruppierungen wieder aufheben. Die im letzten Beispiel erzeugten Daten sind z. B. immer noch nach Fragen (Question) gruppiert. Wenn wir also die Gesamtstandardabweichung der Differenz berechnen möchten, können wir die Gruppierung aufheben und dann zusammenfassen: ```{r eval=T} weapons_avgSup %>% ungroup() %>% summarize(diff=sd(diff)) ``` (Natürlich würde die Ausführung von `sd(weapons_avgSup$diff))` das gleiche Ergebnis liefern). Wie (und warum) würde das Ergebnis aussehen, wenn wir die Gruppierung **nicht** aufheben würden? ### 4.2. Multiple Aggregation/Zusammenfassen Die Beispiele verwendeten alle eine einzelne Gruppierungsvariable. Wir können auch nach mehreren Spalten gruppieren. Zum Beispiel könnten wir die durchschnittliche Unterstützung pro Frage und pro Population berechnen: ```{r eval=T} weapons %>% group_by(Question, Population) %>% summarize(Support=mean(Support)) weapons ``` Das Ergebnis ist ein Datensatz mit einer Zeile pro Gruppe, d. h. einer Kombination aus `Question` und `Population`, mit separaten Spalten für jede Gruppierung und Aggregation. Der resultierende Datensatz ist lediglich nach `Question` gruppiert. Die Gruppen nach der Zusammenfassung intakt zu lassen, wäre nicht sinnvoll, da ihr niemals eine Zusammenfassung derselben Gruppen berechnen werdet: Jede der alten Gruppen ist jetzt eine einzelne Reihe. Während also `mutate()` die Gruppierungsinformationen beibehält, wird bei `summarize()` die eine Gruppierungsspalte, in diesem Fall `Population`, gelöscht. Dadurch könnt ihr die durchschnittliche Unterstützung pro Frage berechnen: (d. h. der Mittelwert der Zusammenfassungen pro Population): ```{r eval=T} weapons %>% group_by(Question, Population) %>% summarize(Support=mean(Support)) %>% mutate(avg_support=mean(Support)) weapons ``` Gibt es eine Möglichkeit, auch den Mittelwert der einzelnen Umfragen zu addieren? ### 4.3. Fehlende Werte Zusammenfassungsfunktionen in `R` geben standardmäßig NA zurück, wenn einer der zusammenzufassenden Werte fehlt: ```{r eval=T} mean(c(3,4,NA,6)) ``` Wenn ihr also Daten zusammenfasst, bei denen Reihen fehlendene Werte enthalten, wird der zusammengefasste Wert auf NA gesetzt. Im folgenden Code erstellen wir mittels `ifelse()` einen NA-Wert für unsere Waffenumfragedaten: Wir setzen den Unterstützungwert für alle CBS News-Umfragen auf NA: (Hinweis: `ifelse()` nimmt 3 Werte an: `ifelse(test, value-if-true, value-if-false)`, wodurch jede Zeile entsprechend dem Test gesetzt wird. In diesem Fall wird getestet, ob `Pollster` gleich `CBS` ist, und wenn dies der Fall ist, wird `Support` auf `NA` gesetzt, andernfalls wird `support` auf `support` gesetzt (d. h. es bleit unverändert)) ```{r eval=T} weapons2 <- weapons %>% mutate(Support=ifelse(Pollster == "CBS News", NA, Support)) weapons2 ``` Wenn wir jetzt den Mittelwert für Support pro Frage nehmen, ergibt das ein `NA` für alle Fragen, bei denen CBS Teil des Sets war: ```{r eval=T} weapons2 %>% group_by(Question) %>% summarize(Support=mean(Support)) weapons2 ``` Es ist zwar der mathemtische korrekte Weg fehlende Werte zu behandeln, aber meistens wollen wir das einfach ignorieren. Um das zu bewerkstelligen fügen wir `na.rm=T` (T steht für TRUE) zur Mittelwertfunktion hinzu: ```{r eval=T} weapons2 %>% group_by(Question) %>% summarize(Support=mean(Support, na.rm=T)) weapons2 ``` # 5. Grundlagen der Datenvisualisierung In fünften Teil des Tutorials arbeiten wir mit dem Paket `ggplot2`. Hier noch ein paar generelle Hinweise zu diesem Paket: * Ihr findet ein paar sehr schöne Visualisierungsbeispiele mit `ggplot2` in der [R Graph Gallery](https://www.r-graph-gallery.com/ggplot2-package.html). Dort findet ihr auch den zugehörigen Code. Damit könnt ihr dann die Beispiele mit euren eigenen Daten nachbauen. * Keinen Code, aber etwas Inspiration für eure eigenen Visualisierungen findet ihr [hier](https://fivethirtyeight.com/features/the-52-best-and-weirdest-charts-we-made-in-2016/). * Abschließen noch der Hinweis auf einen interessanten Artikel der `ggplot2` Entwickler über die ["Grammatik von Graphiken"](http://vita.had.co.nz/papers/layered-grammar.html). ### 5.1. `ggplot2` Nehmen wir an, dass wir die Beziehung zwischen Hochschulbildung und Haushaltseinkommen für die US Staaten sehen wollen. Die dafür notwendigen Daten beziehen wir von der Github-Seite des ["Houston Data Visualization Meetup"](https://github.com/houstondatavis/data-jam-august-2016), die einen Datensatz namens "**country_facts**" veröffentlicht haben. Der Datensatz ist sehr groß, weshalb wir für unser Beispiel mit einem subset arbeiten werden (ich würde mich freuen, wenn ihr privat mit dem gesamten Datensatz experimentieren würdet): ```{r eval=F} install.packages("ggplot2") # beim ersten mal nicht vergessen! ``` ```{r eval=T} csv_folder_url <- "https://raw.githubusercontent.com/houstondatavis/data-jam-august-2016/master/csv/county_facts.csv" facts <- read_csv(csv_folder_url) facts_subset <- facts %>% select(fips, area_name, state_abbreviation , population=Pop_2014_count, pop_change=Pop_change_pct , over65=Age_over_65_pct, female=Sex_female_pct , anglo_americans=Race_white_pct, college=Pop_college_grad_pct , income=Income_per_capita) # hier nehmen wir den Datensatz (facts) und wählen nur ein paar Variablen aus (mittels select) und benennen einige Variablen um (z.B. die Variable "Pop_college_grad_pct" heißt in unserem facts_subset-Datensatz jetzt "college") facts_state <- facts_subset %>% filter(is.na(state_abbreviation) & fips != 0) %>% select(-state_abbreviation) facts_state ``` Gut! Jetzt haben wir einen Datensatz. Machen wir uns jetzt daran eine der am meisten genutzten Visualisierungen überhaupt zu gestalten: ein Steudiagramm! Für selbiges soll der Prozentsatz der Hochschulabsolventinnen auf der x-Achse und das Medianeinkommen auf der y-Achse dargestellt werden. ```{r eval=T} library(ggplot2) # bei jedem Durchlauf eures Skripts (im Gegensatz zu install.packages()) ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income)) ``` Wie ihr seht besteht der Befehl aus zwei Teilen. Im ersten Schritt erstellt `ggplot` eine *leere Leinwand*, die mit dem Datensatz `fact_state` verknüpft ist (`ggplot(data=facts_state)`). Im zweiten Schritt nutzen wir `geom_point` um die uns interessierenden Informationen (Datensatzvariablen) auf diese leere Leinwand zu projezieren. Ihr könnt euch die `ggplot`-Befehle als Schichten vorstellen: 1) die "Leinwand", verknüpft mit dem Datensatz; 2) die geometische Form, hier Punkte (`geom_**point**`); 3) Aspekte um die Datenpunkte abzubilden (hier x=college und y=income). Unser Beispiel ist die einfachste Form einer Visualisierung. Das `gglot2`-Paket hat unendlich viele Möglichkeiten, die ihr mit der Zeit und mit euren eigenen Daten und Ideen selber herausfinden werdet. Unsere Beispiel-Graphik ist ein Streudiagramm, in dem jeder Punkt einen US Staat repräsentiert. Zusätzlich sehen wir eine klare Korrelation zwischen Bildungsniveau und Einkommen. Es gibt aber auch einen klaren Ausreißer oben rechts. Welcher Staat ist das? Bevor ihr weiterlest, versucht euch doch mal darin, die Datenpunkte mit Lables zu versehen, um diese Frage zu beantworten. ### 5.2. Ein paar Hinweise zur `ggplot`-Syntax Damit der Plot funktioniert, muss `R` den gesamten `ggplot`-Aufruf und alle Ebenen als eine einzige Anweisung ausführen. Praktisch bedeutet das, dass, wenn ihr einen Plot über mehrere Zeilen kombiniert, das Pluszeichen am Ende der Zeile stehen **muss**. Erst dann weiß `R`, dass noch mehr kommt. Aber die Positionierung des + ist ebenso wichtig: **Richtig**: ```{r eval=F} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income)) ``` **Falsch**: ```{r eval=F} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income)) ``` Da die Argumente `data` und `mapping` die ersten Argumente sind, die die `ggplot`-Funktionen erwarten (siehe `?gglot` und `?geom_point`), müsst ihr diese nicht extra schreiben: ```{r eval=F} ggplot(facts_state) + geom_point(aes(x=college, y=income)) ``` ### 5.3. Weitere ästhetische Aspekte Um herauszufinden, welche visuellen Elemente verwendet werden können, lest euch die Dokumentation der Funktionen durch (z. B. `?geom_point`). Hier seht ihr, dass wir unter anderem die Farbe, die Transparenz (alpha) und die Größe der Datenpunkte nach unseren Wünschen verändern können. Stellen wir zunächst die Größe der Punkte auf die Bevölkerung jedes Staates ein: ```{r eval=T} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income, size=population)) ``` Da es schwierig ist, überlappende Punkte zu sehen, wollen wir alle Punkte etwas transparenter machen. Hinweis: Da wir das Alpha aller Punkte auf einen einzigen Wert setzen wollen, ist dies kein Mapping (da es nicht auf eine Spalte aus dem Datenrahmen abgebildet wird), sondern eine Konstante. Diese wird deshlab außerhalb des Mapping-Arguments gesetzt: ```{r eval=T} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income, size=population), alpha=.5, colour="red") ``` Anstatt die Farbe auf einen konstanten Wert zu setzen, können wir sie auch mit den Daten variieren lassen. Zum Beispiel können wir die Staaten nach dem Prozentsatz der Bevölkerung über 65 einfärben: ```{r eval=T} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income, size=population, colour=over65), alpha=.9) ``` Schließlich können wir auch eine kategoriale Variable abbilden. Hierfür müssen wir zuerst die Kategorien bilden, z.B. ob die Bevölkerungen in einem Staat wächst (mindestens um 1%) oder stabil bleibt. Um diese Kategorie zu erstellen, benutzen wir die Funktion `if_else()`, die den Wert "if true" zuweist, wenn die definierte Bedingung wahr ist (anderenfalls ist es falsch). ```{r eval=T} facts_state <- facts_state %>% mutate(growth=ifelse(pop_change > 1, "Growing", "Stable")) # wenn der wert "pop_change"" über 1 weis der neuen Variable "growth" den Wert "Growing" zu, wenn nicht dann "Stable". ``` Jetzt können wir eine "Kategorien-Farbe" zu der Graphik hinzufügen: ```{r eval=T} ggplot(data=facts_state) + geom_point(mapping=aes(x=college, y=income, size=population, colour=growth), alpha=.9) ``` Wie ihr in diesen Beispielen sehen können, versucht `ggplot`, die von euch verlangte Zuordnung intelligent zu gestalten. Es setzt die x- und y-Bereiche automatisch auf die Werte der verwendeten Daten. Für die Farbe z.B. erstellt `ggplot` bei Intervallvariablen eine Farbskala, während es bei einer kategorialen Variable jeder Gruppe automatisch eine Farbe zuordnet. Natürlich kann jede dieser Möglichkeiten angepasst werden. Das ist auch sinnvoll. Zum Beispiel könnte man Rot für Republikaner und Blau für Demokraten verwenden. Andererseits verlangen Publikationen in Fachzeitschriften meistens schlichte Grafiken in schwarz-weiß bzw. mit verschiedenen Grau- oder Schwarztönen. Am besten ihr sucht im Internet mal eigenständig nach Beispielen für andere Farbkonfigurationen für `ggplot`-Graphen und experimentiert etwas rum. ### 5.4. Balkendiagramme Eine weitere Standardvisualisierung ist das Balkendiagramm. `R` geht bei Balkendiagrammen generell davon aus, dass ein Histogramm dargestellt werden soll. Zum Beispiel können wir darstellen ob die Bevölkerung in den US Staaten wächst oder stabil bleibt: ```{r eval=T} ggplot(data=facts_state) + geom_bar(mapping=aes(x=growth)) # wir nehmen die von uns oben erstelle kategoriale variable "growth" ``` Zugegeben, diese Darstellung ist leidlich interessant. Um das zu ändern laden wir jetzt einen weiteren Datensatz von der Github-Seite des ["Houston Data Visualization Meetup"](https://github.com/houstondatavis/data-jam-august-2016) und visualisieren die Stimmen pro republikanischem Kandidaten in der Vorwahl in New Hampshire. Zuerst laden wir die Daten pro Bezirk herunter, fassen diese auf Ebene der Staaten zusammen und filtern die Ergebnisse für die repubikanische Partei in New Hampshire heraus. ```{r eval=T} primary_results_url <- "https://raw.githubusercontent.com/houstondatavis/data-jam-august-2016/master/csv/primary_results.csv" results <- read_csv(primary_results_url) results_state <- results %>% group_by(state, party, candidate) %>% summarize(votes=sum(votes)) nh_gop <- results_state %>% filter(state == "New Hampshire" & party == "Republican") nh_gop ``` Jetzt erstellen wir ein Balkendiagramm mit Stimmen (y) pro Kandidat (x). Da wir nicht wollen, dass `ggplot` die Daten für uns zusammenfasst (das haben wir bereits selbst getan), definieren wir `stat="identity"`. So setzen wir die Gruppierungsstatistik auf die "Identitätsfunktion"", d.h. `ggplot` soll jeden Datenpunkt so verwenden, wie es ihn vorfindet, also keine Aggregation/Transformation vornehmen. ```{r eval=T} ggplot(data=nh_gop) + geom_bar(mapping=aes(x=candidate, y=votes), stat='identity') ``` ### 5.5. Graphikoptionen auf der Diagrammebene Einige Optionen, wie Beschriftungen, Legenden und das Koordinatensystem, gelten für das gesamte Diagramm und nicht pro ästhetischer Ebene (siehe die Beschreibungen in Punkt 5.1.). Diese Optionen werden dem Diagramm mittels zusätzlicher Funktionen hinzugefügt. Zum Beispiel können wir `coord_flip()` verwenden, um die x- und y-Achse zu vertauschen: ```{r eval=T} ggplot(data=nh_gop) + geom_bar(mapping=aes(x=candidate, y=votes), stat='identity') + coord_flip() ``` Die `reorder()`-Funktion kann vorhandene Kategorien umsortieren, zum Beispiel nach der Anzahl der erhaltenen Stimmen: ```{r eval=T} ggplot(data=nh_gop) + geom_bar(mapping=aes(x=reorder(candidate, votes), y=votes, fill=candidate), stat='identity') + coord_flip() ``` Jetzt sollten wir noch die Beschriftung der Achsen verändern und die in diesem Fall unnötige Legende entfernen. Außerdem sollte eine Graphik immer eine Überschrift haben! Zu guter letzt sind Wissenschaftlerinnen meistens Freunde von minimalistischen Darstellungen. Das lässt sich mit Grautönen und der Änderung des "Themes" bewerkstelligen: ```{r eval=T} p <- ggplot(data=nh_gop) + geom_bar(mapping=aes(x=reorder(candidate, votes), y=votes, fill=candidate), stat='identity') + coord_flip() + xlab("Candidate") + xlab("Votes") + ggtitle("New Hampshire: Votes per Candidate in the primaries") + guides(fill=F) + scale_fill_grey() + theme_minimal() # hier wählen wir das gewünschte minimalistische theme aus p ``` (Beachtet: Wir geben einer Graphik zum ersten Mal einen Namen (p)! Damit lassen sich die Graphiken nicht nur im Environment (oben links) speichern, sondern der Code lässt sich auch sehr gut und übersichtlich darstellen.) Wir können auch Gruppen zu Balkendiagrammen hinzufügen. Zum Beispiel können wir uns die Staaten anschauen (der Lesbarkeit wegen begrenzt auf New Hampshire und Iowa) und dann nach Kandidaten gruppieren: ```{r eval=T} gop2 <- results_state %>% filter(party == "Republican" & (state == "New Hampshire" | state == "Iowa")) # Neuer Datensatz mit Daten nur für Iowa und New Hampshire ggplot(data=gop2) + geom_bar(mapping=aes(x=state, y=votes, fill=candidate), stat='identity') ``` Standardmäßig sind solche Graphiken gestapelt. Das kann mit dem Parameter `position` gesteuert werden, der auf `dodge` (für gruppierte Balken) oder `fill` (Stapeln auf 100 %) eingestellt werden kann: ```{r eval=T} ggplot(data=gop2) + geom_bar(mapping=aes(x=state, y=votes, fill=candidate), stat='identity', position='dodge') ggplot(data=gop2) + geom_bar(mapping=aes(x=state, y=votes, fill=candidate), stat='identity', position='fill') ``` Wir können auch dafür sorgen, dass sich die gruppierten Balken zu 100 % addieren. Hierfür müssen wir das Verhältnis manuell berechnen. ```{r eval=T} gop2 <- gop2 %>% group_by(state) %>% mutate(vote_prop=votes/sum(votes)) # Neuer Datensatz (mit dem "alten" Namen) mit der manuellen Berechnung ggplot(data=gop2) + geom_bar(mapping=aes(x=state, y=vote_prop, fill=candidate), stat='identity', position='dodge')+ ylab("Votes (%)") ``` Beachtet hier bitte, dass `group_by %>% summarize` den Datensatz im Ganzen verändert und `group_by %>% mutate` dem bestehenden Datensatz lediglich eine weitere Spalte/Variable hinzufügt (also in diesem Fall das Eregbnis der manuellen Berechnung `vote_prop=votes/sum(votes)`). ### 5.5. Linien-/Kurvendiagramme Zu guter Letzt ein weiterer Evergreen der Datenvisualisierung: das Liniendiagramm. Wir können zum Beispiel den Erfolg von Donald Trump darstellen, indem wir seinen Stimmenanteil über Zeit betrachten. Zuerst kombinieren wir die Ergebnisse pro Bundesstaat mit den Terminen der Primaries (hierfür verwende euch bisher unbekannte, da noch nicht besprochene, Funktionen zum Zusammemnführen von Daten): ```{r eval=T} schedule_url <- "https://raw.githubusercontent.com/houstondatavis/data-jam-august-2016/master/csv/primary_schedule.csv" schedule <- read_csv(schedule_url) # Der Datensatz "primary_schedule" stammt ebenfalls aus der Github-Seite des Houston meetups schedule <- schedule %>% mutate(date = as.Date(date, format="%m/%d/%y")) # hier transformieren wir die Daten der Vorwahlen in Monat-Tag-Jahr trump <- results_state %>% group_by(state, party) %>% mutate(vote_prop=votes/sum(votes)) %>% filter(candidate=="Donald Trump") # die Daten für die US Staaten werden gesubsettet um die Ergebnisse für Donald Trump zu isolieren, außerdem wird eine neue Variable ("vote_prop") mittels manueller Berechnung (votes/sum(votes)) erstellt. trump <- left_join(trump, schedule) # jetzt führen wir die Daten der Vorwahlen mit dem Trump-spezifischen Datensatz zusammen trump <- trump %>% group_by(date) %>% summarize(vote_prop=mean(vote_prop)) # gruppieren und zusammenfassen trump ``` Nehmt euch Zeit, den obigen Code zu verstehen. Zeile für Zeile! Das geht am besten, wenn ihr in einem eigenen `R`-Skript die Ausgabe jeder Zeile untersucht und die Ergebnisse und Auswirkungen jedes Befehls zurückverfolgt. ```{r eval=T} # plotting ggplot(trump) + geom_line(aes(x=date, y=vote_prop)) ``` Machen wir jetzt das selbe für mehr Kandidaten. Diesmal wählen wir die Demokratischen-Kandidaten aus: ```{r eval=T} dems <- results_state %>% filter(party=="Democrat") %>% left_join(schedule) dems <- dems %>% group_by(date, candidate) %>% summarize(votes=sum(votes)) %>% mutate(vote_prop=votes / sum(votes)) # plotting ggplot(dems) + geom_line(aes(x=date, y=vote_prop, colour=candidate)) ``` Bonusfrage: Im Code für den Trump-Plot wurde der Anteil in zwei Anweisungen berechnet (zuerst pro Staat, dann pro Datum). In dem Demokraten-Code wurde er nur pro Datum berechnet. Inwiefern spielt das eine Rolle? Ist eine der beiden Berechnungen genauer als die andere? Nur um euch einige der weiteren Möglichkeiten von `ggplot` zu zeigen, erstellen wir jetzt ein Diagramm aller republikanischen Vorwahlergebnisse am Super Tuesday (1. März): ```{r eval=T} super <- results_state %>% left_join(schedule) %>% filter(party=="Republican" & date=="2016-03-01") %>% group_by(state) %>% mutate(vote_prop=votes/sum(votes)) ggplot(super) + geom_bar(aes(x=candidate, y=vote_prop), stat='identity') + facet_wrap(~ state, nrow = 3) + coord_flip() ``` Ihr müsst nicht direkt alles verstehen. Das wird alles mit Zeit und Übung kommen. Wichtig ist hier nur eines: ihr müsst den Code sehen und zumindest die Logik dahinter verstehen. ### 5.6. Themes Die Anpassung von Dingen wie Hintergrundfarbe, Gitter usw. wird durch sogenannte Themes gehandhabt. `ggplot` hat zwei standardmäßige Themes: theme_grey (Standard) und theme_bw (für ein minimalistischeres Theme mit weißem Hintergrund). Das Paket `ggthemes` (wie immer: einmalig `install.packages("ggthemes")` und dann vor jeder Verwendung `library(ggthemes)`!) hat einige weitere Themes, darunter ein `economist`-Theme (basierend auf der Zeitung). Um ein Thema zu verwenden, müsst ihr das einfach nur als Codezeile definieren: ```{r eval=T} library(ggthemes) ggplot(trump) + geom_line(aes(x=date, y=vote_prop)) + theme_economist() ``` Mehr über Themes: * * * (hier werden auch nette und für euch verfügbare Daten verwendet. reinschauen und nachmachen!) ### 5.7. Karten! Geografische Informationen können in `ggplot` ähnlich wie Streudiagramme geplottet werden. Dafür werden einfach einfach Längen- und Breitengrad als x und y verwendet. Oftmals möchten wir Daten auf einer bestimmten Karte (eines Teils) der Welt plotten, z. B. um Standorte von Tweets zu plotten oder eine Karte mit Informationen pro Land oder Staat einzufärben. In `ggplot` wird das realisiert, indem die Umrisse der Länder geplottet werden. Das Paket enthält standardmäßig (sie werden bei der Paket-Installation mitgeliefert) Daten für die USA, die Welt und einige Länder wie Frankreich (nicht die EU oder Deutschland). Die Karten stammen original aus dem Paket `maps`. Das heißt ihr könnt in der Dokumentation von `maps` (wie immer: `?maps`) die Liste der Länder nachschauen. ```{r eval=T} states <- map_data("state") # die Namen der beim Paket (hier ggplot2) mitglieferten Daten (hier "state") findet ihr in der Dokumentation des jeweiligen Pakets und natürlich auf CRAN! head(states) ``` Wir können diese Daten sofort plotten, indem wir die Funktion `geom_polygon` verwenden. Wir geben x und y als Längen- und Breitengrad an, füllen nach Staaten und machen die Grenzen weiß. ```{r eval=T} ggplot(data = states) + geom_polygon(aes(x = long, y = lat, fill = region, group = group), color = "white") + coord_fixed(1.3) + guides(fill=FALSE) ``` Hinweis: Der Befehl `coord_fixed()` fixiert das Seitenverhältnis auf 1,3 und `guides(fill=FALSE)` verhindert dass eine Legende gezeichnet wird (in diesem Fall würde die Legende für jeden US Staat die zugeordnete Farbe auflisten).