R
: Transformation, Zusammenfassen und Visualisieren von Daten mit tidyDas 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:
Ein zentraler Aspekt des tidyverse
ist das Paket dplyr
(data-pliers), das die meisten der Funktionen enthält, die wir im Folgenden verwenden.
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.
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.
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.)
library(tidyvers)
Fehler in library(tidyvers) : es gibt kein Paket namens ‘tidyvers’
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):
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
## # A tibble: 3 x 4
## id gericht rechtstradition abstrakte_NK
## <dbl> <chr> <chr> <dbl>
## 1 1 Bundesverfassungsgericht civil law 1
## 2 2 Österreichischer Verfassungsgerichtshof civil law 1
## 3 3 US Supreme Court common law 0
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.
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 oder vom Comparatives Agendas Project. 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 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.
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
## # A tibble: 6 x 34
## X1 doc_id text docket_nb short_text date year pr_nr pr_date pr_text
## <dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
## 1 5745 201501… "\n … 2 BvE 1/… "Unzulaess… 13.0… 2015 <NA> <NA> <NA>
## 2 5748 201501… "\n … 1 BvR 93… "Regelung … 14.0… 2015 13/2… 11.03.… "\n …
## 3 5759 201501… "\n … 1 BvR 47… "Ein pausc… 27.0… 2015 14/2… 13.03.… "\n …
## 4 5769 201502… "\n … 1 BvR 47… "Auskunfts… 24.0… 2015 16/2… 18.03.… "\n …
## 5 5784 201503… "\n … 2 BvB 1/… "Hinweisbe… 19.0… 2015 <NA> <NA> <NA>
## 6 5788 201503… "\n … 1 BvR 28… "Unterschi… 24.0… 2015 26/2… 30.04.… "\n …
## # … with 24 more variables: pr_dummy <dbl>, pr_dec_diff <dbl>,
## # decision_type <dbl>, second_senate_dummy <dbl>, bvr <dbl>, bvl <dbl>,
## # bvq <dbl>, bvc <dbl>, bve <dbl>, bvf <dbl>, bvb <dbl>, bvg <dbl>,
## # bvk <dbl>, bvh <dbl>, bvm <dbl>, bvn <dbl>, pbvu <dbl>, bvp <dbl>,
## # unanimous <dbl>, sep_op <dbl>, oral <dbl>, status_quo <dbl>,
## # lower_co_uncons <dbl>, deadline <dbl>
weapons <- read_csv(url_weapons)
tail(weapons) # tail() zeigt die letzten zehn beobachtungen des datensatzes
## # A tibble: 6 x 9
## Question Start End Pollster Population Support `Republican Supp…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 stricter-gu… 2/18/… 2/20/… YouGov Registered V… 58 36
## 2 stricter-gu… 2/16/… 2/19/… Quinnipiac Registered V… 66 34
## 3 stricter-gu… 2/22/… 2/26/… Morning Co… Registered V… 68 53
## 4 stricter-gu… 3/3/18 3/5/18 Quinnipiac Registered V… 63 39
## 5 stricter-gu… 3/4/18 3/6/18 YouGov Registered V… 61 42
## 6 stricter-gu… 3/1/18 3/5/18 Morning Co… Registered V… 69 57
## # … with 2 more variables: Democratic Support <dbl>, URL <chr>
(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.
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):
age21 <- filter(weapons, Question == 'age-21') # "Question" ist die Variable und "age-21" die Ausprägung
age21
## # A tibble: 7 x 9
## Question Start End Pollster Population Support `Republican Suppo…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/… CNN/SSRS Registered V… 72 61
## 2 age-21 2/27/18 2/28/… NPR/Ipsos Adults 82 72
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59
## 4 age-21 2/22/18 2/26/… Harris Inter… Registered V… 84 77
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered V… 78 63
## 6 age-21 3/4/18 3/6/18 YouGov Registered V… 72 65
## 7 age-21 3/1/18 3/5/18 Morning Cons… Registered V… 76 72
## # … with 2 more variables: Democratic Support <dbl>, URL <chr>
verfb <- filter(court, bvf == 1)
verfb
## # A tibble: 13 x 34
## X1 doc_id text docket_nb short_text date year pr_nr pr_date pr_text
## <dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
## 1 5893 201507… "\n … 1 BvF 2/… "Keine Ges… 21.0… 2015 57/2… 21.07.… "\n …
## 2 5931 201508… "\n … 2 BvF 1/… "Einstweil… 26.0… 2015 63/2… 1.09.2… "\n …
## 3 6086 201602… "\n … 2 BvF 1/… "Einstweil… 15.0… 2016 63/2… 1.09.2… "\n …
## 4 6294 201607… "\n … 2 BvF 1/… "Einstweil… 20.0… 2016 63/2… 1.09.2… "\n …
## 5 6449 201612… "\n … 2 BvF 1/… "Wiederhol… 22.1… 2016 <NA> <NA> <NA>
## 6 6613 201706… "\n … 2 BvF 1/… "Wiederhol… 13.0… 2017 <NA> <NA> <NA>
## 7 6781 201711… "BUND… 2 BvF 1/… "Einstellu… 21.1… 2017 <NA> <NA> <NA>
## 8 6787 201712… "\n … 2 BvF 1/… "Erneute W… 1.12… 2017 <NA> <NA> <NA>
## 9 6865 201803… "Leit… 1 BvF 1/… "Verpflich… 21.0… 2018 32/2… 4.05.2… "\n …
## 10 6926 201805… "BUND… 2 BvF 1/… "Erneute W… 14.0… 2018 <NA> <NA> <NA>
## 11 7018 201809… "Leit… 2 BvF 1/… "Vorschrif… 19.0… 2018 74/2… 19.09.… "\n …
## 12 7054 201811… "BUND… 2 BvF 1/… "Einstellu… 13.1… 2018 <NA> <NA> <NA>
## 13 7764 202011… "BUND… 2 BvF 2/… "Erfolglos… 3.11… 2020 100/… 18.11.… "\n …
## # … with 24 more variables: pr_dummy <dbl>, pr_dec_diff <dbl>,
## # decision_type <dbl>, second_senate_dummy <dbl>, bvr <dbl>, bvl <dbl>,
## # bvq <dbl>, bvc <dbl>, bve <dbl>, bvf <dbl>, bvb <dbl>, bvg <dbl>,
## # bvk <dbl>, bvh <dbl>, bvm <dbl>, bvn <dbl>, pbvu <dbl>, bvp <dbl>,
## # unanimous <dbl>, sep_op <dbl>, oral <dbl>, status_quo <dbl>,
## # lower_co_uncons <dbl>, deadline <dbl>
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:
filter(weapons, Question == 'age-21' & Support >= 80)
## # A tibble: 2 x 9
## Question Start End Pollster Population Support `Republican Suppo…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 age-21 2/27/18 2/28/… NPR/Ipsos Adults 82 72
## 2 age-21 2/22/18 2/26/… Harris Inter… Registered V… 84 77
## # … with 2 more variables: Democratic Support <dbl>, URL <chr>
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.
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)!
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:
select(age21, Population, Support, Pollster)
## # A tibble: 7 x 3
## Population Support Pollster
## <chr> <dbl> <chr>
## 1 Registered Voters 72 CNN/SSRS
## 2 Adults 82 NPR/Ipsos
## 3 Adults 67 Rasmussen
## 4 Registered Voters 84 Harris Interactive
## 5 Registered Voters 78 Quinnipiac
## 6 Registered Voters 72 YouGov
## 7 Registered Voters 76 Morning Consult
Ihr könnt auch einen Bereich von Spalten angeben, z. B. alle Spalten von Support bis Democratic Support:
select(age21, Support:`Democratic Support`)
## # A tibble: 7 x 3
## Support `Republican Support` `Democratic Support`
## <dbl> <dbl> <dbl>
## 1 72 61 86
## 2 82 72 92
## 3 67 59 76
## 4 84 77 92
## 5 78 63 93
## 6 72 65 80
## 7 76 72 86
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:
select(age21, Pollster, rep=`Republican Support`, dem=`Democratic Support`)
## # A tibble: 7 x 3
## Pollster rep dem
## <chr> <dbl> <dbl>
## 1 CNN/SSRS 61 86
## 2 NPR/Ipsos 72 92
## 3 Rasmussen 59 76
## 4 Harris Interactive 77 92
## 5 Quinnipiac 63 93
## 6 YouGov 65 80
## 7 Morning Consult 72 86
Wenn ihr nur Spalten umbenennen möchten, könnt ihr die Funktion rename()
verwenden:
rename(age21, start_date = Start, end_date = End) # hier nennen wir die Variablen Start und End in start_date und end_date um
## # A tibble: 7 x 9
## Question start_date end_date Pollster Population Support `Republican Sup…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered … 72 61
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59
## 4 age-21 2/22/18 2/26/18 Harris Int… Registered … 84 77
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered … 78 63
## 6 age-21 3/4/18 3/6/18 YouGov Registered … 72 65
## 7 age-21 3/1/18 3/5/18 Morning Co… Registered … 76 72
## # … with 2 more variables: Democratic Support <dbl>, URL <chr>
Natürlich könnt ihr Variablen auch löschen. Dazu müsst ihr Minuszeichen vor die Variablennamen setzen:
select(age21, -Question, -URL)
## # A tibble: 7 x 7
## Start End Pollster Population Support `Republican Sup… `Democratic Sup…
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 2/20/… 2/23/… CNN/SSRS Registered… 72 61 86
## 2 2/27/… 2/28/… NPR/Ipsos Adults 82 72 92
## 3 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 2/22/… 2/26/… Harris In… Registered… 84 77 92
## 5 3/3/18 3/5/18 Quinnipiac Registered… 78 63 93
## 6 3/4/18 3/6/18 YouGov Registered… 72 65 80
## 7 3/1/18 3/5/18 Morning C… Registered… 76 72 86
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:
age21 <- arrange(age21, Population, -Support)
age21
## # A tibble: 7 x 9
## Question Start End Pollster Population Support `Republican Suppo…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 age-21 2/27/18 2/28/… NPR/Ipsos Adults 82 72
## 2 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59
## 3 age-21 2/22/18 2/26/… Harris Inter… Registered V… 84 77
## 4 age-21 3/3/18 3/5/18 Quinnipiac Registered V… 78 63
## 5 age-21 3/1/18 3/5/18 Morning Cons… Registered V… 76 72
## 6 age-21 2/20/18 2/23/… CNN/SSRS Registered V… 72 61
## 7 age-21 3/4/18 3/6/18 YouGov Registered V… 72 65
## # … with 2 more variables: Democratic Support <dbl>, URL <chr>
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!
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.
age21 <- mutate(age21, party_diff = abs(`Republican Support` - `Democratic Support`))
select(age21, Question, Pollster, party_diff)
## # A tibble: 7 x 3
## Question Pollster party_diff
## <chr> <chr> <dbl>
## 1 age-21 NPR/Ipsos 20
## 2 age-21 Rasmussen 17
## 3 age-21 Harris Interactive 15
## 4 age-21 Quinnipiac 30
## 5 age-21 Morning Consult 14
## 6 age-21 CNN/SSRS 25
## 7 age-21 YouGov 15
age21 <- arrange(age21, Population, -Support)
age21
## # A tibble: 7 x 10
## Question Start End Pollster Population Support `Republican Suppo…
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 age-21 2/27/18 2/28/… NPR/Ipsos Adults 82 72
## 2 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59
## 3 age-21 2/22/18 2/26/… Harris Inter… Registered V… 84 77
## 4 age-21 3/3/18 3/5/18 Quinnipiac Registered V… 78 63
## 5 age-21 3/1/18 3/5/18 Morning Cons… Registered V… 76 72
## 6 age-21 2/20/18 2/23/… CNN/SSRS Registered V… 72 61
## 7 age-21 3/4/18 3/6/18 YouGov Registered V… 72 65
## # … with 3 more variables: Democratic Support <dbl>, URL <chr>,
## # party_diff <dbl>
Um eine Variable in derselben Spalte zu transformieren (umzukodieren), könnt ihr einfach einen vorhandenen Namen in mutate()
verwenden, um ihn zu überschreiben.
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:
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:
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.
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()
.
Zur Wiederholung und Einübung laden und bearbeiten wir unsere Waffenumfragedaten:
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
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults 44 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
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):
weapons %>% group_by(Question)
## # A tibble: 57 x 8
## # Groups: Question [8]
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults 44 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
weapons
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults 44 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
Wie ihr sehen könnt, haben sich die Daten nicht geändert. Wir haben lediglich eine Gruppierung (8 Fragen, 8 Gruppen) vorgenommen.
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:
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
## # A tibble: 8 x 2
## Question Support
## <chr> <dbl>
## 1 background-checks 87.4
## 2 mental-health-own-gun 85.8
## 3 age-21 75.9
## 4 ban-high-capacity-magazines 67.3
## 5 stricter-gun-laws 66.5
## 6 ban-assault-weapons 61.8
## 7 arm-teachers 42
## 8 repeal-2nd-amendment 10
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:
weapons_sum <- weapons %>% group_by(Question) %>% summarize(n=n(), mean=mean(Support), sd=sd(Support))
weapons_sum
## # A tibble: 8 x 4
## Question n mean sd
## <chr> <int> <dbl> <dbl>
## 1 age-21 7 75.9 6.01
## 2 arm-teachers 6 42 1.55
## 3 background-checks 7 87.4 7.32
## 4 ban-assault-weapons 12 61.8 6.44
## 5 ban-high-capacity-magazines 7 67.3 3.86
## 6 mental-health-own-gun 6 85.8 5.46
## 7 repeal-2nd-amendment 1 10 NA
## 8 stricter-gun-laws 11 66.5 5.15
Wie ihr jetzt sehen könnt, hat einer der Werte einen fehlenden Wert (NA) für die Standardabweichung. Warum?
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:
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
## # A tibble: 57 x 10
## # Groups: Question [8]
## Question Start End Pollster Population Support Rep Dem avg_support
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 age-21 2/20/… 2/23… CNN/SSRS Registered… 72 61 86 75.9
## 2 age-21 2/27/… 2/28… NPR/Ipsos Adults 82 72 92 75.9
## 3 age-21 3/1/18 3/4/… Rasmussen Adults 67 59 76 75.9
## 4 age-21 2/22/… 2/26… Harris In… Registered… 84 77 92 75.9
## 5 age-21 3/3/18 3/5/… Quinnipiac Registered… 78 63 93 75.9
## 6 age-21 3/4/18 3/6/… YouGov Registered… 72 65 80 75.9
## 7 age-21 3/1/18 3/5/… Morning C… Registered… 76 72 86 75.9
## 8 arm-teac… 2/23/… 2/25… YouGov/Hu… Registered… 41 69 20 42
## 9 arm-teac… 2/20/… 2/23… CBS News Adults 44 68 20 42
## 10 arm-teac… 2/27/… 2/28… Rasmussen Adults 43 71 24 42
## # … with 47 more rows, and 1 more variable: diff <dbl>
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.
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:
weapons_avgSup %>% ungroup() %>% summarize(diff=sd(diff))
## # A tibble: 1 x 1
## diff
## <dbl>
## 1 5.19
(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?
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:
weapons %>% group_by(Question, Population) %>% summarize(Support=mean(Support))
## # A tibble: 15 x 3
## # Groups: Question [8]
## Question Population Support
## <chr> <chr> <dbl>
## 1 age-21 Adults 74.5
## 2 age-21 Registered Voters 76.4
## 3 arm-teachers Adults 42.7
## 4 arm-teachers Registered Voters 41.3
## 5 background-checks Adults 84.5
## 6 background-checks Registered Voters 88.6
## 7 ban-assault-weapons Adults 62.5
## 8 ban-assault-weapons Registered Voters 61.6
## 9 ban-high-capacity-magazines Adults 73
## 10 ban-high-capacity-magazines Registered Voters 66.3
## 11 mental-health-own-gun Adults 92
## 12 mental-health-own-gun Registered Voters 84.6
## 13 repeal-2nd-amendment Registered Voters 10
## 14 stricter-gun-laws Adults 70
## 15 stricter-gun-laws Registered Voters 65.7
weapons
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults 44 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
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):
weapons %>% group_by(Question, Population) %>% summarize(Support=mean(Support)) %>% mutate(avg_support=mean(Support))
## # A tibble: 15 x 4
## # Groups: Question [8]
## Question Population Support avg_support
## <chr> <chr> <dbl> <dbl>
## 1 age-21 Adults 74.5 75.4
## 2 age-21 Registered Voters 76.4 75.4
## 3 arm-teachers Adults 42.7 42
## 4 arm-teachers Registered Voters 41.3 42
## 5 background-checks Adults 84.5 86.6
## 6 background-checks Registered Voters 88.6 86.6
## 7 ban-assault-weapons Adults 62.5 62.0
## 8 ban-assault-weapons Registered Voters 61.6 62.0
## 9 ban-high-capacity-magazines Adults 73 69.7
## 10 ban-high-capacity-magazines Registered Voters 66.3 69.7
## 11 mental-health-own-gun Adults 92 88.3
## 12 mental-health-own-gun Registered Voters 84.6 88.3
## 13 repeal-2nd-amendment Registered Voters 10 10
## 14 stricter-gun-laws Adults 70 67.8
## 15 stricter-gun-laws Registered Voters 65.7 67.8
weapons
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults 44 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
Gibt es eine Möglichkeit, auch den Mittelwert der einzelnen Umfragen zu addieren?
Zusammenfassungsfunktionen in R
geben standardmäßig NA zurück, wenn einer der zusammenzufassenden Werte fehlt:
mean(c(3,4,NA,6))
## [1] NA
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))
weapons2 <- weapons %>% mutate(Support=ifelse(Pollster == "CBS News", NA, Support))
weapons2
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults NA 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
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:
weapons2 %>% group_by(Question) %>% summarize(Support=mean(Support))
## # A tibble: 8 x 2
## Question Support
## <chr> <dbl>
## 1 age-21 75.9
## 2 arm-teachers NA
## 3 background-checks NA
## 4 ban-assault-weapons NA
## 5 ban-high-capacity-magazines 67.3
## 6 mental-health-own-gun 85.8
## 7 repeal-2nd-amendment 10
## 8 stricter-gun-laws NA
weapons2
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults NA 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
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:
weapons2 %>% group_by(Question) %>% summarize(Support=mean(Support, na.rm=T))
## # A tibble: 8 x 2
## Question Support
## <chr> <dbl>
## 1 age-21 75.9
## 2 arm-teachers 41.6
## 3 background-checks 89.5
## 4 ban-assault-weapons 62.5
## 5 ban-high-capacity-magazines 67.3
## 6 mental-health-own-gun 85.8
## 7 repeal-2nd-amendment 10
## 8 stricter-gun-laws 66.6
weapons2
## # A tibble: 57 x 8
## Question Start End Pollster Population Support Rep Dem
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 age-21 2/20/18 2/23/18 CNN/SSRS Registered Vo… 72 61 86
## 2 age-21 2/27/18 2/28/18 NPR/Ipsos Adults 82 72 92
## 3 age-21 3/1/18 3/4/18 Rasmussen Adults 67 59 76
## 4 age-21 2/22/18 2/26/18 Harris Intera… Registered Vo… 84 77 92
## 5 age-21 3/3/18 3/5/18 Quinnipiac Registered Vo… 78 63 93
## 6 age-21 3/4/18 3/6/18 YouGov Registered Vo… 72 65 80
## 7 age-21 3/1/18 3/5/18 Morning Consu… Registered Vo… 76 72 86
## 8 arm-teache… 2/23/18 2/25/18 YouGov/Huffpo… Registered Vo… 41 69 20
## 9 arm-teache… 2/20/18 2/23/18 CBS News Adults NA 68 20
## 10 arm-teache… 2/27/18 2/28/18 Rasmussen Adults 43 71 24
## # … with 47 more rows
In fünften Teil des Tutorials arbeiten wir mit dem Paket ggplot2
. Hier noch ein paar generelle Hinweise zu diesem Paket:
ggplot2
in der R Graph Gallery. Dort findet ihr auch den zugehörigen Code. Damit könnt ihr dann die Beispiele mit euren eigenen Daten nachbauen.ggplot2
Entwickler über die "Grammatik von Graphiken".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", 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):
install.packages("ggplot2") # beim ersten mal nicht vergessen!
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
## # A tibble: 51 x 9
## fips area_name population pop_change over65 female anglo_americans college
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1000 Alabama 4849377 1.4 15.3 51.5 69.7 22.6
## 2 2000 Alaska 736732 3.7 9.4 47.4 66.9 27.5
## 3 4000 Arizona 6731484 5.3 15.9 50.3 83.7 26.9
## 4 5000 Arkansas 2966369 1.7 15.7 50.9 79.7 20.1
## 5 6000 California 38802500 4.2 12.9 50.3 73.2 30.7
## 6 8000 Colorado 5355866 6.5 12.7 49.8 87.7 37
## 7 9000 Connecticut 3596677 0.6 15.5 51.2 81.2 36.5
## 8 10000 Delaware 935614 4.2 16.4 51.6 70.8 28.9
## 9 11000 District O… 658893 9.5 11.3 52.6 43.6 52.4
## 10 12000 Florida 19893297 5.8 19.1 51.1 77.8 26.4
## # … with 41 more rows, and 1 more variable: income <dbl>
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.
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.
ggplot
-SyntaxDamit 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:
ggplot(data=facts_state) +
geom_point(mapping=aes(x=college, y=income))
Falsch:
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:
ggplot(facts_state) +
geom_point(aes(x=college, y=income))
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:
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:
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:
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).
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:
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.
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:
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" 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.
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
## # A tibble: 8 x 4
## # Groups: state, party [1]
## state party candidate votes
## <chr> <chr> <chr> <dbl>
## 1 New Hampshire Republican Ben Carson 6509
## 2 New Hampshire Republican Carly Fiorina 11706
## 3 New Hampshire Republican Chris Christie 21069
## 4 New Hampshire Republican Donald Trump 100406
## 5 New Hampshire Republican Jeb Bush 31310
## 6 New Hampshire Republican John Kasich 44909
## 7 New Hampshire Republican Marco Rubio 30032
## 8 New Hampshire Republican Ted Cruz 33189
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.
ggplot(data=nh_gop) + geom_bar(mapping=aes(x=candidate, y=votes), stat='identity')
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:
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:
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:
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:
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:
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.
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)
).
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):
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
## # A tibble: 17 x 2
## date vote_prop
## <date> <dbl>
## 1 2016-02-01 0.243
## 2 2016-02-09 0.360
## 3 2016-02-20 0.325
## 4 2016-02-23 0.461
## 5 2016-03-01 0.341
## 6 2016-03-05 0.343
## 7 2016-03-08 0.397
## 8 2016-03-15 0.413
## 9 2016-03-22 0.357
## 10 2016-04-05 0.360
## 11 2016-04-19 0.604
## 12 2016-04-26 0.602
## 13 2016-05-03 0.546
## 14 2016-05-10 0.752
## 15 2016-05-17 0.666
## 16 2016-05-24 0.789
## 17 2016-06-07 0.770
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.
# 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:
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):
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.
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:
library(ggthemes)
ggplot(trump) +
geom_line(aes(x=date, y=vote_prop)) +
theme_economist()
Mehr über Themes:
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.
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)
## long lat group order region subregion
## 1 -87.46201 30.38968 1 1 alabama <NA>
## 2 -87.48493 30.37249 1 2 alabama <NA>
## 3 -87.52503 30.37249 1 3 alabama <NA>
## 4 -87.53076 30.33239 1 4 alabama <NA>
## 5 -87.57087 30.32665 1 5 alabama <NA>
## 6 -87.58806 30.32665 1 6 alabama <NA>
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ß.
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).