data > opinion

Tom Alby

4 Einführung in R

2020-10-30


Was ist R?

R ist eine Umgebung zur statistischen Datenverarbeitung und gleichzeitig eine Programmiersprache. Sie wurde 1996 veröffentlicht. Für die Nerds unter den Lesern: R ist eine Open Source-Implementierung der Sprache S (was man sich nicht unbedingt merken muss, aber auf einer Statistiker-Party, wenn es sowas gibt, kommt das sicherlich gut an). Auch für Nerds interessant ist die Tatsache, dass R im Kern eine funktionale Programmiersprache ist. Viele populäre Programmiersprachen sind heute objektorientiert organisiert, bei R steht hingegen die Funktion im Fokus. Was das genau bedeutet, wird in den kommenden Kapiteln noch genau behandelt.

Im Gegensatz zu SPSS, das mittlerweile zu IBM gehört, ist R eine kostenlose Lösung. Ein weiterer Unterschied zu SPSS: R ist sozusagen “nackt”, es kommt nicht mit einer graphischen Benutzeroberfläche wie SPSS, bei der vieles mit ein paar Klicks abgehandelt werden kann. Einige Protagonisten der Data Science gehen so weit zu sagen, dass man Data Science nicht mit Klicks in einer GUI abwickeln kann.

Programme, die in R geschrieben sind, werden nicht kompiliert, es handelt sich bei R also um eine dynamische Programmiersprache, deren Anweisungen in Echtzeit interpretiert werden.

R “pur”

R pur

Nach dem Start begrüßt R den Nutzer mit einer Eingabeaufforderung, wie ein UNIX/Linux-Terminal oder die altehrwürdige DOS-Benutzeroberfläche. Einige Befehle können auch mit dem Menü ausgeführt werden, aber die meiste Zeit verbringt der Analyst hier mit der sogenannten Konsole. Befehl eingeben, Ausgabe ansehen. Dies ist die interaktive Art, R zu nutzen.

Möchte man ein längeres Programm schreiben, so ergibt es jedoch wenig Sinn, den Programm-Code jedes Mal neu einzugeben. Daher werden Programme meistens in Dateien geschrieben, zum Beispiel hello_world.R, und diese Dateien dann in der R-Konsole ausgeführt, nachdem sie geladen wurden. R selbst bietet keinen Editor, aber viele Editoren bieten Erweiterungen für R an, zum Beispiel der altehrwürdige Emacs.

Die Entwicklungsumgebung RStudio

@burk2019 haben es wunderschön formuliert:

“Wenn R ein Pferd ist, dann ist RStudio ein Sattel.”

Zwar ist R auch in RStudio immer noch “nackiger” als SPSS, aber schon um einiges komfortabler als das komplett “nackte” R. RStudio kann als integrierte Entwicklungsumgebung (oder IDE für Integrated Development Environment) für R angesehen werden. Alle wichtigen Funktionen und Programme für die Entwicklung von Software oder das Handling von Daten sind über eine Schnittstelle bedienbar. Es findet sozusagen kein “Medienbruch” statt, da man nicht zwischendurch zum Dateibrowser oder zum Terminal wechseln muss. Wichtig: Es muss zunächst R installiert werden (siehe vorheriger Abschnitt), dann RStudio.

RStudio GUI

Nachdem R und RStudio installiert und gestartet ist, erscheint RStudio mit drei beziehungsweise vier Fenstern, deren Funktionen unterschiedlich konfiguriert werden können. Die vier Fenster in der Standardkonfiguration haben folgende Funktionen:

  • Links oben ist der eigene Code sowie der Inhalt von Data Frames zu finden (dieses Fenster ist nicht beim Start zu sehen, sondern erst wenn eine Code-Datei oder ein Data Frame geöffnet wird)
  • Rechts oben werden alle Objekte aufgeführt.
  • Links unten ist die R-Konsole (die man auch sehen würde, wenn man nur R nutzte und nicht RStudio dazu) sowie ein Terminal-Fenster (das direkten Zugriff auf das darunter liegende Betriebssystem erlaubt)
  • Rechts unten sind die Daten im Dateisystem sowie Plots und eine Hilfefunktion zu sehen

in dem folgenden Video kann ein Rundgang durch das RStudio-Interface angesehen werden:

Eine Besonderheit bei RStudio sind die Projekte. Wird ein Projekt angelegt, so wird automatisch ein Verzeichnis erstellt und beim Wechsel von einem Projekt zum nächsten auch der jeweilige Workspace geladen. Die Projekte finden sich ganz oben rechts.

Warum R? Warum nicht Python?

Es ist kein “Entweder-oder”. Zunächst einmal die Fakten:

  • R ist Openn Source (wie Python auch)
  • Es funktioniert auf mehreren Plattformen (wie Python auch)
  • Es ist eine Statistik-Programmiersprache (ist Python nicht, aber kann erweitert werden)
  • Es können wunderschöne Grafiken damit produziert werden (das kann Python auch)
  • kann mit anderen Sprachen verbunden werden (das kann Python auch, leicht aufwändiger)
  • Flexibel und erweiterbar (wie Python auch)
  • nach wenigen Minuten Einführung können die ersten statistischen Analysen durchgeführt werden (das geht mit Python nicht)

Auch wenn die Python-Jünger sagen, dass Python alles könne, so ist Python zunächst einmal eine mächtige allgemeine Programmiersprache, keine Sprache, die sich auf Statistik fokussiert. Natürlich existieren viele Erweiterungen für Python, um weitere Statistik-Funktionen hinzuzufügen, aber genau das ist der Punkt: Es sind Erweiterungen und keine grundsätzlichen Bestandteile wie bei R. Demgegenüber ist bei R so gut wie alles in der Standardinstallation enthalten, was man für die einfache Arbeit in der Statistik benötigt, es ist halt genau dafür ausgelegt. Aber auch R kann erweitert werden. Und auch für R existieren viele Libraries, die Funktionen nachrüsten, die Python eventuell schon mit Bordmitteln beherrscht. Libraries sind sozusagen Kollektionen von Routinen, die die Funktionen einer Programmiersprache erweitern. Mit Python und R kann man zum Beispiel mit Bordmitteln keine Webseite crawlen, das geht nur mit den zusätzlichen Libraries. In R werden Libraries auch packages oder Pakete genannt.

