data > opinion

Tom Alby

2 R-Basiswissen


Sie sind hier: start / lehrveranstaltungen / datenanalyse data science machine learning / 02 r basiswissen /

R-Basiswissen

R und RStudio

Die Geschichte von 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 kann von der R Project Website heruntergeladen 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. 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.

RStudio GUI

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.

Alternative zur Desktop-Installation: rstudio.cloud

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

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.

Die ersten 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 22:30:33 CEST"
str(my_date)
##  POSIXct[1:1], format: "2019-06-23 22: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.

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.738   2.831   3.000   3.000   3.169   4.249

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.06255166

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.2501033

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.

Eine Beispielanalyse

Für das Beispiel wird der in R mitgelieferte Datensatz ChickWeight verwendet. In einem ersten Schritt schauen wir uns die Daten erst einmal nur an. Mit dem Befehl head() werden uns die ersten Zeilen angezeigt, der Parameter dahinter bestimmt die Anzahl der Zeilen:

head(ChickWeight, 20)
##    weight Time Chick Diet
## 1      42    0     1    1
## 2      51    2     1    1
## 3      59    4     1    1
## 4      64    6     1    1
## 5      76    8     1    1
## 6      93   10     1    1
## 7     106   12     1    1
## 8     125   14     1    1
## 9     149   16     1    1
## 10    171   18     1    1
## 11    199   20     1    1
## 12    205   21     1    1
## 13     40    0     2    1
## 14     49    2     2    1
## 15     58    4     2    1
## 16     72    6     2    1
## 17     84    8     2    1
## 18    103   10     2    1
## 19    122   12     2    1
## 20    138   14     2    1

Wir sehen, dass wir mehrere Spalten mit numerischen Werten haben, so dass wir diese plotten können:

plot(ChickWeight)

Wir sehen hier einen etwas anderen Plot als sonst, was daran liegt, dass es sich hier um gruppierte Daten handelt:

str(ChickWeight)
## Classes 'nfnGroupedData', 'nfGroupedData', 'groupedData' and 'data.frame':   578 obs. of  4 variables:
##  $ weight: num  42 51 59 64 76 93 106 125 149 171 ...
##  $ Time  : num  0 2 4 6 8 10 12 14 16 18 ...
##  $ Chick : Ord.factor w/ 50 levels "18"<"16"<"15"<..: 15 15 15 15 15 15 15 15 15 15 ...
##  $ Diet  : Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 1 1 1 1 1 ...
##  - attr(*, "formula")=Class 'formula'  language weight ~ Time | Chick
##   .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv> 
##  - attr(*, "outer")=Class 'formula'  language ~Diet
##   .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv> 
##  - attr(*, "labels")=List of 2
##   ..$ x: chr "Time"
##   ..$ y: chr "Body weight"
##  - attr(*, "units")=List of 2
##   ..$ x: chr "(days)"
##   ..$ y: chr "(gm)"

Auch wenn die Plots auf den ersten Blick verwirrend aussehen (jeder Plot bildet die Gewichtszunahme für ein Huhn ab, leider fehlt die Diät da drin), so sehen wir, dass Zeit und Gewicht in Bezug zueinander stehen, denn man könnte, kneift man die Augen ein wenig zu, eine Linie durch die Punkte ziehen, auch wenn manche Punkte etwas weiter von der Linie entfernt wären. Als nächstes schauen wir uns mit dem Befehl summary weitere Statistiken zu den Daten an, uns interessiert vor allem das Gewicht in der letzten Woche:

summary(ChickWeight$weight[ChickWeight$Diet == 4 & ChickWeight$Time == 21])
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   196.0   204.0   237.0   238.6   264.0   322.0

In diesem zugegebenermaßen sehr einfachen Datensatz ist eine interessante Beobachtung zu machen, nämlich dass das arithmetische Mittel und der Median auseinanderliegen. Dies bedeutet, dass wir keine Normalverteilung haben. Wie die Verteilung genau aussieht, kann man in einem Histogramm sehen; hier schauen

hist(ChickWeight$weight[ChickWeight$Diet == 1 & ChickWeight$Time == 21])

R ist beim Histogramm manchmal etwas umständlich, da es sich seine Blöcke selber einteilt. Mit der Option “breaks” kann man selbst bestimmen, in wie viele Buckets die Werte eingeteilt werden sollen:

hist(ChickWeight$weight[ChickWeight$Diet == 1 & ChickWeight$Time == 21], breaks=120)

Es ist deutlich zu sehen, dass die Verteilung hier nicht normalverteilt ist. Das eine Huhn mit 305 Gramm wirkt wie ein Ausreißer, doch selbst wenn er rausgenommen wird, so ist der Median immer noch deutlich über dem Mean. Es liegt also nicht am Ausreißer, sondern an der Verteilung. Die Verteilung zu kennen, ist also von großer Bedeutung, da der Durchschnitt ansonsten zu falschen Schlüssen führen könnte.

Verteilungen, die nicht normalverteilt sind, sind auch aus anderen Zusammenhängen bekannt, zum Beispiel beim https://www.zeit.de/wirtschaft/2018-12/mittelschicht-einkommen-deutschland. Dieses wird in dem Beispiel auch mit dem Median berechnet. Das durchschnittliche Haushaltseinkommen liegt bei über 3.000€ im Monat, der Median darunter aufgrund der rechtsschiefen Verteilung. Würde man den Durchschnitt nehmen, würden mehr Menschen den Eindruck bekommen, dass weniger als der Durchschnitt verdienen. Beim Median denkt das nur noch die Hälfte. Genereller Tipp: Wird von einem Durchschnitt gesprochen, ohne dass die Verteilung klar ist, so empfiehlt es sich, noch mal nachzufragen :)