Manches kann Python mit seinen Erweiterungen einfach besser, zum Beispiel ist BeautifulSoup momentan noch ein viel besserer Web Scraper als Rvest es ist. Das Gute ist, dass sich beide Sprachen in RStudio wunderbar kombinieren lassen. Dazu später mehr. Ein großer Vorteil ist, dass R von Statistikern für Statistiker entwickelt wurde, also genau deren Bedürfnisse im Blick hatten. Manche bösen Zungen behaupten allerdings auch, dass dies der große Nachteil von R ist. Wiederum andere sagen, dass R süchtig macht und ungesund ist:

“Using R is a bit akin to smoking. The beginning is difficult, one may get headaches and even gag the first few times. But in the long run,it becomes pleasurable and even addictive. Yet, deep down, for those willing to be honest, there is something not fully healthy in it.” (Francois Pinard)

Natürlich stimmt das nicht. Zumindest nicht ganz. Vielleicht ein bisschen.

Wenn es um die Popularität von Programmiersprachen geht, so werden häufig Statistiken von Stack Overflow angeführt, in denen die Anzahl der Fragen/Antworten als Indikator für die Häufigkeit der Anwendung einer Programmiersprache genommen werden. Das klingt auf den ersten Blick sinnvoll, aber wenn man genauer darüber nachdenkt, dann könnte die Anzahl der Fragen auch ein Indikator dafür sein, wie viel Probleme Nutzer mit einer Sprache haben.

Dies soll aber kein “Flame War” werden, da der Autor auch ein Python-Fan ist. Allerdings ist für das Lernen von Statistik R aus der Erfahrung besser geeignet. Das Gute ist: Wenn man die Konzepte einmal verstanden hat, so ist das zusätzliche Lernen von Python ein Kinderspiel. Nun ja, fast.

Installation, Konfiguration und Erweiterung von R und RStudio

Installation R

R kann von der R Project Website heruntergeladen werden.

Installation RStudio

Das Download-Paket findet sich auf der RStudio Website. Auch RStudio ist erst einmal kostenlos, für Unternehmen ist aber eine Lizenz empfehlenswert.

Man kann RStudio entweder als Desktop-Applikation installieren oder auf einem Server. Für die Server-Installation existieren wunderbare Amazon Machine Images von Louis Aslett für die schnelle Installation auf Amazon Web Services (AWS). Für ein paar Euro im Monat kann man sich somit eine RStudio-Instanz gönnen, die von jedem Browser aus erreichbar ist. Somit kann man zum Beispiel eine Analyse starten, den Rechner zuklappen, und den Server weiterhin rechnen lassen.

Alternative zur Desktop-Installation: rstudio.cloud

RStudio bietet auch eine Cloud-Lösung an; zum jetzigen Zeitpunkt ist die Nutzung noch kostenlos.

Erste Schritte mit R

R interaktiv

R kann wie ein Taschenrechner genutzt werden, indem Operationen in die Konsole eingegeben werden können:

7 + 4
## [1] 11

Das Ergebnis einer solchen Operation kann in einer Variable gespeichert werden. Eine Variable ist eines der einfachsten Objekte in R. Als Objekt wird alles bezeichnet, das der Nutzer erstellt. Eine Variable ist wie ein Container, in dem ein Wert abgespeichert werden kann. Wird der Variable ein neuer Wert zugewiesen, so ist der alte Wert nicht mehr abrufbar. In dem folgenden Beispiel wird der Variable “ergebnis” das Ergebnis einer Rechenoperation zugewiesen:

ergebnis <- 7 + 4

Wird das Ergebnis einer Rechenoperation in einer Variable gespeichert, so können wir das Ergebnis nicht mehr sehen. Wir müssen den Variablennamen eingeben, um das Ergebnis sehen zu können:

ergebnis
## [1] 11

Das kann geändert werden, indem der Ausdruck in Klammern gesetzt wird:

(ergebnis <- 7 + 4)
## [1] 11

Bei R muss man Groß- und Kleinschreibung beachten:

(Ergebnis <- 7+5)
## [1] 12
(ergebnis <- 2+3)
## [1] 5
Ergebnis-ergebnis
## [1] 7

Bei der Wahl von Variablennamen sollte ein einheitlicher Ansatz gewählt werden, zum Beispiel camelCase (jedes Wort in einer Variable wird durch einen großen Anfangsbuchstaben markiert) oder snail_case (durch Unterstriche getrennt) oder durch Punkte getrennt (getrennt.durch.punkte). Nicht erlaubt ist ein -. Wie in dem vorherigen Beispiel zu sehen, wird das wie ein Minus-Zeichen behandelt. Auch ein Leerzeichen ist nicht möglich. Umlaute sind nicht möglich, funktionieren dann aber nicht unbedingt auf jedem System, was nachteilig ist, wenn man seinen Code an andere Nutzer weitergeben will.

Programmierer sind faul

Code sollte immer kommentiert sein, auch wenn man als Programmierer gerne glaubt, dass der Code selbsterklärend sei. Spätestens aber, wenn man nach ein paar Monaten oder Jahren seinen Code noch mal öffnet, rächt sich das Fehlen von Kommentaren. Kommentare werden mit einem Hash gekennzeichnet, so dass Code darin nicht ausgeführt wird.

# Hier steht ein Kommentar
2*4+2
## [1] 10

Von Programmieren wird aber nicht nur erwartet, dass sie gegen ihre eigene Faulheit ankämpfen, sie werden dafür auch stark von R und RStudio unterstützt. Gibt man zum Beispiel nur “sum” ein und drückt dann die Tabulator-Taste, so machen sowohl R als auch RStudio Vorschläge. Nutzt man die Pfeiltasten nach oben und unten, so kann man vorherige Befehle sehen, die man eingegeben hat. Mit etwas Übung kann die Arbeit in R somit erheblich beschleunigt werden.

Ist ein Befehl nicht vollständig, dann wirft R netterweise keine Fehlermeldung aus, sondern zeigt durch das Zeichen “+”, dass man doch bitte noch etwas hinzufügen soll:

Wird Hilfe benötigt, so kann man auch einfach nur ein ? vor einen Befehl schreiben:

?summary

Datentypen

In unseren bisherigen Beispielen wurden den Variablen ausschließlich Zahlen zugeordnet. In der Sprache R kann eine Variable jederzeit einen anderen Typ annehmen, in dem folgenden Beispiel wird x eine Zahl zugeordnet:

(x <- 4)
## [1] 4

Nun wird x eine Zeichenkette, häufig String genannt, zugeordnet:

(x <- "Hello World")
## [1] "Hello World"

Diese dynamische Typisierung, bei der der Variablentyp nicht vorab deklariert werden muss und die Variable jede Art von Typ annehmen kann, auch während des Programms, hat Vor- und Nachteile. Der Vorteil ist, dass man schnell mal etwas ändern kann, und der Nachteil ist, dass dieses “schnell mal etwas ändern” auch schnell zu Fehlern führen kann.

Übrigens, wenn die Zahl in Anführungszeichen gesetzt wird, dann passiert folgendes:

x <- "4"
str(x)
##  chr "4"

Die Zahl ist plötzlich keine Zahl mehr, sondern auch eine Zeichenkette. Mit dem Befehl “str” wird die Struktur eines Objekts angezeigt. An diesem Beispiel wird übrigens auch ein Prinzip von Befehlen in R deutlich: Einem Befehl folgen die Parameter, die in Klammern gesetzt sind. Dies ist die einfachste Möglichkeit, einen Befehl auszuführen.

Warum sind Datentypen überhaupt wichtig? Weil R wissen muss, was mit bestimmten Objekten gemacht werden kann und was nicht. Ein Beispiel hatten wir schon oben gesehen; sobald eine Zahl zum Character wird, kann sie nicht mehr zum Rechnen verwendet werden. R unterscheidet die folgenden Datentypen:

  • Numeric: Eine Zahl wie 1 oder 3,42 (tatsächlich kann R auch noch zwischen Integern und Double unterscheiden)
  • Character: Eine Zeichenkette
  • Logical: TRUE oder FALSE
  • Factor: Wird für kategoriale Variablen verwendet; dabei wird ein Integerwert gespeichert und jedem Integerwert zum Beispiel ein Character. Somit wird Speicher gespart.

Schauen wir uns nun ein paar Operationen mit den Datentypen an:

str(x)
##  chr "4"
mode(x)
## [1] "character"

Der Datentyp kann jederzeit durch einen as.[gewünschter Datentyp] geändert werden, sofern die Operation unterstützt werden kann.

x_char <- as.character(x)
mode(x_char)
## [1] "character"

Ein Datum kann verschiedene Formen annehmen, zum Beispiel 1. Mai 2019 oder 01/05/2019 usw. In dem letzten Fall ist allerdings nicht unbedingt klar, ob es sich um den 1.5. oder den 5.1. handelt, denn in den USA wird erst der Monat genannt, dann der Tag. Ein solches Datumsformat hat aber auch den Nachteil, dass man es erst umwandeln muss, um damit Rechenoperationen durchzuführen, zum Beispiel wenn man wissen will, wie viele Tage zwischen dem 1. Mai und dem 27. April liegen. Eine Möglichkeit, um bequemer mit Daten zu arbeiten, ist der UNIX Timestamp, bei dem die Anzahl der Sekunden seit dem 1.1.1970 verwendet wird.

my_time <- 1561321833
str(my_time)
##  num 1.56e+09

Diese Zahl lässt sich dann relativ einfach umwandeln in ein richtiges Datum:

(my_date <- as.POSIXct(my_time, origin="1970-01-01"))
## [1] "2019-06-23 20:30:33 UTC"
str(my_date)
##  POSIXct[1:1], format: "2019-06-23 20:30:33"

Das Wissen über Datentypen ist auch deswegen wichtig, weil R beim Importieren selbst entscheidet, welchen Datentyp eine Variable hat, wenn es keine zusätzliche Spezifikation erhält. Mitunter führt dies zu Problemen, wenn die Daten nach dem Import dadurch nicht mehr richtig ausgewertet werden können. Siehe hierzu auch den Abschnitt über Datenimporte.

Vektoren

Vektoren sind nach den Variablen das nächsteinfachste Objekt in R. Sie sind eine eindimensionale Sammlung von Datenpunkten. Mit dem Befehl c() sagt man R, dass ein Vektor angelegt werden soll. Das c steht angeblich für “concatenate”.

x <- c(1,2,3,4)
x
## [1] 1 2 3 4

Wie oben bereits erwähnt sind Programmierer faule Menschen, sie versuchen alles zu vereinfachen, was öfter getan werden muss, so dass dieser Vektor auch einfacher erstellt werden kann:

x <- c(1:4)
x
## [1] 1 2 3 4

Bei der Ausgabe verwundert eventuell die Zahl vor der Ausgabe. Sie gibt den Index-Wert des ersten Werts an. Jeder Wert erhält seine eigene Indexnummer. Anders als viele anderen Programmiersprachen wird bei R bei 1 angefangen zu zählen, nicht bei 0.

x <- c(1:100)
x
##   [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
##  [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
##  [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
##  [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
##  [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
##  [91]  91  92  93  94  95  96  97  98  99 100

Vektoren können nur Daten des gleichen Typs enthalten.

x <- c(x,"Hallo Welt")
x
##   [1] "1"          "2"          "3"          "4"          "5"         
##   [6] "6"          "7"          "8"          "9"          "10"        
##  [11] "11"         "12"         "13"         "14"         "15"        
##  [16] "16"         "17"         "18"         "19"         "20"        
##  [21] "21"         "22"         "23"         "24"         "25"        
##  [26] "26"         "27"         "28"         "29"         "30"        
##  [31] "31"         "32"         "33"         "34"         "35"        
##  [36] "36"         "37"         "38"         "39"         "40"        
##  [41] "41"         "42"         "43"         "44"         "45"        
##  [46] "46"         "47"         "48"         "49"         "50"        
##  [51] "51"         "52"         "53"         "54"         "55"        
##  [56] "56"         "57"         "58"         "59"         "60"        
##  [61] "61"         "62"         "63"         "64"         "65"        
##  [66] "66"         "67"         "68"         "69"         "70"        
##  [71] "71"         "72"         "73"         "74"         "75"        
##  [76] "76"         "77"         "78"         "79"         "80"        
##  [81] "81"         "82"         "83"         "84"         "85"        
##  [86] "86"         "87"         "88"         "89"         "90"        
##  [91] "91"         "92"         "93"         "94"         "95"        
##  [96] "96"         "97"         "98"         "99"         "100"       
## [101] "Hallo Welt"

Aus den Zahlen sind auf einmal Zeichenketten geworden, allein dadurch, dass die Zeichenkette “Hallo Welt” angefügt wurde.

Listen

Listen sind eine Datenstruktur in R, die, anders als man sich üblicherweise eine Liste vorstellt, nicht nur aus einer Reihe von Werten bestehen können, sondern aus mehreren Reihen, die auch noch unterschiedliche Längen haben können. Dies ist bei einem Data Frame nicht möglich.

x <- c(1:4)
y <- c("1","2","3","4","5")
(my_list <- list(x,y))
## [[1]]
## [1] 1 2 3 4
## 
## [[2]]
## [1] "1" "2" "3" "4" "5"

Soll ein einzelner Wert aufgerufen werden, so müssen zwei Index-Werte angegeben werden:

my_list[[2]][2]
## [1] "2"

Wir werden mit Listen zunächst wenig zu tun haben.

Data Frames

Data Frames sind ein essentielles Konzept in R (und übrigens auch in Python). Ein Data Frame ist wie eine Art Excel-Tabelle mit Beobachtunge in den Zeilen und Variablen in den Spalten. Um das zu verdeutlichen, schauen wir uns einen der Data Frames an, die in R bereits mitgeliefert werden:

mtcars
##                      mpg cyl  disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
## Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
## Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
## Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
## Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
## Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
## Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
## Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
## Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
## Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
## Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
## Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
## Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
## Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
## Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
## Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
## Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
## Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
## AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
## Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
## Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
## Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
## Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
## Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
## Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
## Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
## Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
## Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

Mit str() können wir wieder die Struktur anzeigen lassen:

str(mtcars)
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

Durch die Eingabe von head() werden die ersten Zeilen des Data Frames angezeigt:

head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

Der Befehl “summary” gibt mehrere Statistiken aus. Auf die Bedeutung der einzelnen Statistiken wird auf den nächsten Seiten eingegangen:

summary(mtcars)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

Mit diesem Data Frame können wir nun die ersten Analysen starten. Wollen wir nur eine Spalte untersuchen, so wird der Data Frame, ein Dollar-Zeichen und dann die Spalte genannt, die untersucht werden soll.

summary(mtcars$mpg)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   10.40   15.43   19.20   20.09   22.80   33.90

Daten einlesen

Daten werden meistens nicht mitgeliefert, sondern müssen von extern eingelesen werden. Hier werden nun zwei typische Fälle vorgestellt. Der erste Fall ist das Einlesen aus einer CSV-Datei. CSV steht für Comma Seperated Value, wobei das Komma irreführend ist. Es kann ebenso gut ein Semikolon sein oder ein Tab.

read.csv("mtcars.csv")
##                      X  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
## 1            Mazda RX4 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
## 2        Mazda RX4 Wag 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
## 3           Datsun 710 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
## 4       Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
## 5    Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
## 6              Valiant 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
## 7           Duster 360 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
## 8            Merc 240D 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
## 9             Merc 230 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
## 10            Merc 280 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
## 11           Merc 280C 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
## 12          Merc 450SE 16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
## 13          Merc 450SL 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
## 14         Merc 450SLC 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
## 15  Cadillac Fleetwood 10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
## 16 Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
## 17   Chrysler Imperial 14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
## 18            Fiat 128 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
## 19         Honda Civic 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
## 20      Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
## 21       Toyota Corona 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
## 22    Dodge Challenger 15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
## 23         AMC Javelin 15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
## 24          Camaro Z28 13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
## 25    Pontiac Firebird 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
## 26           Fiat X1-9 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
## 27       Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
## 28        Lotus Europa 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
## 29      Ford Pantera L 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
## 30        Ferrari Dino 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
## 31       Maserati Bora 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
## 32          Volvo 142E 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

Manchmal ist es sinnvoll, sich eine Datei erst einmal anzusehen, um den Separator zu wissen:

head(readLines("mtcars.csv"))
## [1] "\"\",\"mpg\",\"cyl\",\"disp\",\"hp\",\"drat\",\"wt\",\"qsec\",\"vs\",\"am\",\"gear\",\"carb\""
## [2] "\"Mazda RX4\",21,6,160,110,3.9,2.62,16.46,0,1,4,4"                                            
## [3] "\"Mazda RX4 Wag\",21,6,160,110,3.9,2.875,17.02,0,1,4,4"                                       
## [4] "\"Datsun 710\",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1"                                         
## [5] "\"Hornet 4 Drive\",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1"                                   
## [6] "\"Hornet Sportabout\",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2"

RStudio erleichtert das Einlesen von Dateien ungemein, aber auch hier ist das Verständnis von Separatoren wichtig. Hierzu wird im Dateibrowser von RStudio auf eine Datei geklickt, wo man sich dann entscheiden kann, ob man sich die Datei ansehen möchte oder ob sie importiert werden soll.

Das Dialogfenster bietet mehrere Funktionen:

  • Mit der Eingabe einer Zahl bei “Skip” kann die Anzahl von zu ignorierenden Zeilen spezifiziert werden
  • Manche Dateien haben keinen “Kopf”, der die Spalte beschreibt. Hier muss dann “First Row as Names” deaktiviert werden.
  • Bei dem Delimiter wird der Separator auswgewählt
  • Außerdem kann für jede Spalte einzeln bestimmt werden, um welchen Datentypen es sich handelt.

Die zweite Möglichkeit ist das Einlesen von einer URL:

read.csv("https://tom.alby.de/lehrveranstaltungen/datenanalyse-data-science-machine-learning/mtcars.csv")
##                      X  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
## 1            Mazda RX4 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
## 2        Mazda RX4 Wag 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
## 3           Datsun 710 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
## 4       Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
## 5    Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
## 6              Valiant 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
## 7           Duster 360 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
## 8            Merc 240D 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
## 9             Merc 230 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
## 10            Merc 280 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
## 11           Merc 280C 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
## 12          Merc 450SE 16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
## 13          Merc 450SL 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
## 14         Merc 450SLC 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
## 15  Cadillac Fleetwood 10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
## 16 Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
## 17   Chrysler Imperial 14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
## 18            Fiat 128 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
## 19         Honda Civic 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
## 20      Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
## 21       Toyota Corona 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
## 22    Dodge Challenger 15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
## 23         AMC Javelin 15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
## 24          Camaro Z28 13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
## 25    Pontiac Firebird 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
## 26           Fiat X1-9 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
## 27       Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
## 28        Lotus Europa 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
## 29      Ford Pantera L 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
## 30        Ferrari Dino 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
## 31       Maserati Bora 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
## 32          Volvo 142E 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

Sie funktioniert eigentlich genau so, nur dass anstatt eines Dateipfades eine URL angegeben wird.

R und der Arbeitsspeicher

R lädt alle Daten in den Arbeitsspeicher, das heißt, dass man auf einem normalen Desktop-Rechner schnell an die Grenzen stoßen kann, wenn man mit großen Dateien arbeitet. Hat ein Rechner 8 GB Arbeitsspeicher, so benötigt das Betriebssystem selbst und alle offenen Programme bereits einen Teil des Arbeitsspeichers. Hat man noch 4GB von den 8GB über, so wäre es also theoretisch möglich, einen Datensatz von 4 GB zu nutzen. In der Realität aber ist das alles ein wenig komplizierter. Denn ein UNIX-System wie MacOS zum Beispiel lagert immer einen Teil des Arbeitsspeichers auf die Festplatte aus (“Swapping”), so dass im Prinzip die Grenze von 8 GB nicht ganz so hart sein muss. Andererseits bleibt es selten dabei, dass man nur mit dem einen Datensatz von 4GB arbeitet, denn für das Selektieren, Filtern und Mutieren wird zusätzlicher Speicher benötigt.

Es empfiehlt sich also, den Workspace immer aufzuräumen und zum Beispiel nicht mehr benötigte Objekte zu löschen. Dies geht zum Beispiel mit dem Befehl rm().

rm(y)

R speichert den Workspace, so dass es zu Problemen kommen kann, wenn R oder RStudio neu gestartet wird und ein Projekt geladen wird, das viel Arbeitsspeicher benötigt. Es empfiehlt sich aber auch, unter den Optionen von RStudio anzugeben, dass man .RData nicht beim Start laden soll.

Die Explorative Datenanalyse (EDA)

Eine explorative Datenanalyse ist ein Ansatz aus der Statistik, der von Fahrmeir et al wie folgt beschrieben wird [@fahrmeir2011]:

Über die Darstellung von Daten hinaus ist [die explorative Datenanalyse] konzipiert zur Suche nach Strukturen und Besonderheiten in den Daten und kann so oft zu neuen Fragestellungen oder Hypothesen in den jeweiligen Anwendungen führen. Sie wird daher typischerweise eingesetzt, wenn die Fragestellung nicht genau definiert ist oder auch die Wahl eines geeigneten statistischen Modells unklar ist.

Es empfielt sich dennoch, zu Beginn mindestens eine Ausgangsfragestellung zunächst so klar wie möglich zu definieren und immer wieder zu dieser Ausgangsfragestellung zurückzukehren, bevor man sich in einer Analyse “verliert”. Gleichzeitig ist eine EDA auch eine Art “State of Mind”, wie es Hadley Wickham bezeichnet: jeder Idee, die einem kommt, soll nachgegangen werden im Analyse-Prozess. Es ist ein kreativer Prozess, so dass es auch ok ist, viele Fragen zu generieren. Denn das Schwierigste ist häufig, die richtigen Fragen an die Daten zu stellen [@wickham2017]:

Data exploration is the art of looking at your data, rapidly generating hypotheses, quickly testing them, then repeating again and again and again. The goal of data exploration is to generate many promising leads that you can later explore in more depth

“Erfunden” wurde die Explorative Datenanalyse von John W. Tukey in einem Artikel “The Future of Data Analysis”; sein Buch Exploratory Data Analysis gilt als Klassiker und Grundlage für das Thema Data Science.

Besonders geeignet für eine EDA sind Notebooks, die im nächsten Abschnitt vorgestellt werden.

Notebooks

Notebooks sind die großartigste Erfindung in der Datenanalyse seit der Einführung der Internet-Flatrate. In einem Notebook können Text wie zum Beispiel Fragestellungen und Beschreibungen eines Experiments, Software-Code sowie Daten und Ergebnisse in einem Dokument zusammen erfasst werden. Der Analyst schreibt zum Beispiel zunächst auf, was das Ziel der Analyse ist, dann wird Code geschrieben und das Ergebnis gleich in dem Dokument berechnet. Dadurch, dass alles zusammen in einem Dokument ist, können Vorgehensweise und Software von anderen Analysten nachvollzogen und Analysen einfacher ausgetauscht werden. Reproduzierbarkeit von Ergebnissen ist ein großes Thema in der wissenschaftlichen Welt, aber auch immer mehr in der Data Science-Szene. Sofern datenschutzrechtlich oder aufgrund anderer Einschränkungen nicht ausgeschlossen, können Daten, Software und Dokumentation so einfach untereinander ausgetauscht werden.

In einem Notebook kann in einem Code-Block ein Befehl eingegeben werden. Wird ein neues Notebook erstellt, so ist immer bereits ein Stück Code drin:

plot(cars)

Unter dem Code-Block entsteht dann direkt der Plot, der dann im weiteren Fließtext weiter beschrieben werden kann. So folgt ein Teil der Analyse dem anderen, und es ergibt sich ein nachvollziehbarer Gang durch die Gedanken des Analysten.

Netterweise erlaubt RStudio nicht nur das Einfügen von R-Code, sondern auch die Nutzung anderer Sprachen wie Python. In einem Dokument können also Code-Stücke verschiedener Sprachen genutzt werden und untereinander Daten austauschen. Im folgenden Video wird die Erstellung eines Notebooks genauer beschrieben.

Markdown

Notebooks können in andere Formate exportiert werden, zum Beispiel in HTML-Seiten oder ein PDF-Dokument. So ist wie in der Einleitung beschrieben auch diese Unterlage in R geschrieben worden. Mit Markdown können spezifische Befehle verwendet werden, um die Formatierung zu verändern, HTML-Links einzufügen und so weiter. Ein Beispiel für eine Explorative Datenanalyse, die mit einem Notebook und Markdown entstanden ist, findet sich hier.

Etwas Statistik

Mean, Median und Modus

Einmal zurück zu unserem ersten Datensatz, cars:

summary(cars)
##      speed           dist       
##  Min.   : 4.0   Min.   :  2.00  
##  1st Qu.:12.0   1st Qu.: 26.00  
##  Median :15.0   Median : 36.00  
##  Mean   :15.4   Mean   : 42.98  
##  3rd Qu.:19.0   3rd Qu.: 56.00  
##  Max.   :25.0   Max.   :120.00

Der Durchschnitt (hier mean) wird häufig als einziger Mittelwert genannt, aber wie man an der Distanz sehen kann, unterscheidet sich der Median vom Mean. Der Median ist der mittlere aller Werte, wenn man alle Werte sortiert in eine Reihe stellen würde:

x <- c(2,2,2,2,2,2,2,2,2,2,100)
mean(x)
## [1] 10.90909
median(x)
## [1] 2

Der Median ist anscheinend weniger anfällig für Ausreißer als der Durchschnitt. Tatsächlich ist der Durchschnitt nur dann spannend, wenn eine Normalverteilung vorliegt (siehe den nächsten Abschnitt). Dann nämlich ist Median gleich Mean.

Ein Statistik ist leider nicht in R mit Bordmitteln vorhanden, der Modus: Er zeigt die häufigste Zahl an.

Verteilungen

Die Normalverteilung heißt nicht deswegen so, weil alle anderen Verteilungen oder alles Nichtnormalverteilte nicht normal wären. Vielmehr heißt sie so, weil der belgische Wissenschaftler Adolphe Quetelet bei der Vermessung von Tausenden von Soldaten Normalverteilungen entdeckte und daher wahrscheinlich den Begriff prägte. Eine Normalverteilung ist dadurch geprägt, dass die Häufigkeitsverteilung der Werte einer symmetrischen Glockenkurve entspricht:

x <- rnorm(1000000, 3, .25)
hist(x, breaks=100)

summary(x)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.761   2.832   3.000   3.000   3.169   4.241

Wie wir sehen sind Median und Mean gleich oder zumindest sehr nah beieinander. Oft interessiert uns aber auch, wie breit die Streuung um den Mittelwert ist. Dazu bedienen wir uns der sogenannten Standardabweichung.

Varianz und Standardabweichung

Die Standardabweichung ist so etwas wie der Durchschnitt der Abweichungen, allerdings werden bei einem Durchschnitt die einzelnen Werte summiert und dann durch die Anzahl der Werte geteilt. Da wir sowohl positive als auch negative Werte haben, wird das nicht funktionieren. Der Trick ist nun, dass wir erst einmal alle Werte quadrieren und dann summieren und durch die Anzahl der Elemente teilen. Das ist die sogenannte Varianz:

var(x)
## [1] 0.06244134

Das Problem mit der Varianz ist allerdings, dass sie eine andere Metrik hat. Wenn wir vorher die Länge von Bohnen in Zentimetern gemessen und deren Varianz berechnet haben, dann ist die quadrierte Summe was? Nun können wir aber wieder die Wurzel nehmen und erhalten dann die Standardabweichung, die in derselben Metrik ist wie unsere Ursprungswerte. In R müssen wir natürlich nicht erst die Varianz berechnen und davon dann die Standardabweichung, hier geht das alles mit einem Befehl:

sd(x)
## [1] 0.2498827

95% aller Werte einer Normalverteilung liegen zwischen +/- 1.96 Standardabweichungen (manchmal sagt man auch einfach zwei Standardabweichungen, weil man sich das besser merken kann, aber es sind wirklich nur 1,96). Mit anderen Worten, wenn wir einen Wert aus einer Population haben, dann liegt die Wahrscheinlichkeit bei 95%, dass dieser Wert aus den Werten innerhalb von +/- 1.96 Standardabweichungen kommt.

Das tidyverse

Explorative Datenanalysen mit dem tidyverse

Das tidyverse

Das tidyverse ist eine Erweiterung zu R, die Explorative Datenanalysen stark vereinfacht. Das tidyverse stammt von Hadley Wickham, dem Chief Data Scientist von RStudio. Scherzhafterweise wird das tidyverse von Hadleys Anhängern auch manchmal als Hadleyverse bezeichnet.

DasTidyverse enthält mehrere Libraries:

  • ggplot2: Erweiterte Plotting-Möglichkeiten
  • dplyr: Eine beliebte Bibliothek zur Daten-Manipulation, die im Kurs ausführlicher besprochen wird
  • tidyr: Zum “Aufräumen” von Daten
  • readr: Zum Einlesen von Daten
  • purrr: Erweiterung der Funktionalen Programmierung in R
  • tibble: Eine moderne Version der Dataframes; wird das tidyverse geladen, dann werden aus Dataframes automatisch Tibbles
  • stringr: Library zur Bearbeitung von Strings
  • forcats: Library zur Lösung von Problemen mit Factors

Im Kurs werden nicht alle Pakete behandelt. Es ist möglich, dass auch nur einzelne Libraries installiert werden, mit install.packages(tidyverse) werden alle Libraries auf einmal installiert.

#install.packages("tidyverse")
library(tidyverse)
## ── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2     ✓ purrr   0.3.4
## ✓ tibble  3.0.3     ✓ dplyr   1.0.1
## ✓ tidyr   1.1.1     ✓ stringr 1.4.0
## ✓ readr   1.3.1     ✓ forcats 0.5.0
## ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

dplyr

Im Kurs werden wir uns vor allem mit dplyr beschäftigen (das wird Deutsch “dieplaier” ausgesprochen). Eine wunderbare Einführung gibt der Meister himself hier:

Für diejenigen, die nicht das ganze Video sehen wollen, hier die Kernaussagen. dplyr verwendet 5 Haupt-Verben, mit denen grundlegende Befehle ausgeführt werden.

  • select: Zum Auswählen von Variablen
  • filter: Zum Auswählen von Zeilen
  • mutate: Zum Erzeugen von neuen Variablen/Spalten oder auch zum Verändern dieser
  • summarize: Ergebnisse berechnen (im Video sagt Hadley, dass zu einer Zeile reduziert wird, aber man kann auch mehrere Zeilen als Ergebnis haben)
  • arrange: Zum Sortieren von Zeilen

Ein wichtiger Punkt im Tidyverse ist die so genannte Pipe. Unter UNIX sind Pipes ein beliebtes Mittel, um die Ausgabe aus einer Funktion als Input für eine andere Funktion zu nutzen, z.B.

grep ‘2018’ bericht.csv | wc -l

In diesem Beispiel unter UNIX wird in der Datei bericht.csv nach allen Zeilen gesucht, in denen der String 2018 vorkommt.Die Ausgabe wären dann genau diese Zeilen. Mit dem Operator | wird diese Ausgabe in den nächsten Befehl geleitet. wc -l (steht für word count mit dem Parameter lines, so dass Zeilen und nicht Wörter gezählt werden) zählt die Zeilen. Wir erfahren also mit diesem Befehl, wie viele Zeilen den String 2018 enthalten. Pipes sind ein sehr effizientes Mittel unter UNIX. Genau diesen Mechanismus verwendet das tidyverse, wobei hier der Fokus auf der Lesbarkeit liegt.

In dplyr wird anstatt des “|” das Pipe-Symbol %>% verwendet (früher %.% wie oben im Video zu sehen).

Ein Beispiel-Datensatz zur Demonstration ist der Book-Crossing-Datensatz. In dem Datensatz users ist das Alter zum Beispiel nicht bei jedem Nutzer vorhanden, und in diesem Beispiel hat R die Spalte mit dem Datentyp Character importiert:

## tibble [278,858 × 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ User-ID : num [1:278858] 1 2 3 4 5 6 7 8 9 10 ...
##  $ Location: chr [1:278858] "nyc, new york, usa" "stockton, california, usa" "moscow, yukon territory, russia" "porto, v.n.gaia, portugal" ...
##  $ Age     : chr [1:278858] "NULL" "18" "NULL" "17" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   `User-ID` = col_double(),
##   ..   Location = col_character(),
##   ..   Age = col_character()
##   .. )

Diese Daten sollten zunächst einmal in eine Zahl konvertiert werden:

users %>%
  mutate(Age = as.numeric(Age)) %>%
  head()
## Warning: Problem with `mutate()` input `Age`.
## x NAs introduced by coercion
## ℹ Input `Age` is `as.numeric(Age)`.
## Warning in mask$eval_all_mutate(dots[[i]]): NAs introduced by coercion
## # A tibble: 6 x 3
##   `User-ID` Location                             Age
##       <dbl> <chr>                              <dbl>
## 1         1 nyc, new york, usa                    NA
## 2         2 stockton, california, usa             18
## 3         3 moscow, yukon territory, russia       NA
## 4         4 porto, v.n.gaia, portugal             17
## 5         5 farnborough, hants, united kingdom    NA
## 6         6 santa monica, california, usa         61

Der Befehl head() wird dafür verwendet, dass nur die ersten Zeilen angezeigt werden (ansonsten würde diese Seite zu lange brauchen um zu laden). Die Warnhinweise kommen daher, dass NULL nicht zu einer Zahl konvertiert werden kann und daher anstatt dessen NA genutzt wird. Allerdings ist der Datensatz jetzt noch nicht geändert, die Ergebnisse des Ausdrucks müssen in ein neues oder dasselbe Objekt geschrieben werden. Ein neues Objekt hat den Vorteil, dass man noch Zugriff auf die Originaldaten hat, allerdings ist dann der Speicherverbrauch höher.

users <- users %>%
  mutate(Age = as.numeric(Age))
## Warning: Problem with `mutate()` input `Age`.
## x NAs introduced by coercion
## ℹ Input `Age` is `as.numeric(Age)`.
## Warning in mask$eval_all_mutate(dots[[i]]): NAs introduced by coercion

Die Ergebnisse werden nun nicht mehr angezeigt, da sie ja in das neue Objekt geschrieben werden. Dies lässt sich umgehen, indem man den ganzen Ausdruck in Klammern setzt:

(users2 <- users)
## # A tibble: 278,858 x 3
##    `User-ID` Location                             Age
##        <dbl> <chr>                              <dbl>
##  1         1 nyc, new york, usa                    NA
##  2         2 stockton, california, usa             18
##  3         3 moscow, yukon territory, russia       NA
##  4         4 porto, v.n.gaia, portugal             17
##  5         5 farnborough, hants, united kingdom    NA
##  6         6 santa monica, california, usa         61
##  7         7 washington, dc, usa                   NA
##  8         8 timmins, ontario, canada              NA
##  9         9 germantown, tennessee, usa            NA
## 10        10 albacete, wisconsin, spain            26
## # … with 278,848 more rows

Gelöscht wird mit rm() für “remove”.

rm(users2)

Danach können alle Datensätze herausgefiltert werden, in denen ein Alter angegeben wurde, das unwahrscheinlich ist:

users %>%
  filter(Age < 100 | Age > 6) 
## # A tibble: 168,096 x 3
##    `User-ID` Location                                 Age
##        <dbl> <chr>                                  <dbl>
##  1         2 stockton, california, usa                 18
##  2         4 porto, v.n.gaia, portugal                 17
##  3         6 santa monica, california, usa             61
##  4        10 albacete, wisconsin, spain                26
##  5        11 melbourne, victoria, australia            14
##  6        13 barcelona, barcelona, spain               26
##  7        18 rio de janeiro, rio de janeiro, brazil    25
##  8        19 weston, ,                                 14
##  9        20 langhorne, pennsylvania, usa              19
## 10        21 ferrol / spain, alabama, spain            46
## # … with 168,086 more rows

Allerdings heißt ein falsch angegebenes Alter nicht, dass das Rating, das der Nutzer angegeben hat, auch falsch ist. Daher wollen wir den Datensatz nicht modifizieren. Der Operator | heißt hier “oder”.

Das Verb ‘select’ kann gut an dem books-Dataframe demonstriert werden, denn dieser hat ein paar Spalten, die wir in der Regel nicht benötigen werden:

str(books)
## tibble [271,379 × 8] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ ISBN               : chr [1:271379] "0195153448" "0002005018" "0060973129" "0374157065" ...
##  $ Book-Title         : chr [1:271379] "Classical Mythology" "Clara Callan" "Decision in Normandy" "Flu: The Story of the Great Influenza Pandemic of 1918 and the Search for the Virus That Caused It" ...
##  $ Book-Author        : chr [1:271379] "Mark P. O. Morford" "Richard Bruce Wright" "Carlo D'Este" "Gina Bari Kolata" ...
##  $ Year-Of-Publication: num [1:271379] 2002 2001 1991 1999 1999 ...
##  $ Publisher          : chr [1:271379] "Oxford University Press" "HarperFlamingo Canada" "HarperPerennial" "Farrar Straus Giroux" ...
##  $ Image-URL-S        : chr [1:271379] "http://images.amazon.com/images/P/0195153448.01.THUMBZZZ.jpg" "http://images.amazon.com/images/P/0002005018.01.THUMBZZZ.jpg" "http://images.amazon.com/images/P/0060973129.01.THUMBZZZ.jpg" "http://images.amazon.com/images/P/0374157065.01.THUMBZZZ.jpg" ...
##  $ Image-URL-M        : chr [1:271379] "http://images.amazon.com/images/P/0195153448.01.MZZZZZZZ.jpg" "http://images.amazon.com/images/P/0002005018.01.MZZZZZZZ.jpg" "http://images.amazon.com/images/P/0060973129.01.MZZZZZZZ.jpg" "http://images.amazon.com/images/P/0374157065.01.MZZZZZZZ.jpg" ...
##  $ Image-URL-L        : chr [1:271379] "http://images.amazon.com/images/P/0195153448.01.LZZZZZZZ.jpg" "http://images.amazon.com/images/P/0002005018.01.LZZZZZZZ.jpg" "http://images.amazon.com/images/P/0060973129.01.LZZZZZZZ.jpg" "http://images.amazon.com/images/P/0374157065.01.LZZZZZZZ.jpg" ...
##  - attr(*, "problems")= tibble [38 × 5] (S3: tbl_df/tbl/data.frame)
##   ..$ row     : int [1:38] 6451 6451 43666 43666 51750 51750 92037 92037 104318 104318 ...
##   ..$ col     : chr [1:38] "Year-Of-Publication" NA "Year-Of-Publication" NA ...
##   ..$ expected: chr [1:38] "a double" "8 columns" "a double" "8 columns" ...
##   ..$ actual  : chr [1:38] "John Peterman" "9 columns" "\\\"Freedom Song\\\"\"" "10 columns" ...
##   ..$ file    : chr [1:38] "'BX-Books.csv'" "'BX-Books.csv'" "'BX-Books.csv'" "'BX-Books.csv'" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   ISBN = col_character(),
##   ..   `Book-Title` = col_character(),
##   ..   `Book-Author` = col_character(),
##   ..   `Year-Of-Publication` = col_double(),
##   ..   Publisher = col_character(),
##   ..   `Image-URL-S` = col_character(),
##   ..   `Image-URL-M` = col_character(),
##   ..   `Image-URL-L` = col_character()
##   .. )

Wir können mit select() zum einen die Spalten auswählen, die wir haben wollen:

books %>%
  select(ISBN,`Book-Title`,`Book-Author`,`Year-Of-Publication`,Publisher)
## # A tibble: 271,379 x 5
##    ISBN   `Book-Title`             `Book-Author`   `Year-Of-Public… Publisher   
##    <chr>  <chr>                    <chr>                      <dbl> <chr>       
##  1 01951… Classical Mythology      Mark P. O. Mor…             2002 Oxford Univ…
##  2 00020… Clara Callan             Richard Bruce …             2001 HarperFlami…
##  3 00609… Decision in Normandy     Carlo D'Este                1991 HarperPeren…
##  4 03741… Flu: The Story of the G… Gina Bari Kola…             1999 Farrar Stra…
##  5 03930… The Mummies of Urumchi   E. J. W. Barber             1999 W. W. Norto…
##  6 03991… The Kitchen God's Wife   Amy Tan                     1991 Putnam Pub …
##  7 04251… What If?: The World's F… Robert Cowley               2000 Berkley Pub…
##  8 06718… PLEADING GUILTY          Scott Turow                 1993 Audioworks  
##  9 06794… Under the Black Flag: T… David Cordingly             1996 Random House
## 10 07432… Where You'll Find Me: A… Ann Beattie                 2002 Scribner    
## # … with 271,369 more rows

Man kann das aber auch einfacher haben, indem man die Spalten angibt, die man nicht haben will:

books %>%
  select(-`Image-URL-S`,-`Image-URL-M`,-`Image-URL-L`)
## # A tibble: 271,379 x 5
##    ISBN   `Book-Title`             `Book-Author`   `Year-Of-Public… Publisher   
##    <chr>  <chr>                    <chr>                      <dbl> <chr>       
##  1 01951… Classical Mythology      Mark P. O. Mor…             2002 Oxford Univ…
##  2 00020… Clara Callan             Richard Bruce …             2001 HarperFlami…
##  3 00609… Decision in Normandy     Carlo D'Este                1991 HarperPeren…
##  4 03741… Flu: The Story of the G… Gina Bari Kola…             1999 Farrar Stra…
##  5 03930… The Mummies of Urumchi   E. J. W. Barber             1999 W. W. Norto…
##  6 03991… The Kitchen God's Wife   Amy Tan                     1991 Putnam Pub …
##  7 04251… What If?: The World's F… Robert Cowley               2000 Berkley Pub…
##  8 06718… PLEADING GUILTY          Scott Turow                 1993 Audioworks  
##  9 06794… Under the Black Flag: T… David Cordingly             1996 Random House
## 10 07432… Where You'll Find Me: A… Ann Beattie                 2002 Scribner    
## # … with 271,369 more rows

Ein letztes Beispiel soll die Funktionen von summarize und arrange demonstrieren:

ratings %>%
  group_by(ISBN) %>%
  summarize(AnzahlRatings = n()) %>%
  arrange(desc(AnzahlRatings))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 340,553 x 2
##    ISBN       AnzahlRatings
##    <chr>              <int>
##  1 0971880107          2502
##  2 0316666343          1295
##  3 0385504209           883
##  4 0060928336           732
##  5 0312195516           723
##  6 044023722X           647
##  7 0679781587           639
##  8 0142001740           615
##  9 067976402X           614
## 10 0671027360           586
## # … with 340,543 more rows

Das Verb group_by() gehört zwar nicht zu den Grundverben, aber es ist ungemein nützlich. Man könnte en Ausdruck oben wie folgt formulieren:

  • Zeig mir den Datensatz ratings an
  • und dann schaue für jeden einzelnen unique Wert
  • wie oft er vorkommt
  • und zeige das dann absteigend an

Reguläre Ausdrücke

Reguläre Ausdrücke sind Ausdrücke, mit denen man Muster erkennen kann. In dem folgenden Beispiel werden Zahlen gesucht, auf die ein oder mehrere Buchstaben folgen können.

books %>%
  mutate(ISBN = str_extract(ISBN, "[0-9]*X*"))
## # A tibble: 271,379 x 8
##    ISBN  `Book-Title` `Book-Author` `Year-Of-Public… Publisher `Image-URL-S`
##    <chr> <chr>        <chr>                    <dbl> <chr>     <chr>        
##  1 0195… Classical M… Mark P. O. M…             2002 Oxford U… http://image…
##  2 0002… Clara Callan Richard Bruc…             2001 HarperFl… http://image…
##  3 0060… Decision in… Carlo D'Este              1991 HarperPe… http://image…
##  4 0374… Flu: The St… Gina Bari Ko…             1999 Farrar S… http://image…
##  5 0393… The Mummies… E. J. W. Bar…             1999 W. W. No… http://image…
##  6 0399… The Kitchen… Amy Tan                   1991 Putnam P… http://image…
##  7 0425… What If?: T… Robert Cowley             2000 Berkley … http://image…
##  8 0671… PLEADING GU… Scott Turow               1993 Audiowor… http://image…
##  9 0679… Under the B… David Cordin…             1996 Random H… http://image…
## 10 0743… Where You'l… Ann Beattie               2002 Scribner  http://image…
## # … with 271,369 more rows, and 2 more variables: `Image-URL-M` <chr>,
## #   `Image-URL-L` <chr>

Weitere Informationen zum tidyverse

Ein weiteres, informatives Video mit Hadley Wickham ist hier zu finden: