1 Einführung

Liebe Studierende,
bei dem Ihnen hier vorliegenden Dokument handelt es sich um das Skript zur Methodenübung “Datenanalyse in R” am Institut für Publizistik- und Kommunikationswissenschaft der FU Berlin. Die Veranstaltung wird sowohl auf Bachelor- als auch auf Masterniveau abgehalten. Das Skript ist für beide Zielgruppen gleich, allerdings werden im Bachelor nicht alle Aspekte behandelt.

Das Skript soll Ihnen als Unterstützung beim Erlernen des Umgangs mit R dienen. Es gilt als Ergänzung zu den Lehrveranstaltungen und Online Tutorials, wobei diese im Skript nicht eins zu eins abgebildet werden (und auch nicht immer in der selben Reihenfolge behandelt sind). Schauen Sie sich immer auch den Code zu den Videos und zum Input der Präsenzsitzungen an.

Wie bei vielen Dingen im Leben gilt auch beim Lernen von R: Selbermachen macht schlau! Üben Sie so viel wie möglich. Machen Sie die vorgegebenen Übungen und trauen Sie sich auch eigene Versuche (z.B. mit selbst recherchierten Daten) zu unternehmen. Gerade am Anfang ist das Lernen von R mit dem Lernen einer neuen Sprache vergleichbar. Lernen Sie also zum Beispiel grundlegende Befehle, wie Sie Vokabeln lernen würden. Aber eine Sprache wirklich zu beherrschen, kann nur durch Anwendung gelingen. Nutzen Sie daher jede Gelegenheit!

So wie das OpenSource Programm R ist auch dieses Skript nicht in Stein gemeißelt, sondern entwickelt sich jedes Semester weiter. Nicht alle Stellen sind perfekt formuliert. Hin und wieder wird sich sicher mal ein Rechtschreib- oder Kommafehler eingeschlichen haben. Sehen Sie dies nach. Wenn Sie zum Beispiel kurz vor Ihrem Abschluss beim Verfassen Ihrer Abschlussarbeit eine aktualisierte Version haben möchten, scheuen Sie nicht, mich anzusprechen.

1.1 Allgemeine Hinweise

R…

… ist nicht einfach eine Statistik-Software, sondern eine Programmiersprache

… ist kostenlos (Open Source) für alle Betriebssysteme

… bietet Tausende von Paketen für alle Arten von Analysen

… erlaubt eine flexible Anpassung an spezifische Problemstellungen

… ermöglicht unzählige Möglichkeiten der Visualisierung und Dokumentenaufbereitung

1.2 Installation

R ist eine Programmiersprache. Die eigentliche Software, mit der wir im Kurs arbeiten, ist RStudio. Beides - sowohl die Programmiersprache als auch die Software - müssen auf der Website www.rstudio.com heruntergeladen werden:

Download RStudio

Achten Sie dabei darauf, die richtige Version für Ihr Betriebssystem auszuwählen. Installieren Sie zuerst die Programmiersprache R und dann das Arbeitsumfeld RStudio.

1.3 RStudio Oberfläche

R Umgebung
R Umgebung
  • Source (Syntax) – Text-Editor für Skriptdateien
    – Eingabe mehrerer Codezeilen auf einmal
    – Ausführen über STRG bzw. cmd und Enter bzw. Schaltfläche „Run”
    – Speicherung des Codes

  • Console
    – Output: Darstellung der Ergebnisse
    – Eingabe von Code der nicht dauerhaft gespeichert wird
    – Jede eingegebene Zeile wird mit „Enter” ausgeführt

  • Environment
    – Ablage von Variablen
    – Historie der bisherigen Anweisungen

  • Files, Plots, Packages und Help
    – Files: Dateien und Verzeichnisse auf dem Computer (in der Regel würde hier der Projektordner offen bleiben zur schnellen Orientierung)
    – Plots: Anzeige von erstellten Grafiken und Diagrammen
    – Packages: Liste aller installierten Packages (abgeschlossene Sammlung von Funktionen und Daten, die den Funktionsumfang von R erweitern)
    – Help: R-Hilfeseiten
    – Viewer: Vorschau verschiedener fortgeschrittener Ansichten (z.B. Webseiten oder Präsentationen), die sich mit R und den entsprechenden Zusatzpaketen erstellen lassen

Eingabe in die Console – durch Enter wird die Operation direkt ausgeführt und das Ergebnis angezeigt

5+6
## [1] 11

Eingabe von Funktionen (auf Klammern und Anführungszeichen achten!) – Befehle zur Ausführung (dahinter stehen dann zum Beispiel Formeln zur Berechnung)

print("Hallo Welt")
## [1] "Hallo Welt"

2 R Basics

2.1 Ein Projekt anlegen und ein Working Directory festlegen

Bei der Arbeit mit R bewegt man sich in einem selbst angelegten Projekt als in sich abgeschlossene Einheit. Dem Projekt wird ein Arbeitsverzeichnis/ Working Directory (ein Ordner auf Ihrem Computer) zugewiesen. In diesem Verzeichnis werden alle Dateien gesammelt, die für das Projekt benötigt werden. Es bietet sich an, dass Sie sich ein Projektverzeichnis für den Kurs anlegen und dort alle Kursdateien speichern. Dies ermöglicht das einfachere Zurechtfinden und Verwalten der eigenen Arbeit. Sie brauchen dann beispielsweise beim Laden von Datensätzen keine langen Dateipfade eingeben, wenn Sie den Datensatz vorher in Ihrem Working Directory abgelegt haben.

Bei der Anlage eines Projektes wird das Arbeitsverzeichnis festgelegt. Speichern Sie das Projekts über die Menüleiste:

File - New Project - New Directory

Benennen Sie Ihr Projekt und legen einen Ordner fest. Das Management Ihrer Ordnerstruktur erfolgt im Reiter „Files”. Alles, was an Dateien erstellt wird, wird in diesem Verzeichnis abgelegt.

Projektordner anlegen
Projektordner anlegen

Das Working Directory kann sowohl über die Menüführung als auch per Code gesetzt werden. Hier zwei Beispiele für Windows und MacOS. Beachten Sie die Verwendung vom Slash-Zeichen bei MacOS und Backslash bei Windows.

# Windows 
setwd("C:\Documents\R Workshop") 

# macOS
setwd("~/Documents/R Workshop") 

Hat man vergessen, wo man das Working Directory abgespeichert hat, hilft die Ausführung des Befehls getwd() - ohne weitere Spezifizierung in den Klammern.

2.2 Einfache Rechenoperationen

Wirklich sehr basic: R kann rechnen.

1 + 3 # Addition
## [1] 4
7 - 5 # Substraktion
## [1] 2
2 * 6 # Multiplikation
## [1] 12
4 / 3 # Division
## [1] 1.333333
2^3   # Potenzieren
## [1] 8
2^0.5 # Wurzel ziehen
## [1] 1.414214
pi # Pi aufrufen
## [1] 3.141593

2.3 Objekte anlegen

Die Zuweisung eines Wertes zu einem Objekt erfolgt über “<-” und wird im Environment gespreichert.

a <- 2
b <- 3

(Hinweis: Manchmal sieht man auch die Verwendung von “=” statt des Pfeils. Das geht auch, aber bei R-Programmier:innen ist der Pfeil in der Regel gebräuchlicher.)

Mit den angelegten Objekten kann weitergearbeitet werden.

a*b
## [1] 6

Auch können Objekte zur Anwendung in Funktionen verwendet werden.

sqrt(a)
## [1] 1.414214

Das Ergebnis aus dieser Berechnung kann ebenfalls wieder in einem Objekt gespeichert werden. Das folgende Beispiel zeiht auch, dass Objekte nicht zwangsläufig nur aus einem Buchstaben bestehen müssen, sondern dass das Objekt “wurzel”, das hier angelegt wird, ebenfalls einen nummerischen Wert enthalten kann.

wurzel <- sqrt(a)

Und auch mit diesem Objekt lässt sich weiter arbeiten.

round(wurzel, digits = 2)
## [1] 1.41

Es bietet sich immer an, aussagekräftige Bezeichnungen für Objekte zu verwenden, um den Überblick zu behalten. Gerade dann, wenn Objekte dazu benutzt werden, Variablen für einen Datensatz anzulegen, sollte aus der Objektbezeichnung der Variablenname hervorgehen. Dabei ist die Groß- und Kleinschreibung zu beachten. “Wurzel” und “wurzel” sind zwei verschiedene Bezeichnungen.

Es können aber nicht nur Zahlen sondern auch Zeichenketten, sogenannte “Strings” als Objekte angelegt werden:

Vorname <- "Albert"
Nachname <- "Mustermensch"

Auch mit diesen Objekten kann im Folgenden gearbeitet werden, z.B. mit der print() Funktion:

print(Vorname)
## [1] "Albert"
print(Nachname)
## [1] "Mustermensch"

Oder mit der paste() Funktion, die mehrere Strings aneinanderreihen kann:

paste(Vorname, Nachname)
## [1] "Albert Mustermensch"

So lässt sich zum Beispiel eine Begrüßung formulieren:

paste("Guten Tag", Vorname, Nachname)
## [1] "Guten Tag Albert Mustermensch"

Mit der Funktion c() - wobei c für “concatenate”, auf deutsch: verketten - lassen sich auch mehrere Werte in einem Objekt hinterlegen:

Vornamen <- c("Albert", "Betty", "Carlo")
print(Vornamen)
## [1] "Albert" "Betty"  "Carlo"
Alter <- c(21, 19, 25)
print(Alter)
## [1] 21 19 25

2.4 Datentypen

Es gibt unterschiedliche Arten von Daten. Mit der Funktion typeof() können wir uns anzeigen lassen, um welche Form es sich handelt.

Reelle Zahlen (positv, negativ, mit und ohne Dezimalstelle) werden in R mit “double” oder “numeric” bezeichnet.

a <- 3.4
typeof(a)
## [1] "double"

Eine weitere Form der numerischen Variablen stellen die Variablen vom Typ “integer” da. Dabei handelt es sich um ganze Zahlen (ohne Dezimalstellen). Soll eine Zahl explizit als integer gespeichert werden, muss hinter den Wert ein großes L geschrieben werden. Integer-Variablen haben den Vorteil, dass sie weniger Speicherplatz einnehmen und dann auch schneller verarbeitet werden können. Das kann bei der Verarbeitung sehr großer Datenmengen relevant sein.

b <- 5L
typeof(b)
## [1] "integer"

Die nächste wichtige Art von Variablentyp sind die “character” Variablen. Dabei handelt es sich um Strings, also Zeichenfolgen. Bei der Eingabe von character-Variablen (und Strings allgemein) ist auf die Verwendung der Anführungszeichen zu achten.

name <- "Albert"
print(name)
## [1] "Albert"
typeof(name)
## [1] "character"

Werden Zahlenwerte in Anführungszeichen gesetzt, erkennt R diese ebenfalls als Zeichenfolgen. Es ließe sich damit nicht ohne weiteres Rechnen, wie mit einer numerischen Variable (vom Typ double).

c <- "29"
typeof(c)
## [1] "character"

Allerdings lassen sich als String gespeicherte Variablen umwandeln mit den Funktionen as.integer(), as.double oder as.numeric(), wie hier im Beispiel. Die alte Variable muss dabei durch Zuweisung überschrieben werden.

c <- as.numeric(c)
typeof(c)
## [1] "double"

Für den Einstieg in R sind darüber hinaus noch die Daten vom Typ “logical” relevant. Dabei handelt es sich um die Booleschen Operatoren TRUE und FALSE. Diese können auch als Variablen gespeichert werden.

is_day <- TRUE
is_night <- FALSE 

typeof(is_day)
## [1] "logical"
typeof(is_night)
## [1] "logical"

TRUE und FALSE können aber auch das Ergebnis von Abfragen sein:

is.character(name)
## [1] TRUE
is.numeric(c)
## [1] TRUE

2.5 Packages installieren und laden

Packages (Pakete) sind Sammlungen von Funktionen, die spezifische Probleme lösen sollen.
Um ein Package zu installieren, wird die Funktion install.packages() verwendet. Das installierte Package muss vor der Ausführung aber (jedes Mal, wenn R neu gestartet wird) wieder aufgerufen werden. Dies erfolgt über die Funktion library()

# install.packages("car")
library(car)
## Loading required package: carData

2.6 Die Hilfefunktion

Jede Funktion hat eine Dokumentation, die bei Aufruf im Reiter “Help” einsehbar ist. Dort gibt es auch ein Suchfeld.
Ist der Name der gesuchten Funktion bekannt, kann das Hilfefenster mit “?” vor der Funktion aufgerufen werden:

?sum

Der Aufbau der Hilfeseiten ist immer gleich:

  • Description: Überblick über die Funktion
  • Usage: Beispiel, wie die Funktion geschrieben werden kann (inkl. Voreinstellungen, engl.: default settings), mit der erwarteten Reihenfolge der Argumente
  • Arguments: Liste der benötigten Argumente
  • Details: weitere Dokumentation zur Entwicklung und zum tieferen Verständnis der Funktion
  • Value: der zu erwartende Output (nach seiner Form)
  • Resources: Literaturhinweise
  • Examples: Beipiele, kopierbar zur Anwendung mit eigenen Werten

Ist der Name der gesuchten Funktion nicht bekannt, lässt sich auch eine Keyword-Suche mit “??” durchführen. In diesem Fall werden alle Funktionen aufgelistet, in denen das Suchword vorkommt,

??sum

2.7 Ein erstes kleines Programm schreiben (Umrechner)

Sobald wir Code schreiben mit dem Ziel ein bestimmtes Problem nach vorgegebenen Regeln/ Anweisungen zu lösen, schreiben wir ein Programm. Mit den bisher behandelten Aspekten lässt sich zum Beispiel schon ein einfaches Programm schreiben, mit dem Meilen in Kilometer umgerechnet werden können:

miles <- 500
kilometers <- miles * 1.609344
print(kilometers)
## [1] 804.672

Wenn der Wert für die Variable miles geändert wird, wird auch die Kilometer-Angabe neu berechnet.

Ein kleiner Exkurs:
Soll das ganze im Ergebnis noch ein bisschen schöner dargestellt werden, kann statt mit print() mit der sprintf() Funktion gearbeitet werden, mit der man über Platzhalter Variablen in eine Zeichenfolge einbetten kann:

sprintf("%s Meilen entsprechen %s Kilometern.", miles, kilometers)
## [1] "500 Meilen entsprechen 804.672 Kilometern."

3 Vektoren

Ein Vektor ist die Zusammenfassung von Objekten zu einer endlichen Folge. Vektoren sind eindimensional und bestehen nur aus einem Datentyp.

3.1 Einen Vektor anlegen

Mit der Funktion c() lassen sich Vektoren anlegen. “c” steht dabei für combine oder concatenate (=verketten). Die Werte des anzulegenden Vektors werden in der Funktion mit Komma getrennt. Sollen Dezimalzahlen im Vektor enthalten sein, muss der Punkt als Dezimaltrennzeichen genutzt werden.

b <- c(0, 1, 2, 3, 4, 5)

Der Vektor b lässt sich auch wie folgt anlegen, wenn man aufeinanderfolgende Werte hat und diese nicht einzeln auflisten will. Der “:” bedeutet in diesem Fall “bis”.

b <- c(0:5)
print(b)
## [1] 0 1 2 3 4 5
b_rückwärts <- c(5:0)
print(b_rückwärts)
## [1] 5 4 3 2 1 0

Wie lang ein Vektor ist, kann mit der Funktion length() angezeigt werden:

length(b)
## [1] 6

Welche Struktur ein Objekt aufweist, lässt sich mit str() herausfinden:

str(b)
##  int [1:6] 0 1 2 3 4 5

Um einen Vektor nicht mit ganzen Zahlen sondern mit einer Sequenz kleinerer Zwischenschritte anzulegen, kann die Funktion seq(from = , to = , by = ) genutzt werden:

c <- seq(from = 1, to = 2, by = 0.1)
print(c)
##  [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0

Auch für Vektoren gilt, dass nicht nur nummerische Objekte verwendet werden können, sondern auch Strings.

d <- c("Albert", "Betty", "Carlo")
length(d)
## [1] 3
str(d)
##  chr [1:3] "Albert" "Betty" "Carlo"

3.2 Indexing in einem Vektor

Indexing bedeutet das Zugreifen auf Elemente in einem Objekt. Das Zugreifen auf Elemente in einem Vektor erfolgt über die eckigen Klammern:

# es wird auf das dritte Element zugegriffen 
# (weil der Vektor mit 0 beginnt, ist die 2 das dritte Element)
b[3] 
## [1] 2
# es wird auf alle Elemente AUßER das dritte Element zugegriffen
b[-3] 
## [1] 0 1 3 4 5
# Zugriff auf mehrere Elemente (1 bis 4)
b[1:4] 
## [1] 0 1 2 3

3.3 Werte in einem Vektor ersetzen

Mithilfe des Indexing können einzelne Werte im Vektor geändert werden. Schauen wir uns den oben angelegten Vektor b noch einmal an:

print(b)
## [1] 0 1 2 3 4 5

Soll nun der dritte Wert ausgewechselt werden, wird das wie folgt gelöst:

b[3] <- 99
print(b)
## [1]  0  1 99  3  4  5

Auf diese Weise können auch Werte angefügt werden:

b[6] <- 6
print(b)
## [1]  0  1 99  3  4  6

3.4 Eine Funktion auf einen Vektor anwenden

# errechnet den Durchschnitt aus den Werten des Vektors
mean(b) 
## [1] 18.83333
# bildet die Summe aus den Werten des Vektors
sum(b) 
## [1] 113
# bildet das Produkt aus den Werten des Vektors
prod(b)
## [1] 0

3.5 Einen Vektor benennen

Es wird ein Vektor mit vier Werten angelegt:

age <- c(21, 22, 23, 24)

Dieser Vektor hat noch keine weiteren Eigenschaften, was mit den Funktionen attributes() und names() überprüft werden kann.

attributes(age)
## NULL
names(age)
## NULL

Dem Vektor sollen Eigenschaften zugeordnet werden mithilfe der Funktion names()
Es handelt sich bei dem Vektor um die Altersangaben von vier (fiktiven) Studierenden. Die Namen der Studierenden werden nun dem Vektor ebenfalls zugeschrieben.
Achtung: Die Namen als Zeichenfolgen müssen in Anführungszeichen gesetzt werden!

names(age) <- c("Albert", "Betty", "Carlo", "Dani") 

Lassen wir den Vektor mit der Funktion print() ausgeben, sehen wir Namen und Alter zugeordnet.

print(age)
## Albert  Betty  Carlo   Dani 
##     21     22     23     24

Auch in diesem Vektor kann man mithilfe des Indexing auf Elemente zugreifen.

# Indexing über die Position des Elements
age[2] 
## Betty 
##    22
# Indexing über den Namen des Elements (Wie alt ist Albert?)
age["Albert"] 
## Albert 
##     21
# Auswahl der ersten drei Einträge
age[1:3] 
## Albert  Betty  Carlo 
##     21     22     23
# Ausschluss der Einträge 2 bis 3
age[-(2:3)] 
## Albert   Dani 
##     21     24
# Auswahl durch logische Operatoren
age[age < 23]
## Albert  Betty 
##     21     22

Sollen die Namen/ Attribute des Vektors gelöst werden, kann die Funktion überschrieben werden:

names(age) <- NULL

Soll ein Objekt ganz gelöscht werden, kann die Funktion rm() (=remove) genutzt werden.

remove(age)

Das Objekt taucht anschließend nicht mehr im Environment auf und kann nicht weiter genutzt werden.

4 Matrizen

Matrizen sind Erweiterungen von Vektoren. Während Vektoren eindimensional sind, sind Matrizen zweidimensionale Anordnungen. Dabei haben Matrizen eine feste Anzahl von Zeilen und Spalten und können nur einen Datentyp enthalten.

4.1 Vom Vektor zur Matrix: Dimensionen eines Vektors ändern

Mit der Funktion seq() wird eine Sequenz angelegt, im folgenden Beispiel also eine Sequenz von 10 bis 120. Das Argument “by =” gibt an, dass die Sequenz in Zehnerschritten angelegt werden soll.

d <- seq(10, 120, by = 10)
print(d)
##  [1]  10  20  30  40  50  60  70  80  90 100 110 120

Mit der Funktion class() kann überprüft werden, um was für eine Art Objekt es sich handelt. In diesem Fall handelt es sich um einen numerischen Vektor.

class(d)
## [1] "numeric"

Nun soll der eindimensionale Vektor in ein zweidimensionales Objekt mit 3 Zeilen und 4 Spalten umgewandelt werden mit der Funktion dim()

dim(d) <- c(3,4) 
print(d)
##      [,1] [,2] [,3] [,4]
## [1,]   10   40   70  100
## [2,]   20   50   80  110
## [3,]   30   60   90  120

Wird nun mit der Funktion class() überprüft, um welche Art von Objekt es sich handelt, zeigt sich, dass die Umwandlung in eine Matrix/ ein Datenfeld (= array) erfolgreich war:

class(d)
## [1] "matrix" "array"

4.2 Eine Matrix erstellen

Matrizen können mit der Funktion matrix() erstellt werden. Hier eine Matrix mit den Werten von 1 bis 12, aufgeteilt auf 3 Zeilen mit dem Argument “nrow =”:

mtrx <- matrix(1:12, nrow = 3)
print(mtrx)
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
## [3,]    3    6    9   12

Es kann aber auch die Anzahl der Spalten definiert werden mit dem Argument “ncol =”:

mtrx2 <- matrix(1:12, ncol = 3)
print(mtrx2)
##      [,1] [,2] [,3]
## [1,]    1    5    9
## [2,]    2    6   10
## [3,]    3    7   11
## [4,]    4    8   12

Nach der Voreinstellung (= default settings) werden die Werte in der Matrix entlang der Spalten geordnet. Um die Werte entlang der Zeilen zu ordnen, muss das Argument “byrow = TRUE” aufgenommen werden.

mtrx2 <- matrix(1:12, ncol = 4, byrow = TRUE) 
print(mtrx2)
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    5    6    7    8
## [3,]    9   10   11   12

4.3 Mehrere Vektoren zu einer Matrix zusammenfügen

Mehrere Vektoren können mithilfe der Funktionen cbind() (= column bind) und rbind() (=row bind) zu einer Matrix zusammengefasst werden. Zuerst werden zwei Vektoren angelegt, mit der täglichen Social Media Nutzung in Minuten von Albert und Betty:

Albert <- c(123, 114, 105, 134, 127, 102, 99)
Betty <- c(90, 98, 92, 102, 115, 81, 75)

socialmedia <- cbind(Albert, Betty)
print(socialmedia)
##      Albert Betty
## [1,]    123    90
## [2,]    114    98
## [3,]    105    92
## [4,]    134   102
## [5,]    127   115
## [6,]    102    81
## [7,]     99    75

Nun sollen die Zeilen auch noch benannt werden mit der Funktion rownames()

rownames(socialmedia) <- c("Montag", "Dienstag", "Mittwoch", 
                           "Donnerstag", "Freitag", "Samstag", "Sonntag")
print(socialmedia)
##            Albert Betty
## Montag        123    90
## Dienstag      114    98
## Mittwoch      105    92
## Donnerstag    134   102
## Freitag       127   115
## Samstag       102    81
## Sonntag        99    75

Zeilen und Spalten lassen sich einfach tauschen mit der Funktion t() Achtung: Das ursprüngliche Objekt wird hier überschrieben durch die Zuordnung:

socialmedia <- t(socialmedia)
print(socialmedia)
##        Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag
## Albert    123      114      105        134     127     102      99
## Betty      90       98       92        102     115      81      75

Nun soll ein weiterer Fall hinzugefügt werden. Dazu wird die Socialmedia Nutzung von Carlo als Vektor angelegt und anschließend mit der Funktion rbind() zur Matrix hinzugefügt:

Carlo <- c(67, 78, 80, 72, 62, 55, 84)
socialmedia <- rbind(socialmedia, Carlo)
print(socialmedia)
##        Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag
## Albert    123      114      105        134     127     102      99
## Betty      90       98       92        102     115      81      75
## Carlo      67       78       80         72      62      55      84

4.4 Operationen in der Matrix

Die Summe der Zeilen kann mit der Funktion rowSums() errechnet werden. Dies entspricht im Beispiel der Gesamtzahl der Socialmedianutzung pro Person:

rowSums(socialmedia)
## Albert  Betty  Carlo 
##    804    653    498

Mit der Funktion colSums() werden die Summen für jede Spalte gebildet:

colSums(socialmedia)
##     Montag   Dienstag   Mittwoch Donnerstag    Freitag    Samstag    Sonntag 
##        280        290        277        308        304        238        258

Die Funktionen rowMeans() und colMeans() bilden jeweils den Durchschnitt für die Zeilen und Spalten:

rowMeans(socialmedia)
##    Albert     Betty     Carlo 
## 114.85714  93.28571  71.14286
colMeans(socialmedia) 
##     Montag   Dienstag   Mittwoch Donnerstag    Freitag    Samstag    Sonntag 
##   93.33333   96.66667   92.33333  102.66667  101.33333   79.33333   86.00000

Die so errechneten Summen und Durchschnitte können wiederum als Vektoren in Objekte gespeichert werden, um sie dann zur Matrix hinzuzufügen:

total <- colSums(socialmedia)
average <- colMeans(socialmedia)
mtrx.comb <- rbind(socialmedia, total, average)
print(mtrx.comb)
##            Montag  Dienstag  Mittwoch Donnerstag  Freitag   Samstag Sonntag
## Albert  123.00000 114.00000 105.00000   134.0000 127.0000 102.00000      99
## Betty    90.00000  98.00000  92.00000   102.0000 115.0000  81.00000      75
## Carlo    67.00000  78.00000  80.00000    72.0000  62.0000  55.00000      84
## total   280.00000 290.00000 277.00000   308.0000 304.0000 238.00000     258
## average  93.33333  96.66667  92.33333   102.6667 101.3333  79.33333      86

4.5 Matrix erstellen in einer Code-Zeile

Die oben in mehreren Schritten erstellte Matrix kann auch in einem Schritt erstellt werden. Die Benennung von Zeilen und Spalten erfolgt mit dem Argument “dimnames =”, wobei dann eine Liste mit zwei Vektoren folgt.

socialmedia <- matrix(c(123, 114, 105, 134, 127, 102, 
                    99, 90, 98, 92, 102, 115, 81, 
                    75, 67, 78, 80, 72, 62, 55, 84),
              nrow = 3, byrow = TRUE,
              dimnames = list(c("Albert", "Betty", "Carlo"),
                            c("Montag", "Dienstag", "Mittwoch",
                              "Donnerstag", "Freitag", "Samstag",
                              "Sonntag")))

print(socialmedia)
##        Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag
## Albert    123      114      105        134     127     102      99
## Betty      90       98       92        102     115      81      75
## Carlo      67       78       80         72      62      55      84

4.6 Indexing in der Matrix

Auch in einer Matrix kann auf einzelne Elemente zugegriffen werden.

vec <- c(11, 21, 31, 41, 12, 22, 32, 42)
matr.a <- matrix(vec, nrow = 4)
matr.a
##      [,1] [,2]
## [1,]   11   12
## [2,]   21   22
## [3,]   31   32
## [4,]   41   42

Möchte man sich vergewissern (bzw. herausfinden), wie viele Zeilen und Spalten eine Matrix hat, kann die Funktion dim() dazu verwendet werden.

dim(matr.a)
## [1] 4 2

Indexing in der Matrix erfolgt ebenfalls über die Verwendung eckiger Klammern. Wichtig ist hier, dass in den Klammern beide Dimensionen der Matrix angesteuert werden, wobei die Position des auszuwählenden Element in der Zeile zuerst genannt wird (und vor dem Komma steht), die Position in der Spalte an zweiter Stelle (und hinter dem Komma). Soll also das Element in der 3. Zeile und 2. Spalte gewählt werden, ist dies wie folgt anzugeben:

matr.a[3,2]
## [1] 32

Alle Elemente der dritten Zeile werden wie folgt angesteuert (der Platz HINTER dem Komma bleibt leer):

matr.a[3,]
## [1] 31 32

Alle Elemente der zweiten Spalte werden wie folgt angesteuert (der Platz VOR dem Komma bleibt leer):

matr.a[,2]
## [1] 12 22 32 42

Auch mehrere Zeilen und Spalten lassen sich auswählen. Im Beispiel werden die Zeilen 1 und 3 der ursprünglichen Matrix in einer neuen Matrix dagestellt:

matr.a[c(1,3),]
##      [,1] [,2]
## [1,]   11   12
## [2,]   31   32

Eine andere Variante, um mehrere Spalten oder Zeilen gleichzeitig ausgeben zu lassen, ist die Verwendung des Doppelpunkts. Im Beispiel werden damit die Zeilen 2 BIS 3 ausgewählt.

matr.a[2:3, ]
##      [,1] [,2]
## [1,]   21   22
## [2,]   31   32

Die Zeilen uns Spalten der Matrix können mit den Funktionen rownames() und colnames() benannt werden. Im Folgenden kann auch über die Namen auf die Elemente zugegriffen werden.

rownames(matr.a) <- c("Zeile 1", "Zeile 2", "Zeile 3", "Zeile 4")
colnames(matr.a) <- c("Spalte 1", "Spalte 2")
print(matr.a)
##         Spalte 1 Spalte 2
## Zeile 1       11       12
## Zeile 2       21       22
## Zeile 3       31       32
## Zeile 4       41       42
matr.a["Zeile 2", ]
## Spalte 1 Spalte 2 
##       21       22
matr.a["Zeile 1", "Spalte 2"]
## [1] 12

Mit der Funktion dimnames() kann man sich die Benennungen der Zeilen und Spalten ausgeben lassen.

dimnames(matr.a)
## [[1]]
## [1] "Zeile 1" "Zeile 2" "Zeile 3" "Zeile 4"
## 
## [[2]]
## [1] "Spalte 1" "Spalte 2"

4.7 Werte in einer Matrix ersetzen

Wie auch bei Vektoren lassen sich mithilfe des Indexing Werte in der Matrix ersetzen. Schauen wir uns die ursprüngliche Matrix noch einmal an:

print(matr.a)
##         Spalte 1 Spalte 2
## Zeile 1       11       12
## Zeile 2       21       22
## Zeile 3       31       32
## Zeile 4       41       42

Es können sowohl einzelne Werte, ganze Zeilen oder ganze Spalten ersetzt werden. Im ersten Beispiel wird der erste Wert der zweiten Zeile ersetzt, indem diesem ein neuer Wert zugeschrieben wird:

matr.a[2,1] <- 99
print(matr.a)
##         Spalte 1 Spalte 2
## Zeile 1       11       12
## Zeile 2       99       22
## Zeile 3       31       32
## Zeile 4       41       42

Nun wird die gesammte dritte Zeile geändert:

matr.a[3,] <- c(100, 101)
print(matr.a)
##         Spalte 1 Spalte 2
## Zeile 1       11       12
## Zeile 2       99       22
## Zeile 3      100      101
## Zeile 4       41       42

Und schließlich die gesamte zweite Spalte:

matr.a[,2] <- c(200, 201)
print(matr.a)
##         Spalte 1 Spalte 2
## Zeile 1       11      200
## Zeile 2       99      201
## Zeile 3      100      200
## Zeile 4       41      201

Beachten Sie: Obwohl in der zweiten Spalte vier Werte enthalten sind, werden im Befehl oben nur zwei neue Werte angegeben. Diese neuen Werte werden bei der Umsetzung aber einfach so lange wiederholt, bis alle Werte der Spalte mit neuen Werten gefüllt ist. Diesen Effekt nennt man recycling in R.

5 Data Frames

Vektoren und Matrizen können nur Elemente eines einzigen Datentyps speichern. Data Frames können mehrere Datentypen beinhalten. Es handelt sich bei Data Frames um zweidimensionale Listen, die aus Vektoren mit gleicher Länge aber nicht zwangsläufig dem gleichen Datentyp bestehen.

5.1 Einen Data Frame erstellen

Ein Data Frame lässt sich mithilfe von mehreren unterschiedlichen Vektoren erstellen. Im Folgenden werden Vektoren mit gleicher Länge erstellt:

name <- c("Albert", "Betty", "Carlo", "Dani")
sex <- c("m", "w", "m", "d")
age <- c(23, 21, 25, 19)
hight <- c(173, 165, 168, 175)
edu <- c("BA", "BA", "MA", "Abi")

Mit der Funktion data.frame() können die Vektoren zu einem Datensatz zusammengefügt werden. Es entsteht eine klassische Datentabelle, in der jede Zeile einen Fall und jede Spalte eine Variable darstellt.

my.data <- data.frame(name, sex, age, hight, edu)
print(my.data)
##     name sex age hight edu
## 1 Albert   m  23   173  BA
## 2  Betty   w  21   165  BA
## 3  Carlo   m  25   168  MA
## 4   Dani   d  19   175 Abi

Die Spalten sind nun mit den Variablennamen überschrieben. Diese lassen sich nachträglich ändern, wenn zum Beispiel aussagekräftigere Bezeichnungen gewünscht werden:

names(my.data) <- c("Name", "Geschlecht", "Alter", "Größe", "Abschluss")
print(my.data)
##     Name Geschlecht Alter Größe Abschluss
## 1 Albert          m    23   173        BA
## 2  Betty          w    21   165        BA
## 3  Carlo          m    25   168        MA
## 4   Dani          d    19   175       Abi

Der Schritt der Benennung kann auch schon während der Erstellung des Datensatzes erfolgen:

my.data <- data.frame(Name = name, Geschlecht = sex, Alter = age, Größe = hight, Abschluss = edu)

Die im Datensatz enthaltenen Variablen lassen sich mit der Funktion names() aufrufen:

names(my.data)
## [1] "Name"       "Geschlecht" "Alter"      "Größe"      "Abschluss"

Das Variablenniveau wird von R automatisch zugeschrieben. Die Zuschreibung lässt sich überprüfen mit der Funktion str() (= structure). Im Beispiel sind Variablen des Typs “num” = numeric sowie “chr = character” enthalten:

str(my.data)
## 'data.frame':    4 obs. of  5 variables:
##  $ Name      : chr  "Albert" "Betty" "Carlo" "Dani"
##  $ Geschlecht: chr  "m" "w" "m" "d"
##  $ Alter     : num  23 21 25 19
##  $ Größe     : num  173 165 168 175
##  $ Abschluss : chr  "BA" "BA" "MA" "Abi"

5.2 Eine bestehende Matrix in einen Dataframe umwandeln

In Kapitel 3.5 wurde die Matrix socialmedia erstellt:

print(socialmedia)
##        Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag
## Albert    123      114      105        134     127     102      99
## Betty      90       98       92        102     115      81      75
## Carlo      67       78       80         72      62      55      84

Schauen wir uns auch die Struktur der Matrix noch einmal an:

str(socialmedia)
##  num [1:3, 1:7] 123 90 67 114 98 78 105 92 80 134 ...
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:3] "Albert" "Betty" "Carlo"
##   ..$ : chr [1:7] "Montag" "Dienstag" "Mittwoch" "Donnerstag" ...

Die Matrix kann mit der Funktion as.data.frame() in einen Datensatz umgewandelt werden:

socialmedia.df <- as.data.frame(socialmedia)
str(socialmedia.df)
## 'data.frame':    3 obs. of  7 variables:
##  $ Montag    : num  123 90 67
##  $ Dienstag  : num  114 98 78
##  $ Mittwoch  : num  105 92 80
##  $ Donnerstag: num  134 102 72
##  $ Freitag   : num  127 115 62
##  $ Samstag   : num  102 81 55
##  $ Sonntag   : num  99 75 84

Aus der Übersicht der Struktur lässt sich nunerkennen, dass der Data Frame 3 Fälle (obs. = observations) und 7 Variablen enthält.

print(socialmedia.df)
##        Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag
## Albert    123      114      105        134     127     102      99
## Betty      90       98       92        102     115      81      75
## Carlo      67       78       80         72      62      55      84

In der Liste der Variablen ist ersichtlich, dass die erste Spalte der Matrix (die Namen der Fälle) nicht als Variable aufgenommen worden ist. Verloren sind die Namen aber auch nicht. Sie sind als Zeilenbezeichnungen erhalten:

row.names(socialmedia.df)
## [1] "Albert" "Betty"  "Carlo"

Braucht man die Namen der Fälle als eigene Variable, kann das Package “data.table” eine Lösung bieten mit der Funktion as.data.table() und dem Argument “keep.rownames = TRUE”:

library(data.table)
socialmedia.df <- as.data.table(socialmedia.df, keep.rownames = TRUE)
str(socialmedia.df)
## Classes 'data.table' and 'data.frame':   3 obs. of  8 variables:
##  $ rn        : chr  "Albert" "Betty" "Carlo"
##  $ Montag    : num  123 90 67
##  $ Dienstag  : num  114 98 78
##  $ Mittwoch  : num  105 92 80
##  $ Donnerstag: num  134 102 72
##  $ Freitag   : num  127 115 62
##  $ Samstag   : num  102 81 55
##  $ Sonntag   : num  99 75 84
##  - attr(*, ".internal.selfref")=<externalptr>

Nun taucht die character-Variable mit den Namen in der Liste der Variablen auf.

5.3 Den Datensatz speichern

Ein erstellter Datensatz kann gespeichert werden, zum Beispiel im csv-Format. csv steht für für comma separated values und ist ein gängiges Format für die Speicherung von Datensätzen. Es handelt sich dabei um eine Textdatei, in der die Werte des Datensatzes Zeilenweise aufgelistet werden, wobei die einzelnen Werte mit einem Trennzeichen getrennt werden. Bei der Funktion write.csv() wird das Komma als Trennzeichen verwendet. Das kann zu Problemen führen, falls der Datensatz Dezimalzahlen mit Komma enthält (im deutschsprachigen Raum sehr üblich). Darum gibt es die Alternative write.csv2(), die als Trennzeichen das Semikolon verwendet. Zur Struktur der Argumente in der Funktion: Zuerst wird das Objekt/ der Datensatz genannt, der gespeichert werden soll, anschließend wird mit dem Argument “file =” ein Dateiname vergeben, der die Dateiendung enthalten muss. Die Datei wird (wenn nicht anders angegeben) direkt im Working Directory gespeichert. Das Argument “row.names = FALSE” verhindert, dass eine weitere Spalte vorangestellt und gespeichert wird mit einer unnötigen zusätzlichen Zeilennummerierung.

write.csv(my.data, file = "myfirstdata.csv", row.names = FALSE)
write.csv2(my.data, file = "myfirstdata2.csv", row.names = FALSE)

5.4 Einen Datensatz öffnen

5.4.1 csv-Dateien

Das Einlesen eines gespeicherten csv-Datensatzes funktioniert analog zur Speicherung. Je nachdem, ob das verwendete Trennzeichen ein Komma oder ein Semikolon ist, muss entweder die Funktion read.csv() (für Komma-getrennte Werte) oder read.csv2() (für Semikolon-getrennte Werte) genutzt werden. Es bietet sich an, beim Laden eines Datensatzes diesen direkt als Objekt anzulegen

data1 <- read.csv("myfirstdata.csv")
print(data1)
##     Name Geschlecht Alter Größe Abschluss
## 1 Albert          m    23   173        BA
## 2  Betty          w    21   165        BA
## 3  Carlo          m    25   168        MA
## 4   Dani          d    19   175       Abi
data2 <- read.csv2("myfirstdata2.csv")
print(data2)
##     Name Geschlecht Alter Größe Abschluss
## 1 Albert          m    23   173        BA
## 2  Betty          w    21   165        BA
## 3  Carlo          m    25   168        MA
## 4   Dani          d    19   175       Abi

5.4.2 SPSS Dateien

Es lassen sich nicht nur csv-Dateien in R laden, sondern zum Beispiel auch Excel oder SPSS Dateien. In der Regel braucht es ein passendes Package, um den entsprechenden Importbefehl ausführen zu können. Das Beispiel zeigt das Öffnen einer SPSS-Datei mit dem Befehl read_spss aus dem Package “haven”. (Bei dem Datensatz im Beispiel handelt es sich um Befragungsdaten deutscher Journalist:innen aus der Worlds of Journalism Study.)

library(haven)
WJS <- read_spss("WJS_Germany_kurz.sav", n_max = 10)
print(WJS)
## # A tibble: 10 × 23
##    C1              C1A   C2      C2A   O1    C3    O2    C4      C5      C6     
##    <dbl+lbl>       <chr> <dbl+l> <chr> <dbl> <dbl> <dbl> <dbl+l> <dbl+l> <dbl+l>
##  1  8 [News write… ""    3 [Fre… ""    NA     1     1    1 [Yes] 1 [Yes] 1 [Wor…
##  2 10 [Other]      "fre… 3 [Fre… ""    NA     5     5    2 [No]  1 [Yes] 1 [Wor…
##  3 10 [Other]      "fre… 3 [Fre… ""    NA    10    10    1 [Yes] 1 [Yes] 2 [Wor…
##  4 10 [Other]      "fre… 3 [Fre… ""    NA     3     3    2 [No]  1 [Yes] 2 [Wor…
##  5 10 [Other]      "fre… 3 [Fre… ""    NA     2     2    2 [No]  1 [Yes] 1 [Wor…
##  6 10 [Other]      "fre… 3 [Fre… ""    NA     5    10    1 [Yes] 1 [Yes] 1 [Wor…
##  7  7 [Reporter]   ""    3 [Fre… ""    NA     4     5    1 [Yes] 1 [Yes] 1 [Wor…
##  8  5 [Senior edi… ""    3 [Fre… ""    NA     3     4    2 [No]  1 [Yes] 1 [Wor…
##  9 10 [Other]      "fre… 3 [Fre… ""    NA     4     3    2 [No]  1 [Yes] 1 [Wor…
## 10  7 [Reporter]   ""    3 [Fre… ""    NA     8     8    1 [Yes] 1 [Yes] 1 [Wor…
## # ℹ 13 more variables: C7 <dbl+lbl>, C7A <chr+lbl>, O3 <dbl+lbl>, C8 <chr+lbl>,
## #   C17 <dbl+lbl>, O5 <dbl+lbl>, O6 <dbl+lbl>, C20 <dbl+lbl>, C21 <dbl+lbl>,
## #   O7 <dbl+lbl>, C22 <dbl+lbl>, C23 <dbl+lbl>, O8 <dbl+lbl>

Mit dem Argument “n_max = 10” werden im Beispiel zu Anschauungszwecken nur die ersten 10 Fälle im Datensatz eingelesen. Um einen vollständigen Datensatz zu laden, würde dieses Arguent weggelassen werden.

Ein kleiner Exkurs: Die Arbeit mit SPSS-Datensätzen kann tricky sein, gerade was der Umgang mit Variablennamen und -bezeichnungen sowie Wertelabels angeht. Im Beispiel wird sichtbar, dass nur die Zahlencodes verwedet werden und es entsprechend immer eine Liste zur Hand bräuchte, mit der diese den Bezeichnungen der Ausprägungen zugeordnet werden können:

table(WJS$C1)
## 
##  5  7  8 10 
##  1  2  1  6

Um die Wertelabels sichbar zu machen, können Variablen zum Beispiel in einen Faktor umgewandelt werden mit der Funktion as_factor() und dem Argument levels = “default”. Nun lassen sich die beruflichen Positionen der befragten Journalist:innen direkt ablesen.

WJS$C1 <- as_factor(WJS$C1, levels = "default")
table(WJS$C1)
## 
##                        Refused                     Don`t know 
##                              0                              0 
##                  Missing value                Editor in chief 
##                              0                              0 
##                Managing editor Desk head or assignment editor 
##                              0                              0 
##                Department head                  Senior editor 
##                              0                              1 
##                       Producer                       Reporter 
##                              0                              2 
##                    News writer                        Trainee 
##                              1                              0 
##                          Other 
##                              6

5.5 Grundlegende Operationen im Datensatz

Um auf eine Variable im Datensatz zuzugreifen, um mit ihr zu arbeiten, wird folgende Schreibweise verwendet Datensatz$Variable Im Folgenden wird die Variable “Geschlecht” angezeigt

my.data$Geschlecht
## [1] "m" "w" "m" "d"

Den Altersdurchschnitt kann man entsprechend mit der Funktion mean() berechnen:

mean(my.data$Alter)
## [1] 22

Die absoluten Häufigkeiten einer Variable lassen sich mit der Funktion table() anzeigen:

table(my.data$Abschluss)
## 
## Abi  BA  MA 
##   1   2   1

5.6 Subsets auswählen (Indexing im Dataframe)

Wie auch schon bei den Vektoren und den Matrizen können wir mithilfe der eckigen Klammern einzelne Werte, Zeilen und Spalten auswählen.

my.data[2,]
##    Name Geschlecht Alter Größe Abschluss
## 2 Betty          w    21   165        BA

Diese Auswahl lässt sich als eigener Dataframe speichern.

Betty <- my.data[2,]
str(Betty)
## 'data.frame':    1 obs. of  5 variables:
##  $ Name      : chr "Betty"
##  $ Geschlecht: chr "w"
##  $ Alter     : num 21
##  $ Größe     : num 165
##  $ Abschluss : chr "BA"

Auch lassen sich Spalten auswählen:

my.data[,1]
## [1] "Albert" "Betty"  "Carlo"  "Dani"

In diesem Fall werden die Werte der Spalte als Vektor gespeichert:

names <- my.data[,1]
str(names)
##  chr [1:4] "Albert" "Betty" "Carlo" "Dani"

Es können auch mehrere Fälle ausgwählt werden, indem Bedingungen formuliert werden. Sollen zum Beispiel alle weiblichen Personen ausgewählt werden:

my.data[my.data$Geschlecht == "w",]
##    Name Geschlecht Alter Größe Abschluss
## 2 Betty          w    21   165        BA

Oder alle Personen, die älter sind als 21:

my.data[my.data$Alter > 21,]
##     Name Geschlecht Alter Größe Abschluss
## 1 Albert          m    23   173        BA
## 3  Carlo          m    25   168        MA

Oder alle weibichen Personen, die älter sind als 21:

my.data[my.data$Alter > 21 & my.data$Geschlecht == "w",]
## [1] Name       Geschlecht Alter      Größe      Abschluss 
## <0 rows> (or 0-length row.names)

5.7 Variablen zum Datensatz hinzufügen

Dem Datensatz sollen weitere Variablen mit der Haarfarbe und der täglichen (durchschnittlichen) Socialmedianutzung hinzugefügt werden.

hair <- c("schwarz", "braun", "rot", "blond")
daily_socialm <- c(95, 71, 126, 89)

my.data$Haarfarbe <- hair
print(my.data)
##     Name Geschlecht Alter Größe Abschluss Haarfarbe
## 1 Albert          m    23   173        BA   schwarz
## 2  Betty          w    21   165        BA     braun
## 3  Carlo          m    25   168        MA       rot
## 4   Dani          d    19   175       Abi     blond

Eine Variable wieder entfernen:

my.data$Haarfarbe <- NULL

Variablen hinzufügen mit mit cbind()

my.data <- cbind(my.data, Haarfarbe = hair, Socialmedia = daily_socialm)
print(my.data)
##     Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia
## 1 Albert          m    23   173        BA   schwarz          95
## 2  Betty          w    21   165        BA     braun          71
## 3  Carlo          m    25   168        MA       rot         126
## 4   Dani          d    19   175       Abi     blond          89

5.8 Variablenlabels nachträglich hinzufügen

Wurden bei der Erstellung einer (zum Beispiel nominalen oder ordinalen) Variablen nur ihre nummerischen Werte notiert, lassen sich Variablenlabels auch nachträglich hinzufügen. Für das Beispiel legen wir im Folgenden eine weitere Variable an, in der notiert wird, ob die Person einen Test bestanden (=1) oder nicht bestanden (=0) hat.

test <- c(1,1,2,1)
my.data <- cbind(my.data, test)
print(my.data)
##     Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia test
## 1 Albert          m    23   173        BA   schwarz          95    1
## 2  Betty          w    21   165        BA     braun          71    1
## 3  Carlo          m    25   168        MA       rot         126    2
## 4   Dani          d    19   175       Abi     blond          89    1
my.data$test <- factor(my.data$test, 
                       levels = c(1,2),
                       labels = c("nicht bestanden", "bestanden"))
print(my.data)
##     Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia            test
## 1 Albert          m    23   173        BA   schwarz          95 nicht bestanden
## 2  Betty          w    21   165        BA     braun          71 nicht bestanden
## 3  Carlo          m    25   168        MA       rot         126       bestanden
## 4   Dani          d    19   175       Abi     blond          89 nicht bestanden

5.9 Fälle hinzufügen

Fälle zu einem bestehenden Datensatz hinzuzufügen ist etwas schwieriger als Variablen hinzuzufügen. Es kann nicht einfach ein Vektor angelegt werden, da Vektoren nur einen Datentyp enthalten können. Die Lösung: Den Fall als eigenen Datensatz anlegen und mit rbind() beide Datensätze zusammenführen.

Emma <- data.frame(Name = "Emma", Geschlecht = "w", Alter = 24, Größe = 155,
                   Abschluss = "MA", 
                   Haarfarbe = "mittelblond", Socialmedia = 101, test = "bestanden")

my.data <- rbind(my.data, Emma)
print(my.data)
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1 Albert          m    23   173        BA     schwarz          95
## 2  Betty          w    21   165        BA       braun          71
## 3  Carlo          m    25   168        MA         rot         126
## 4   Dani          d    19   175       Abi       blond          89
## 5   Emma          w    24   155        MA mittelblond         101
##              test
## 1 nicht bestanden
## 2 nicht bestanden
## 3       bestanden
## 4 nicht bestanden
## 5       bestanden

5.10 Weitere Funktionen beim Datenimport

Der Beispieldatensatz von oben wurde um weitere Fälle ergänzt und gespeichert.

Studis <- read.csv2("Studis.csv")
print(Studis)
##         Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1     Albert          m    23   173        BA     schwarz          95
## 2      Betty          w    21   165        BA       braun          71
## 3      Carlo          m    25   168        MA         rot         126
## 4       Dani          d    19   175       Abi       blond          89
## 5       Emma          w    24   155        MA mittelblond         101
## 6     Fritzi          w    27   171        MA     schwarz         119
## 7       Gina          w    18   145       Abi       braun         143
## 8      Hakim          m    22   165        BA         rot          25
## 9        Ivo          m    23   192        BA       blond          33
## 10    Jasmin          w    26   167        MA mittelblond         109
## 11     Kalle          m    26   166        MA     schwarz          51
## 12    Ludwig          m    21   194        BA       braun         111
## 13      Mila          w    20   155       Abi         rot          32
## 14      Nael          m    21   192        BA       blond         102
## 15       Ole          m    20   181        BA mittelblond          76
## 16     Pablo          m    24   170        MA     schwarz          86
## 17    Quamar          m    19   176       Abi       braun          59
## 18     Ronja          w    18   167       Abi         rot         110
## 19    Sascha          d    21   177        BA       blond          41
## 20     Tarek          m    25   179        MA mittelblond          81
## 21   Ulfried          m    67   180       Abi        grau           0
## 22 Valentino          m    31   182        BA     schwarz          91
## 23     Wanda          w    28   162        BA       braun         107
## 24      Xeno          m    21   183        MA         rot          86
## 25      Yuna          w    20   176        BA       blond          91
## 26     Zaida          w    18   175       Abi mittelblond          21

Nur eine bestimmte Anzahl Fälle laden mit dem Argument “nrow =” beim Import:

Studis_short <- read.csv2("Studis.csv", nrow = 7)
print(Studis_short)
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1 Albert          m    23   173        BA     schwarz          95
## 2  Betty          w    21   165        BA       braun          71
## 3  Carlo          m    25   168        MA         rot         126
## 4   Dani          d    19   175       Abi       blond          89
## 5   Emma          w    24   155        MA mittelblond         101
## 6 Fritzi          w    27   171        MA     schwarz         119
## 7   Gina          w    18   145       Abi       braun         143

Eine bestimmte Anzahl von Fällen überspringen mit “skip =”.
Aber Achtung: skip überspringt auch die erste Zeile mit den Variablennamen. mit dem Argument “header = FALSE” bleibt die erste Zeile als Variablenbezeichnung (allerdings nicht die Originalbezeichnung)

Studis_short <- read.csv2("Studis.csv", nrow = 7, skip = 5, header = FALSE)
print(Studis_short)
##       V1 V2 V3  V4  V5          V6  V7
## 1   Emma  w 24 155  MA mittelblond 101
## 2 Fritzi  w 27 171  MA     schwarz 119
## 3   Gina  w 18 145 Abi       braun 143
## 4  Hakim  m 22 165  BA         rot  25
## 5    Ivo  m 23 192  BA       blond  33
## 6 Jasmin  w 26 167  MA mittelblond 109
## 7  Kalle  m 26 166  MA     schwarz  51

Möchte man die ursprünglichen Variablenbezeichnungen behalten, können diese zum Beispiel in einem vorherigen Schritt gespeichert werden und dann beim Datenimport mit “col.names =” hinzugefügt werden:

var_labels <- names(read.csv2("Studis.csv", nrows = 1))
Studis_short2 <- read.csv2("Studis.csv", col.names = var_labels, skip = 5, header =FALSE)
print(Studis_short2)
##         Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1       Emma          w    24   155        MA mittelblond         101
## 2     Fritzi          w    27   171        MA     schwarz         119
## 3       Gina          w    18   145       Abi       braun         143
## 4      Hakim          m    22   165        BA         rot          25
## 5        Ivo          m    23   192        BA       blond          33
## 6     Jasmin          w    26   167        MA mittelblond         109
## 7      Kalle          m    26   166        MA     schwarz          51
## 8     Ludwig          m    21   194        BA       braun         111
## 9       Mila          w    20   155       Abi         rot          32
## 10      Nael          m    21   192        BA       blond         102
## 11       Ole          m    20   181        BA mittelblond          76
## 12     Pablo          m    24   170        MA     schwarz          86
## 13    Quamar          m    19   176       Abi       braun          59
## 14     Ronja          w    18   167       Abi         rot         110
## 15    Sascha          d    21   177        BA       blond          41
## 16     Tarek          m    25   179        MA mittelblond          81
## 17   Ulfried          m    67   180       Abi        grau           0
## 18 Valentino          m    31   182        BA     schwarz          91
## 19     Wanda          w    28   162        BA       braun         107
## 20      Xeno          m    21   183        MA         rot          86
## 21      Yuna          w    20   176        BA       blond          91
## 22     Zaida          w    18   175       Abi mittelblond          21

var_labels <- names(read.csv(“starwars.csv”, nrows = 1)) SW2b <- read.csv(“starwars.csv”, stringsAsFactors = FALSE, col.names = var_labels, skip = 5, header =FALSE)

5.11 Einen Datensatz erkunden

nrow(Studis) # Anzahl der Zeilen (Fälle)
## [1] 26
ncol(Studis) # Anzahl der Spalten (Variablen)
## [1] 7
names(Studis) # Variablennamen
## [1] "Name"        "Geschlecht"  "Alter"       "Größe"       "Abschluss"  
## [6] "Haarfarbe"   "Socialmedia"
colnames(Studis) # Namen der Spalten 
## [1] "Name"        "Geschlecht"  "Alter"       "Größe"       "Abschluss"  
## [6] "Haarfarbe"   "Socialmedia"
rownames(Studis) # Namen der Zeilen (nicht sinnvoll in diesem Fall)
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14" "15"
## [16] "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26"
str(Studis) # Angaben über die Struktur der Variablen
## 'data.frame':    26 obs. of  7 variables:
##  $ Name       : chr  "Albert" "Betty" "Carlo" "Dani" ...
##  $ Geschlecht : chr  "m" "w" "m" "d" ...
##  $ Alter      : int  23 21 25 19 24 27 18 22 23 26 ...
##  $ Größe      : int  173 165 168 175 155 171 145 165 192 167 ...
##  $ Abschluss  : chr  "BA" "BA" "MA" "Abi" ...
##  $ Haarfarbe  : chr  "schwarz" "braun" "rot" "blond" ...
##  $ Socialmedia: int  95 71 126 89 101 119 143 25 33 109 ...
summary(Studis) # erste deskriptive Einblicke 
##      Name            Geschlecht            Alter           Größe      
##  Length:26          Length:26          Min.   :18.00   Min.   :145.0  
##  Class :character   Class :character   1st Qu.:20.00   1st Qu.:166.2  
##  Mode  :character   Mode  :character   Median :21.50   Median :174.0  
##                                        Mean   :24.15   Mean   :172.7  
##                                        3rd Qu.:25.00   3rd Qu.:179.8  
##                                        Max.   :67.00   Max.   :194.0  
##   Abschluss          Haarfarbe          Socialmedia    
##  Length:26          Length:26          Min.   :  0.00  
##  Class :character   Class :character   1st Qu.: 53.00  
##  Mode  :character   Mode  :character   Median : 87.50  
##                                        Mean   : 79.08  
##                                        3rd Qu.:105.75  
##                                        Max.   :143.00

6 Listen

Listen sind eine weitere Möglichkeit in R, Daten zu speichern. Es handelt sich bei Listen wie bei Vektoren ebenfalls um eindimensionale Objekte. Allerdings können Listen - anders als Vektoren - Daten unterschiedlicher Typen beinhalten. Damit sind Listen sehr allgemeine und flexible Elemente in R.

6.1 Listen erstellen

Erstellen wir zunächst einen Vektor und betrachten dessen Struktur:

data.vector <- c(1, 2, 3)
print(data.vector)
## [1] 1 2 3
str(data.vector)
##  num [1:3] 1 2 3

Eine Liste wird mit der Funktion list() erzeugt. Der Output der Liste sieht etwas anders aus:

data.list <- list(1, 2, 3)
print(data.list)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3

Die doppelten eckigen Klammern geben die Position jedes Elements der Liste an. Über diese Positionsbezeichnung kann auf die Elemente der Liste zugegriffen werden:

print(data.list[[3]])
## [1] 3

Betrachten wir auch die Struktur der Liste (im Vergleich zur Struktur des oben angelegten Vektors):

str(data.list)
## List of 3
##  $ : num 1
##  $ : num 2
##  $ : num 3

Aus der ersten Zeile geht hervor, dass es sich um eine Liste mit drei Elementen handelt. In den weiteren Zeilen wird für jedes enthaltene Element der Datentyp angezeigt.

Nun kann die Liste, wie oben erwähnt, unterschiedliche Datentypen enthalten:

data.list2 <- list(1, 2, 3, "letzte Chance")
print(data.list2)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3
## 
## [[4]]
## [1] "letzte Chance"

Sogar Vektoren können Teil von Listen sein:

data.list3 <- list(1, 2, 3, c("letzte Chance:", "Vorbei!"))
print(data.list3)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3
## 
## [[4]]
## [1] "letzte Chance:" "Vorbei!"

Überprüfen wir auch hier noch mal die Struktur:

str(data.list3)
## List of 4
##  $ : num 1
##  $ : num 2
##  $ : num 3
##  $ : chr [1:2] "letzte Chance:" "Vorbei!"

Es wird deutlich, dass es sich nun um eine Liste aus vier Elementen handelt und das letzte Element eine Länge von zwei

6.2 Ein Element zur Liste anfügen

Der Liste können mit der Funktion append neue Elemente hinzugefügt werden:

data.list3 <- append(data.list3, "neues Element")
print(data.list3)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3
## 
## [[4]]
## [1] "letzte Chance:" "Vorbei!"       
## 
## [[5]]
## [1] "neues Element"

Im Beispiel wird das neue Element hinten an die Liste angefügt. Soll ein neuer Wert an einer anderen Stelle eingefügt werden, spezifiert man die Stelle mit dem Argument “after =”

data.list4 <- append(data.list3, "Einschub", after = 2)
print(data.list4)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] "Einschub"
## 
## [[4]]
## [1] 3
## 
## [[5]]
## [1] "letzte Chance:" "Vorbei!"       
## 
## [[6]]
## [1] "neues Element"

6.3 Elemente der Liste ändern

Mithilfe des Indexing über die doppelten eckigen Klammern können Elemente in der Liste verändert werden:

data.list3[[3]] <- 100
print(data.list3)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 100
## 
## [[4]]
## [1] "letzte Chance:" "Vorbei!"       
## 
## [[5]]
## [1] "neues Element"

6.4 Elemente der Liste löschen

Elemente der Liste können auch gelöscht werden:

data.list3[[5]] <- NULL
print(data.list3)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 100
## 
## [[4]]
## [1] "letzte Chance:" "Vorbei!"

6.5 Elemente der Liste benennen

Elemente in einer Liste lassen sich benennen (ähnlich der Vergabe von Variablenbezeichnungen):

person <- list(firstname = "Albert", lastname = "Mustermensch")
print(person)
## $firstname
## [1] "Albert"
## 
## $lastname
## [1] "Mustermensch"

In der Darstellung findet sich nun die Schreibweise mit Dollarzeichen statt die Variante mit den doppelt eckigen Klammern. Die Elemente der Liste können nun auch über die Bezeichnung des jeweiligen Elements aufgerufen werden:

print(person$firstname)
## [1] "Albert"

6.6 Eine Liste in einen Vektor umwandeln

Listen können auch in Vektoren umgewandelt werden. Wenn unterschiedliche Datentypen in der Liste vorhanden sind, werden alle Elemente als String-Variablen im Vektor gespeichert.

new.vector <- unlist(data.list3)
print(new.vector)
## [1] "1"              "2"              "100"            "letzte Chance:"
## [5] "Vorbei!"

7 Deskriptive Statistik

Worum gehts?

  • Einen vorliegenden Datensatz verstehen und beschreiben
  • Anzahl und Verteilung von Merkmalen erkunden
  • Den Zahlen wieder Informationen entlocken

Jedes mithilfe deskriptiver Statistik gewonnene Ergebnis trifft damit Aussagen genau über die Gruppe der Merkmalsträger/ Elemente des Datensatzes. Nicht aber darüber hinaus.

Alle folgenden Beispiele beziehen sich auf den “Studis.csv”-Datensatz.

7.1 Häufigkeiten

Häufigkeit (frequencies): Darstellung einer Ausprägung im Verhältnis ihres Auftretens

7.1.1 Absolute Häufigkeit

  • tatsächliches Vorkommen im Sample
  • schwer vergleichbar bei unterschiedlichen Samplen-Größen
  • Darstellung mit der Funktion table()
abschluss <- table(Studis$Abschluss) 
print(abschluss)
## 
## Abi  BA  MA 
##   7  11   8

7.1.2 Relative Häufigkeit

  • Anteil der Ausprägung am Sample in %
  • Darstellung mit der Funktion prop.table()
  • Achtung: Hier wird das Objekt genutzt, das im vorherigen Schritt für die absoluten Häufigkeiten angelegt worden ist. Die Funktion könnte aber auch verschachtelt werden: prop.table(table())
rel_abschluss <- prop.table(abschluss)
print(rel_abschluss)
## 
##       Abi        BA        MA 
## 0.2692308 0.4230769 0.3076923

7.1.3 Kumulierte Häufigkeit

  • gibt zu jedem Wert der Merkmalsausprägung an, wie viele Fälle kleiner oder gleich dem Wert sind
  • addieren sich insgesamt auf 1 (=100%) zusammen
  • Darstellung mit der Funktion cumsum()
kum_abschluss <- cumsum(rel_abschluss) 
print(kum_abschluss)
##       Abi        BA        MA 
## 0.2692308 0.6923077 1.0000000

7.1.4 Häufigkeiten in einer gemeinsamen Tabelle darstellen

Möchte man sich die oben erstellten absoluten, relativen und kumulativen Häufigkeiten zur besseren Übersicht zusammen darstellen lassen, können die erstellten Objekte in einer gemeinsamen Tabelle dargestellt werden. Um die Tabelle lesbarer zu gestalten, werden die mit der Funktion round() alle Werte auf drei Nachkommastellen gerundet.

Bildungsabsch <- cbind(abschluss, rel_abschluss, kum_abschluss)
round(Bildungsabsch, digits = 3)
##     abschluss rel_abschluss kum_abschluss
## Abi         7         0.269         0.269
## BA         11         0.423         0.692
## MA          8         0.308         1.000

7.2 Streuung

  • Wichtig zur Beschreibung der Daten
  • Auskunft darüber, wie weit Datenpunkte voneinander entfernt liegen
  • Streuung bedeutet Variabilität – Ziel von Statistik diese zu verstehen und zu erklären
    – Grundlage für viele weitere statistische Methoden

7.2.1 Minimum, Maximum und Range

Der Abstand zwischen dem kleinsten Wert (Minimum) und dem größten Wert (Maximum) bildet die Range (dt.: Spannweite). Diese wird in der Regel mit einem großen kursiven R dargestellt.

min <- min(Studis$Alter)
print(min)
## [1] 18
max <- max(Studis$Alter)
print(max)
## [1] 67
R <- max - min 
print(R)
## [1] 49

7.2.2 Standardabweichung

  • Angabe, wie weit die Datenpunkte durchschnittlich vom Mittelwert entfernt liegen
sd(Studis$Alter)
## [1] 9.358172

7.2.3 Varianz

  • Quadrierte Standardabweichung
  • Selten als Wert für die Streuung von Daten direkt angegeben aufgrund der schwierigen Interpretierbarkeit
  • Aber zentrale Grundlage für viele weitere statistische Berechnungen, die die Streuung/ Variabilität von Daten aufklären wollen (Varianzaufklärung)
var(Studis$Alter)
## [1] 87.57538

Die Werte für Minimum, Maximum, Range und Standardabweichung lassen sich auch mit der describe() Funktion aus dem psych-Package anzeigen.

# install.packages("psych")
library(psych)
## 
## Attaching package: 'psych'
## The following object is masked from 'package:car':
## 
##     logit
describe(Studis$Alter)
##    vars  n  mean   sd median trimmed  mad min max range skew kurtosis   se
## X1    1 26 24.15 9.36   21.5   22.45 3.71  18  67    49 3.65    13.95 1.84

7.3 Maße der zentralen Tendenz

7.3.1 Modus

Der Modus einer kategorialen Häufigkeitsverteilung ist der Wert der häufigsten Merkmalsausprägung (auch mehrere Modalwerte möglich, wenn mehrere Merkmale gleich häufig auftreten). Es gibt in R keine direkte Funktion, um sich den Modus ausgeben zu lassen. Über die Anzeige der absoluten Häufigkeiten lässt sich aber der Wert mit der häufigsten Merkmalsausprägung ablesen. Im Beispiel beträgt der Modus 21 (das Alter 21 kommt 5mal vor).

table(Studis$Alter)
## 
## 18 19 20 21 22 23 24 25 26 27 28 31 67 
##  3  2  3  5  1  2  2  2  2  1  1  1  1

7.3.2 Mittelwert oder arithmetisches Mittel

Der Mittelwert ist der Durchschnitt der Werte, der sich aus der Summe aller Werte geteilt durch die Anzahl der Werte ergibt. Achtung: Extremwerte (wenige sehr große oder kleine Werte) beeinflussen den Mittelwert stark. Es ist immer sinnvoll, den Median ebenfalls zu betrachten.

mean(Studis$Alter)
## [1] 24.15385

7.3.3 Median

Der Median ist der Zentralwert eines Merkmals und damit die Ausprägung des Falls in der Mitte der der Größe nach geordneten Fälle. Bei einer gerade Anzahl wird die Ausprägung der beiden in der Mitte liegenden Fälle ermittelt. Im Gegensatz zum Mittelwert ist der Median weniger sensitiv gegenüber Extremwerten.

median(Studis$Alter)
## [1] 21.5

Median und Mittelwert werden sowohl bei der describe()-Funktion als ach bei der summary()-Funktion ausgegeben.

summary(Studis$Alter)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   18.00   20.00   21.50   24.15   25.00   67.00

7.4 Kreuztabellen

Kreuztabellen dienen der Darstellung des gemeinsamen Auftretens von nominalen oder ordinalen Merkmalsausprägungen. Kreuztabellen lassen sich ebenfalls mit der Funktion table() erstellen. Es werden die absoluten Häufigkeiten des gemeinsamen Auftretens zweier Merkmale dargestellt:

table(Studis$Geschlecht, Studis$Abschluss)
##    
##     Abi BA MA
##   d   1  1  0
##   m   2  7  5
##   w   4  3  3

Um mit der Kreuztabelle weiterarbeiten zu können, bietet es sich an, diese in einem eigenen Objekt zu speichern:

xtab <- table(Studis$Geschlecht, Studis$Abschluss)

Wichtig für die weitere Arbeit mit Kreuztabellen sind die sogenannten Randverteilungen, also die Summen der Werte in den Zeilen und Spalten. Diese lassen sich mit der Funktion addmargins() anzeigen. Dazu kann das oben angelegte Objekt genutzt werden.

addmargins(xtab)
##      
##       Abi BA MA Sum
##   d     1  1  0   2
##   m     2  7  5  14
##   w     4  3  3  10
##   Sum   7 11  8  26

Möchte man die relativen Häufigkeiten der gemeinsamen Merkmalsausprägungen betrachten, wird die Funktion prop.table() genutzt. Bei der folgenden Darstellung ist die Basis für die Berechnung der relativen Werte die Gesamtzahl der Fälle. In diesem Fall ist N=26.

prop.table(xtab)
##    
##            Abi         BA         MA
##   d 0.03846154 0.03846154 0.00000000
##   m 0.07692308 0.26923077 0.19230769
##   w 0.15384615 0.11538462 0.11538462

Oftmals interessieren aber auch die relativen Anteile einer Ausprägung innerhalb einer Gruppe. Dazu nutzt man die Angabe von Zeilen- und Spaltenprozenten anstatt der Gesamtprozente. Dazu muss das Argument “margin =” spezifiziert werden. Das folgende Beispiel zeigt die Zeilenprozente. Die Funktion wird mit der Funktion addmargins() verschachtelt um die Randverteilungen darzustellen. Daraus lässt sich ablesen, dass sich nun die Werte in der Zeile auf 100% aufaddieren.

addmargins(prop.table(xtab, margin = 1))
##      
##             Abi        BA        MA       Sum
##   d   0.5000000 0.5000000 0.0000000 1.0000000
##   m   0.1428571 0.5000000 0.3571429 1.0000000
##   w   0.4000000 0.3000000 0.3000000 1.0000000
##   Sum 1.0428571 1.3000000 0.6571429 3.0000000

Die Tabelle liest sich nun also so: 40% aller Frauen im Datensatz haben “Abi” als höchsten Bildungsabschluss angegeben. 30 % der Frauen haben einen Bachelor, weitere 30 % einen Master. 40 + 30 + 30 ergeben dann 100% der Frauen.

Für die Spaltenprozente muss das Argument “margin = 2” gesetzt werden:

addmargins(prop.table(xtab, margin = 2))
##      
##              Abi         BA         MA        Sum
##   d   0.14285714 0.09090909 0.00000000 0.23376623
##   m   0.28571429 0.63636364 0.62500000 1.54707792
##   w   0.57142857 0.27272727 0.37500000 1.21915584
##   Sum 1.00000000 1.00000000 1.00000000 3.00000000

Es addieren sich nun die Spalten auf 100 % zusammen. Entsprechend würde man die Tabelle lesen: Von allen Personen, die Abi als höchsten Abschluss angeben haben, sind 14,28 % divers, 28,57 % männlich und 57,14 % weiblich.

Ist die Tabelle zu unübersichtlich, kann mit der Funktion round(x, digits = 3) gerundet werden.
(Beachte: Um die Code-Zeile etwas lesbarer zu gestalten, werden im folgenden Beispiel Zeilenumbrüche und Einrückungen verwendet.)

round(
  addmargins(
    prop.table(xtab, margin = 2)), 
      digits = 3)
##      
##         Abi    BA    MA   Sum
##   d   0.143 0.091 0.000 0.234
##   m   0.286 0.636 0.625 1.547
##   w   0.571 0.273 0.375 1.219
##   Sum 1.000 1.000 1.000 3.000

8 Visualisierungen mit R-Basics

Einfache Visualisierungen helfen beim Verständnis von Daten, lassen erste Muster erkennen, Ausreißer identifizieren etc. Die folgenden Abschnitte behandeln einfache Visualisierungen mit dem Basic-Paket von R. Solche stilistisch einfachen Grafiken dienen eher der Exploration von Daten im eigenen Arbeitsprozess. Für die grafische Aufbereitung zum Zwecke der Veröffentlichung bietet sich die Arbeit mit dem Package ggplot2 an, das allerdings seine eigene Grammatik mitbringt. Die Grundlagen von ggplot2 werden in einem späteren Abschnitt behandelt.

8.1 Einfaches Balkendiagramm

Die Funktion zur Erstellung eines Balkendiagramms ist barplot(). Mithilfe von Balkendiagramm lassen sich sowohl absolute als auch relative Häufigkeiten anzeigen. Im Folgenden sollen die Häufigkeiten für das Alter aus dem Studi-Datensatz als Balkendiagramm visualisiert werden. Damit die absoluten Häufigkeiten verwendet werden, muss als Grundlage für die Visualisierung die Häufigkeitstabelle genutzt werden.

barplot(table(Studis$Alter))

Im Folgenden werden die Achsen beschriftet. Zuerst die x-Achse mit dem Argument “xlab =”

barplot(table(Studis$Alter), 
        xlab = "Alter")

Anschließend die y-Achse mit dem Argument “ylab =”

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl")

Mit dem Argument “nain =” lässt sich eine Überschrift einfügen:

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm")

Die Größe der Beschriftungen lassen sich mit den Argumenten “cex.axis =” für die y-Achse und “cex.names =” für die x-Achse anpassen:

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm", 
        cex.axis = 2, cex.names = 2)

Die Farbe der Balken kann mit dem Argument “col =” verändert werden:

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm", 
        cex.axis = 1.2, cex.names = 1.2, 
        col = "lightblue") 

Weitere Farbanpassungen für die Achsenbeschriftungen (Werte und Titel) die Überschrift können mit “col.main”, “col.axis” und “col.lab” vorgenommen werden. Eine schöne Übersicht über die Möglichkeiten der Farbgebung findet sich hier: https://bjoernwalther.com/wp-content/uploads/2019/12/col9.png

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm", 
        cex.axis = 1.2, cex.names = 1.2, 
        col = "lightblue", col.axis = "darkred", 
        col.main = "darkgreen", col.lab = "darkblue") 

Mit dem Argument “axis.lty = 1” werden Striche zur x-Achse hinzugefügt, um Balken und Werte besser miteinander zu verbinden:

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm", 
        cex.axis = 1.2, cex.names = 1.2, 
        col = "lightblue", col.axis = "darkred", 
        col.main = "darkgreen", col.lab = "darkblue",
        axis.lty = 1)

Zu guter Letzt kann die Beschriftung an der x-Achse gedreht werden mit dem Argument “las = 2”, was der besseren Lesbarkeit dienen kann, wenn nicht nur Zahlen sondern Begriffe für die Beschriftung benötigt werden.

barplot(table(Studis$Alter), 
        xlab = "Alter", ylab = "Anzahl", 
        main = "Säulendiagramm", 
        cex.axis = 1.2, cex.names = 1.2, 
        col = "lightblue", col.axis = "darkred", 
        col.main = "darkgreen", col.lab = "darkblue",
        axis.lty = 1,
        las = 2)

8.2 Einfaches Streudiagramm

Streudiagramme (engl.: scatter plot) sind Darstellungen vom gemeinsamen Auftreten von mindestens zwei i.d.R. metrischen Variablen in einem Koordinatensystem. Mithilfe von Streudiagrammen lassen sich zum Beispiel die Streuung der Daten wahrnehmen, erste Eindrücke von möglichen Zusammenhängen erkennen, sowie Ausreißer identifizieren.

Der Befehl zur Erstellung des Streudiagramms im Basic-Paket von R lautet plot(). Im Folgenden werden das Alter und die Socialmedianutzung (in Minuten) der Studierenden im Datensatz angezeigt. Es lässt sich direkt ein Ausreißer identifizieren: (Mindestens) Eine Person ist deutlich älter als der Rest, der insgesamt eher altershomogen ist. Die durchschnittliche Socialmedianutzung scheint stärker zu streuen. Auf den ersten Blick lässt sich in der Punktwolke kein Zusammenhang zwischen Alter und Socialmedianutzung erkennen.

plot(Studis$Alter, Studis$Socialmedia)

+

Im Folgenden soll es allerdings weniger um die Interpretation gehen als vielmehr um die Gestaltung des Diagramms. Analog zum Balkendiagramm oben lassen beispielsweise die Beschriftungen ändern:

plot(Studis$Alter, Studis$Socialmedia, 
     xlab = "Alter", ylab = "Socialmedianutzung in Minuten", 
     main = "Streudiagramm")

Desweiteren lassen sich die Farbe, Form und Größe der Datenpunkte verändern. Das Argument “pch =” dient dabei zur Definition der Form. Die folgende Abbildung zeigt die Formen und dazugehörigen Ziffern, die dem Argument eingefügt werden können:

Im Beispiel könnte es dann also wie folgt aussehen:

plot(Studis$Alter, Studis$Socialmedia, 
     xlab = "Alter", ylab = "Socialmedianutzung in Minuten", 
     main = "Streudiagramm",
     col = "steelblue", pch = 16, cex = 2)

Auch kann es hilfreich sein, den Wertebereich der Achsen manuell einzustellen. Das lässt sich mit den Argumenten “xlim =” für die x-Achse und “ylim =” für die y-Achse lösen. Das folgende Beispiel verändert den Wertebereich der x-Achse:

plot(Studis$Alter, Studis$Socialmedia, 
     xlab = "Alter", ylab = "Socialmedianutzung in Minuten", 
     main = "Streudiagramm",
     col = "steelblue", pch = 16, cex = 1.5,
     xlim = c(0,80))

8.3 Histogramm

Das Histogramm sieht dem Balkendiagramm auf den ersten Blick sehr ähnlich, hat aber einige Besonderheiten. Es dient in erster Linie dazu, die Häufigkeitsverteilung einer i.d.R. metrischen Variable darzustellen.
Die Funktion zur Erstellung lautet hist(). Das Beispiel zeigt das Histogramm für die Socialmedianutzung und fügt direkt schon die bekannten Argumente zur Achsenbeschriftung und Farbgebung ein.

hist(Studis$Socialmedia, 
     main = "Histogramm Social Media Nutzung", 
     xlab = "Social Media Nutzung in Minuten", ylab = "Häufigkeit",
     col = "plum3")

Mit dem Argument “breaks =” lassen sich die Abstände der Balken verändern. Wenn wir wissen, dass sich die Werte etwa zwischen 0 und 150 befinden, scheint es angemessen, 15 Gruppen zu bilden, sodass ein Balken jeweils einen 10er Schritt abdeckt:

hist(Studis$Socialmedia, 
     main = "Histogramm Social Media Nutzung", 
     xlab = "Social Media Nutzung in Minuten", ylab = "Häufigkeit",
     col = "plum3",
     breaks = 15)

Das sieht schon ganz gut aus, allerdings ist es ein Problem, dass nicht jeder Balken eine eigene Beschriftung hat, sodass beispielsweise nicht zu erkennen ist, in welchen Wertebereichen offenbar keine Fälle liegen (dort wo keine Balken sind). Im nächsten Schritt entfernen wir deshalb erst die Beschriftung der x-Achse mit dem Argument “xaxt=‘n’”, um dann in der nächsten Zeile die Form der Beschriftung neu zu definieren. Wir geben an, dass wir eine Sequenz von 0 bis 150 in 10er Schritten hinzufügen wollen:

hist(Studis$Socialmedia, 
     main = "Histogramm Social Media Nutzung", 
     xlab = "Social Media Nutzung in Minuten", ylab = "Häufigkeit",
     col = "plum3", 
     breaks = 15, xaxt='n')
axis(side=1, at=seq(0,150, 10))

## Boxplot

Text zur Erklärung von Boxplots

boxplot(Studis$Socialmedia)

Spannend sind Boxplots vor allem auch, um sich Unterschiede in der Streuung bei unterschiedlichen Gruppen anzuschauen.

boxplot(Studis$Socialmedia ~ Studis$Abschluss)

Auch hier lassen sich natürlich Beschriftungen und Farben verändern:

boxplot(Studis$Socialmedia ~ Studis$Abschluss,
        ylab = "Socialmedianutzung in Minuten",
        xlab = "Bildungsabschluss",
        main = "Socialmedianutzung nach Bildungsabschluss",
        col=c("chartreuse3", "firebrick2", "royalblue3"))

8.4 Weitere Merkmale

Weitere Argumente zum Bearbeiten der Diagramme sind zum Beispiel “cex.main” und “cex.lab” für die Schriftgrößen der Überschrift und y-Achsentitel. Auch die Schrift lässt sich verändern für die Überschrift “font.main”, den Untertitel “font.sub” und die Achsen “font.lab”, wobei folgende Codes nutzbar sind:
1 = normal
2 = fett
3 = kursiv
4 = fett und kursiv
5 = griechisch

9 Datenbereinigung und -aufarbeitung

9.1 Subsets und Filter

Im Kapitel Data Frames wurde bereits angerissen, wie über das Indexing mit eckigen Klammern sowie die Verwendung und Kombination von Bedingungen mithilfe logischer Operatoren Fälle ausgwählt werden können, um sie in einem verkleinerten Data Frame zu speichern.

Es gibt zwei weitere Möglichkeiten, einen (großen) Datensatz zu verkleinern, um sich auf die für die eigene Analyse wichtigen Aspekte zu konzentrieren und ein Subset zu bilden: (1) Auswahl bestimmter Variablen (2) Auswahl bestimmter Fälle

Beide Aufgaben lassen sich sehr effizient mit Funktionen aus dem “tidyverse”-Package erledigen. Ein Vorteil der Verwendung der Funktionen aus dem tidyverse-Package ist, dass diese dann auch in Kombination mit dem sogenannten Pipe-Operator verwendet werden können. Dieser wird am Ende des Kapitels eingeführt.

# install.packages("tidyverse")
library(tidyverse)

9.1.1 Auswahl bestimmter Variablen

Um ein Subset mit einer Auswahl an Variablen zu bilden, kann die Funktion select() genutzt werden, wenn die Variable in einer eigenen Spalte angelegt ist. In der select-Funktion wird dann der Datensatz angegeben sowie die auszuwählenden Spalten.

Studis_klein <- select(Studis, Name, Abschluss)
print(Studis_klein)
##         Name Abschluss
## 1     Albert        BA
## 2      Betty        BA
## 3      Carlo        MA
## 4       Dani       Abi
## 5       Emma        MA
## 6     Fritzi        MA
## 7       Gina       Abi
## 8      Hakim        BA
## 9        Ivo        BA
## 10    Jasmin        MA
## 11     Kalle        MA
## 12    Ludwig        BA
## 13      Mila       Abi
## 14      Nael        BA
## 15       Ole        BA
## 16     Pablo        MA
## 17    Quamar       Abi
## 18     Ronja       Abi
## 19    Sascha        BA
## 20     Tarek        MA
## 21   Ulfried       Abi
## 22 Valentino        BA
## 23     Wanda        BA
## 24      Xeno        MA
## 25      Yuna        BA
## 26     Zaida       Abi

Wenn die aktuell nicht benötigten Variablen beibehalten werden sollen, kann mit dem Argument “everything” dafür gesorgt werden, dass die restlichen Variablen hintenangestellt werden:

Studis_umgeordnet <- select(Studis, Name, Abschluss, everything())
print(Studis_umgeordnet)
##         Name Abschluss Geschlecht Alter Größe   Haarfarbe Socialmedia
## 1     Albert        BA          m    23   173     schwarz          95
## 2      Betty        BA          w    21   165       braun          71
## 3      Carlo        MA          m    25   168         rot         126
## 4       Dani       Abi          d    19   175       blond          89
## 5       Emma        MA          w    24   155 mittelblond         101
## 6     Fritzi        MA          w    27   171     schwarz         119
## 7       Gina       Abi          w    18   145       braun         143
## 8      Hakim        BA          m    22   165         rot          25
## 9        Ivo        BA          m    23   192       blond          33
## 10    Jasmin        MA          w    26   167 mittelblond         109
## 11     Kalle        MA          m    26   166     schwarz          51
## 12    Ludwig        BA          m    21   194       braun         111
## 13      Mila       Abi          w    20   155         rot          32
## 14      Nael        BA          m    21   192       blond         102
## 15       Ole        BA          m    20   181 mittelblond          76
## 16     Pablo        MA          m    24   170     schwarz          86
## 17    Quamar       Abi          m    19   176       braun          59
## 18     Ronja       Abi          w    18   167         rot         110
## 19    Sascha        BA          d    21   177       blond          41
## 20     Tarek        MA          m    25   179 mittelblond          81
## 21   Ulfried       Abi          m    67   180        grau           0
## 22 Valentino        BA          m    31   182     schwarz          91
## 23     Wanda        BA          w    28   162       braun         107
## 24      Xeno        MA          m    21   183         rot          86
## 25      Yuna        BA          w    20   176       blond          91
## 26     Zaida       Abi          w    18   175 mittelblond          21

9.1.2 Auswahl von Fällen (Filter-Funktion)

Zur Auswahl von bestimmten Fällen gibt es im tidyverse-Packet die Funktion filter(), die mithilfe logischer Operatoren verwendet werden kann.

filter(Studis, Haarfarbe == "schwarz") #  genau gleich
##        Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia
## 1    Albert          m    23   173        BA   schwarz          95
## 2    Fritzi          w    27   171        MA   schwarz         119
## 3     Kalle          m    26   166        MA   schwarz          51
## 4     Pablo          m    24   170        MA   schwarz          86
## 5 Valentino          m    31   182        BA   schwarz          91
filter(Studis, Haarfarbe != "schwarz") #  ungleich
##       Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1    Betty          w    21   165        BA       braun          71
## 2    Carlo          m    25   168        MA         rot         126
## 3     Dani          d    19   175       Abi       blond          89
## 4     Emma          w    24   155        MA mittelblond         101
## 5     Gina          w    18   145       Abi       braun         143
## 6    Hakim          m    22   165        BA         rot          25
## 7      Ivo          m    23   192        BA       blond          33
## 8   Jasmin          w    26   167        MA mittelblond         109
## 9   Ludwig          m    21   194        BA       braun         111
## 10    Mila          w    20   155       Abi         rot          32
## 11    Nael          m    21   192        BA       blond         102
## 12     Ole          m    20   181        BA mittelblond          76
## 13  Quamar          m    19   176       Abi       braun          59
## 14   Ronja          w    18   167       Abi         rot         110
## 15  Sascha          d    21   177        BA       blond          41
## 16   Tarek          m    25   179        MA mittelblond          81
## 17 Ulfried          m    67   180       Abi        grau           0
## 18   Wanda          w    28   162        BA       braun         107
## 19    Xeno          m    21   183        MA         rot          86
## 20    Yuna          w    20   176        BA       blond          91
## 21   Zaida          w    18   175       Abi mittelblond          21
filter(Studis, Alter < 20) # kleiner als
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1   Dani          d    19   175       Abi       blond          89
## 2   Gina          w    18   145       Abi       braun         143
## 3 Quamar          m    19   176       Abi       braun          59
## 4  Ronja          w    18   167       Abi         rot         110
## 5  Zaida          w    18   175       Abi mittelblond          21
filter(Studis, Alter > 27) # größer als
##        Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia
## 1   Ulfried          m    67   180       Abi      grau           0
## 2 Valentino          m    31   182        BA   schwarz          91
## 3     Wanda          w    28   162        BA     braun         107
filter(Studis, Größe <= 175) # kleiner gleich
##      Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1  Albert          m    23   173        BA     schwarz          95
## 2   Betty          w    21   165        BA       braun          71
## 3   Carlo          m    25   168        MA         rot         126
## 4    Dani          d    19   175       Abi       blond          89
## 5    Emma          w    24   155        MA mittelblond         101
## 6  Fritzi          w    27   171        MA     schwarz         119
## 7    Gina          w    18   145       Abi       braun         143
## 8   Hakim          m    22   165        BA         rot          25
## 9  Jasmin          w    26   167        MA mittelblond         109
## 10  Kalle          m    26   166        MA     schwarz          51
## 11   Mila          w    20   155       Abi         rot          32
## 12  Pablo          m    24   170        MA     schwarz          86
## 13  Ronja          w    18   167       Abi         rot         110
## 14  Wanda          w    28   162        BA       braun         107
## 15  Zaida          w    18   175       Abi mittelblond          21
filter(Studis, Größe >= 180) # größer gleich
##        Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1       Ivo          m    23   192        BA       blond          33
## 2    Ludwig          m    21   194        BA       braun         111
## 3      Nael          m    21   192        BA       blond         102
## 4       Ole          m    20   181        BA mittelblond          76
## 5   Ulfried          m    67   180       Abi        grau           0
## 6 Valentino          m    31   182        BA     schwarz          91
## 7      Xeno          m    21   183        MA         rot          86

Mit den Operatoren & (“und”) sowie | (“oder”) lassen sich Bedingungen auch verknüpfen.

filter(Studis, Haarfarbe == "schwarz" & Geschlecht == "w") # und
##     Name Geschlecht Alter Größe Abschluss Haarfarbe Socialmedia
## 1 Fritzi          w    27   171        MA   schwarz         119
filter(Studis, Größe <=160 | Größe >= 190) # oder 
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia
## 1   Emma          w    24   155        MA mittelblond         101
## 2   Gina          w    18   145       Abi       braun         143
## 3    Ivo          m    23   192        BA       blond          33
## 4 Ludwig          m    21   194        BA       braun         111
## 5   Mila          w    20   155       Abi         rot          32
## 6   Nael          m    21   192        BA       blond         102

9.2 Fehlende Werte

Fehlende Werte zu identifizieren in einem Datensatz ist ein wichtiger Arbeitsschritt, bevor mit der eigentlichen Datenbereinigung begonnen werden kann. Denn fehlende Werte können zu Problemen führen. Die folgenden Beispiele beziehen sich auf den Studis-Datensatz, dem allerdings einige fehlende Werte beigemischt worden sind.

#Datensatz mit fehlenden Werten bei Haarfarbe und Socialmedianutzung
Studis.na <- read.csv2("Studis_na.csv") 

Soll nun die durchschnittliche Socialmedianutzung berechnet werden, gibt es ein Problem:

mean(Studis.na$Socialmedia)
## [1] NA

Das Problem kann behoben werden, indem das Argument “na.rm = TRUE” in die Funktion aufgenommen wird.

mean(Studis.na$Socialmedia, na.rm = TRUE)
## [1] 76.26087

Jeder Datensatz sollte daher auf fehlende Werte überprüft werden. Optimalerweise werden fehlende Werte mit NA im Datensatz gekennzeichnet.

Enthält der Datensatz also mit NA-gekennzeichnete Werte?

anyNA(Studis.na)
## [1] TRUE

Als Ausgabe bekommen wir “TRUE” angegeben, also enthält der Datensatz fehlende Werte. Aber wo nur?

is.na(Studis.na)
##        Name Geschlecht Alter Größe Schulabschluss Haarfarbe Socialmedianutzung
##  [1,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [2,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [3,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [4,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [5,] FALSE      FALSE FALSE FALSE          FALSE     FALSE               TRUE
##  [6,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [7,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [8,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
##  [9,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [10,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [11,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [12,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [13,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [14,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [15,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [16,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [17,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [18,] FALSE      FALSE FALSE FALSE          FALSE     FALSE               TRUE
## [19,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [20,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [21,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [22,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [23,] FALSE      FALSE FALSE FALSE          FALSE      TRUE              FALSE
## [24,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE
## [25,] FALSE      FALSE FALSE FALSE          FALSE     FALSE               TRUE
## [26,] FALSE      FALSE FALSE FALSE          FALSE     FALSE              FALSE

Überall dort, wo TRUE als Wert verzeichnet ist, gibt es also offenbar einen fehlenden Wert. Die Übersicht ist allerdings maximal unübersichtlich. Mit dem folgenden Befehl ist auf den ersten Blick ersichtlich, bei welchen Variablen wie viele NAs zu finden sind:

colSums(is.na(Studis.na))
##               Name         Geschlecht              Alter              Größe 
##                  0                  0                  0                  0 
##     Schulabschluss          Haarfarbe Socialmedianutzung 
##                  0                  1                  3

Die Gesamtzahl der fehlenden Werte im Datensatz oder bei einer Variablen lässt sich auch mit der table-Funktion abzeigen.

table(is.na(Studis.na))
## 
## FALSE  TRUE 
##   178     4
table(is.na(Studis.na$Socialmedianutzung))
## 
## FALSE  TRUE 
##    23     3

Allerdings sind fehlende Werte nicht immer einheitlich mit NA gekennzeichnet. Manchmal wird bei der Erstellung eines Datensatzes ein Wert wie “-99” oder ähnliches verwendet. Darum sollte man sich immer auch die deskriptive Statistik einer Variablen anschauen, im Beispiel für die Haarfarbe:

table(Studis.na$Haarfarbe)
## 
##         -99       blond       braun        grau mittelblond         rot 
##           1           5           4           1           5           4 
##     schwarz 
##           5

Wir wissen aus der Übersicht oben, dass die Haarfarbe einen mit NA gekennzeichneten Wert hat. Dieser wird wiederum mit der table-Funktion nicht angezeigt. Aber wir sehen, dass es einen Wert mit “-99” bei der Variablen Haarfarbe gibt, der offenbar auch einen fehlenden Wert markiert. Die Bezeichnung fehlender Werte sollte natürlich einheitlich sein, weshalb die “-99” auch in NA umgewandelt werden sollten:

Studis.na$Haarfarbe[Studis.na$Haarfarbe == -99] <- NA
table(is.na(Studis.na$Haarfarbe))
## 
## FALSE  TRUE 
##    24     2

Ist bekannt, dass im gesamten Datensatz ein anderer Wert als NA zur Kennzeichnung fehlender Werte genutzt worden ist, lässt sich dieses Problem auch in einem Schritt beheben, wie das folgende Code-Beispiel zeigt.

Studis.na[Studis.na == -99] <- NA

Eine andere Variante wäre es, die mit NA gekennzeichneten Werte durch einen inhaltlichen Wert zu ersetzen (dann taucht der Wert auch in der Übersicht mit der table-Funktion auf), zum Beispiel wie folgt:

Studis.na$Haarfarbe[is.na(Studis.na$Haarfarbe)] <- "unbekannt"
table(Studis.na$Haarfarbe)
## 
##       blond       braun        grau mittelblond         rot     schwarz 
##           5           4           1           5           4           5 
##   unbekannt 
##           2

Bei nummerischen Werten ist es auch denkbar, die fehlenden Werte durch den Durchschnitt oder den Median aller Werte der jeweiligen Variablen zu ersetzen. Ein solcher Schritt sollte gut abgewogen werden, denn er hat beispielsweise Auswirkungen auf die Streuung der Gesamtvariablen (die dadurch kleiner wird).

Studis.na$Socialmedianutzung[is.na(Studis.na$Socialmedianutzung)] <- mean(Studis.na$Socialmedianutzung, na.rm = TRUE)
print(Studis.na$Socialmedianutzung)
##  [1]  95.00000  71.00000 126.00000  89.00000  76.26087 119.00000 143.00000
##  [8]  25.00000  33.00000 109.00000  51.00000 111.00000  32.00000 102.00000
## [15]  76.00000  86.00000  59.00000  76.26087  41.00000  81.00000   0.00000
## [22]  91.00000 107.00000  86.00000  76.26087  21.00000

Analog würde für den Median der Befehl “median(Studis.na$Socialmedianutzung, na.rm = TRUE)” genutzt werden können.

9.3 Berechnung neuer Variablen

Mit der Funktion mutate() lassen sich aus bzw. mit bestehenden Variablen neue Variablen berechnen. Damit können zum Beispiel auch Umrechnungen vorgenommen werden. Im Beispiel wird die in cm angegebene Größe der Studierenden in feet umgerechnet (wobei 1 feet = 30,48 cm). (Für die exemplarische Darstellung wird im Folgenden nur noch ein Subset aus den ersten fünf Fällen angezeigt.)

Studis <- mutate(Studis, feet = Größe/30.48)
print(Studis[c(1:5),])
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia     feet
## 1 Albert          m    23   173        BA     schwarz          95 5.675853
## 2  Betty          w    21   165        BA       braun          71 5.413386
## 3  Carlo          m    25   168        MA         rot         126 5.511811
## 4   Dani          d    19   175       Abi       blond          89 5.741470
## 5   Emma          w    24   155        MA mittelblond         101 5.085302

Hinweis: Die Funktion transmute() berechnet eine neue Variable, ohne die alten Variablen im Datensatz zu behalten.

9.4 Umkodieren in eine andere Variable

Neben der Berechnung neuer Variablen ist auch das Umkodieren bestehender Variablen eine häufig genutzte Operation. Variablen umkodieren kann nötig werden, wenn Variablen nicht in der erwünschten Form vorliegen. Das folgende Beispiel zeigt die Dichotomisierung der metrisch-skalierten Variablen “Socialmedia”. Ziel soll es sein, eine nominale Variable mit zwei Ausprägungen zu erstellen: gering (für Socialmedianutzung unter 30min) und hoch (für Socialmedianutzung über 30min). Dafür wird die Funktion recode() aus dem car-Package genutzt.
(Achtung: weil es in einem anderen Package ebenfalls einen recode-Befehl gibt, kann es hier zu Problemen kommen. Um R mitzuteilen, welcher Befehl genutzt werden soll, wird vor der recode-Funktion noch der Zusatz “car::” angefüht. Diese Schreibweise mit den zwei Doppelpunkten verweist auf das zu nutzende Package.)

library(car)
Studis$socialm_kat <- car::recode(Studis$Socialmedia, "lo:30 = 1; 31:hi = 2") 
print(Studis)
##         Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia     feet
## 1     Albert          m    23   173        BA     schwarz          95 5.675853
## 2      Betty          w    21   165        BA       braun          71 5.413386
## 3      Carlo          m    25   168        MA         rot         126 5.511811
## 4       Dani          d    19   175       Abi       blond          89 5.741470
## 5       Emma          w    24   155        MA mittelblond         101 5.085302
## 6     Fritzi          w    27   171        MA     schwarz         119 5.610236
## 7       Gina          w    18   145       Abi       braun         143 4.757218
## 8      Hakim          m    22   165        BA         rot          25 5.413386
## 9        Ivo          m    23   192        BA       blond          33 6.299213
## 10    Jasmin          w    26   167        MA mittelblond         109 5.479003
## 11     Kalle          m    26   166        MA     schwarz          51 5.446194
## 12    Ludwig          m    21   194        BA       braun         111 6.364829
## 13      Mila          w    20   155       Abi         rot          32 5.085302
## 14      Nael          m    21   192        BA       blond         102 6.299213
## 15       Ole          m    20   181        BA mittelblond          76 5.938320
## 16     Pablo          m    24   170        MA     schwarz          86 5.577428
## 17    Quamar          m    19   176       Abi       braun          59 5.774278
## 18     Ronja          w    18   167       Abi         rot         110 5.479003
## 19    Sascha          d    21   177        BA       blond          41 5.807087
## 20     Tarek          m    25   179        MA mittelblond          81 5.872703
## 21   Ulfried          m    67   180       Abi        grau           0 5.905512
## 22 Valentino          m    31   182        BA     schwarz          91 5.971129
## 23     Wanda          w    28   162        BA       braun         107 5.314961
## 24      Xeno          m    21   183        MA         rot          86 6.003937
## 25      Yuna          w    20   176        BA       blond          91 5.774278
## 26     Zaida          w    18   175       Abi mittelblond          21 5.741470
##    socialm_kat
## 1            2
## 2            2
## 3            2
## 4            2
## 5            2
## 6            2
## 7            2
## 8            1
## 9            2
## 10           2
## 11           2
## 12           2
## 13           2
## 14           2
## 15           2
## 16           2
## 17           2
## 18           2
## 19           2
## 20           2
## 21           1
## 22           2
## 23           2
## 24           2
## 25           2
## 26           1

9.5 Werte der Größe nach ordnen

Mit der Funktion arrange() lassen sich Variablenwerte der Größe nach ordnen. In der default-Variante beginnend mit dem kleinsten Wert, mit dem Zusatzargument “desc()” beginnend mit dem größten Wert. Die Sortierung nach der Größe von Variablen kann zum Beispiel dazu dienen, die Fälle mit den kleinsten und größten Werten zu identifizieren und ggf. auch Extremwerte/ Ausreißer zu erkennen.

arrange(Studis, Alter)
##         Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia     feet
## 1       Gina          w    18   145       Abi       braun         143 4.757218
## 2      Ronja          w    18   167       Abi         rot         110 5.479003
## 3      Zaida          w    18   175       Abi mittelblond          21 5.741470
## 4       Dani          d    19   175       Abi       blond          89 5.741470
## 5     Quamar          m    19   176       Abi       braun          59 5.774278
## 6       Mila          w    20   155       Abi         rot          32 5.085302
## 7        Ole          m    20   181        BA mittelblond          76 5.938320
## 8       Yuna          w    20   176        BA       blond          91 5.774278
## 9      Betty          w    21   165        BA       braun          71 5.413386
## 10    Ludwig          m    21   194        BA       braun         111 6.364829
## 11      Nael          m    21   192        BA       blond         102 6.299213
## 12    Sascha          d    21   177        BA       blond          41 5.807087
## 13      Xeno          m    21   183        MA         rot          86 6.003937
## 14     Hakim          m    22   165        BA         rot          25 5.413386
## 15    Albert          m    23   173        BA     schwarz          95 5.675853
## 16       Ivo          m    23   192        BA       blond          33 6.299213
## 17      Emma          w    24   155        MA mittelblond         101 5.085302
## 18     Pablo          m    24   170        MA     schwarz          86 5.577428
## 19     Carlo          m    25   168        MA         rot         126 5.511811
## 20     Tarek          m    25   179        MA mittelblond          81 5.872703
## 21    Jasmin          w    26   167        MA mittelblond         109 5.479003
## 22     Kalle          m    26   166        MA     schwarz          51 5.446194
## 23    Fritzi          w    27   171        MA     schwarz         119 5.610236
## 24     Wanda          w    28   162        BA       braun         107 5.314961
## 25 Valentino          m    31   182        BA     schwarz          91 5.971129
## 26   Ulfried          m    67   180       Abi        grau           0 5.905512
##    socialm_kat
## 1            2
## 2            2
## 3            1
## 4            2
## 5            2
## 6            2
## 7            2
## 8            2
## 9            2
## 10           2
## 11           2
## 12           2
## 13           2
## 14           1
## 15           2
## 16           2
## 17           2
## 18           2
## 19           2
## 20           2
## 21           2
## 22           2
## 23           2
## 24           2
## 25           2
## 26           1
arrange(Studis, desc(Alter))
##         Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia     feet
## 1    Ulfried          m    67   180       Abi        grau           0 5.905512
## 2  Valentino          m    31   182        BA     schwarz          91 5.971129
## 3      Wanda          w    28   162        BA       braun         107 5.314961
## 4     Fritzi          w    27   171        MA     schwarz         119 5.610236
## 5     Jasmin          w    26   167        MA mittelblond         109 5.479003
## 6      Kalle          m    26   166        MA     schwarz          51 5.446194
## 7      Carlo          m    25   168        MA         rot         126 5.511811
## 8      Tarek          m    25   179        MA mittelblond          81 5.872703
## 9       Emma          w    24   155        MA mittelblond         101 5.085302
## 10     Pablo          m    24   170        MA     schwarz          86 5.577428
## 11    Albert          m    23   173        BA     schwarz          95 5.675853
## 12       Ivo          m    23   192        BA       blond          33 6.299213
## 13     Hakim          m    22   165        BA         rot          25 5.413386
## 14     Betty          w    21   165        BA       braun          71 5.413386
## 15    Ludwig          m    21   194        BA       braun         111 6.364829
## 16      Nael          m    21   192        BA       blond         102 6.299213
## 17    Sascha          d    21   177        BA       blond          41 5.807087
## 18      Xeno          m    21   183        MA         rot          86 6.003937
## 19      Mila          w    20   155       Abi         rot          32 5.085302
## 20       Ole          m    20   181        BA mittelblond          76 5.938320
## 21      Yuna          w    20   176        BA       blond          91 5.774278
## 22      Dani          d    19   175       Abi       blond          89 5.741470
## 23    Quamar          m    19   176       Abi       braun          59 5.774278
## 24      Gina          w    18   145       Abi       braun         143 4.757218
## 25     Ronja          w    18   167       Abi         rot         110 5.479003
## 26     Zaida          w    18   175       Abi mittelblond          21 5.741470
##    socialm_kat
## 1            1
## 2            2
## 3            2
## 4            2
## 5            2
## 6            2
## 7            2
## 8            2
## 9            2
## 10           2
## 11           2
## 12           2
## 13           1
## 14           2
## 15           2
## 16           2
## 17           2
## 18           2
## 19           2
## 20           2
## 21           2
## 22           2
## 23           2
## 24           2
## 25           2
## 26           1

9.6 Umordnen des Datensatzes

Bei der klassischen Datentabelle stellt jede Zeile einen eigenen Fall da, jede Spalte eine Variable. Beim Studis-Datensatz ist das der Fall. Je nach Art der Analyse kann es aber auch sein, dass ein anderes Format benötigt wird. In den folgenden Schritten werden Möglichkeiten gezeigt zur Reorganisation eines Datensatzes.

Zur Veranschaulichung der nächsten Schritte wird der Datensatz um zwei weitere Variablen erweitert. Stellen wir uns vor, die Studierenden sollten an zwei aufeinanderfolgenden Tagen notieren, wie viele Tassen Kaffee sie getrunken haben.
(Zur Vereinfachung werden im Folgenden zufällige Werte zwischen 0 und 6 zu jedem Fall zugeordnet mit der Funktion sample(). Die Argumente lesen sich wie folgt: Ziehe eine Zahl zwischen 0 und 6. Das wiederhole 26 Mal. Damit das auch klappt, lege die bereits gezogene Zahl wieder zurück, sonst reichen die Ziffern von 0 bis 6 nicht, um 26 Zahlen zu ziehen. Weil R diese Zufallsziehung bei jeder Ausführung neu wiederholen würde und damit immer neue Kombinationen von Zufallszahlen entstehen, was sich auf unten folgende Berechnungen auswirken würde, wird die Funktion set.seed() vorangestellt, die die Zufallszahlen festschreibt. Die Zahl als Argument in der set.seed Funktion kann willkürlich gesetzt werden.)

set.seed(1)
day1_coff <- sample(c(0:6), 26, replace = TRUE)
set.seed(2)
day2_coff <- sample(c(0:6), 26, replace = TRUE)
Studis <- cbind(Studis, day1_coff, day2_coff)
print(Studis[c(1:5),])
##     Name Geschlecht Alter Größe Abschluss   Haarfarbe Socialmedia     feet
## 1 Albert          m    23   173        BA     schwarz          95 5.675853
## 2  Betty          w    21   165        BA       braun          71 5.413386
## 3  Carlo          m    25   168        MA         rot         126 5.511811
## 4   Dani          d    19   175       Abi       blond          89 5.741470
## 5   Emma          w    24   155        MA mittelblond         101 5.085302
##   socialm_kat day1_coff day2_coff
## 1           2         0         4
## 2           2         3         6
## 3           2         6         5
## 4           2         0         5
## 5           2         1         0

Weil wir nur die Kaffee-Variablen brauchen, wird der Datensatz zur besseren Weiterverarbeitung verkleinert:

Studis_coff <- select(Studis, Name, day1_coff, day2_coff)
print(Studis_coff[c(1:5),])
##     Name day1_coff day2_coff
## 1 Albert         0         4
## 2  Betty         3         6
## 3  Carlo         6         5
## 4   Dani         0         5
## 5   Emma         1         0
Studis_coff_rearrange <- gather(Studis_coff, day1_coff:day2_coff, key = "day",
                                value = "cups")
print(Studis_coff_rearrange)
##         Name       day cups
## 1     Albert day1_coff    0
## 2      Betty day1_coff    3
## 3      Carlo day1_coff    6
## 4       Dani day1_coff    0
## 5       Emma day1_coff    1
## 6     Fritzi day1_coff    4
## 7       Gina day1_coff    6
## 8      Hakim day1_coff    2
## 9        Ivo day1_coff    5
## 10    Jasmin day1_coff    1
## 11     Kalle day1_coff    2
## 12    Ludwig day1_coff    2
## 13      Mila day1_coff    0
## 14      Nael day1_coff    4
## 15       Ole day1_coff    4
## 16     Pablo day1_coff    1
## 17    Quamar day1_coff    5
## 18     Ronja day1_coff    5
## 19    Sascha day1_coff    1
## 20     Tarek day1_coff    6
## 21   Ulfried day1_coff    0
## 22 Valentino day1_coff    6
## 23     Wanda day1_coff    4
## 24      Xeno day1_coff    4
## 25      Yuna day1_coff    0
## 26     Zaida day1_coff    0
## 27    Albert day2_coff    4
## 28     Betty day2_coff    6
## 29     Carlo day2_coff    5
## 30      Dani day2_coff    5
## 31      Emma day2_coff    0
## 32    Fritzi day2_coff    4
## 33      Gina day2_coff    0
## 34     Hakim day2_coff    3
## 35       Ivo day2_coff    4
## 36    Jasmin day2_coff    0
## 37     Kalle day2_coff    1
## 38    Ludwig day2_coff    2
## 39      Mila day2_coff    0
## 40      Nael day2_coff    2
## 41       Ole day2_coff    5
## 42     Pablo day2_coff    1
## 43    Quamar day2_coff    2
## 44     Ronja day2_coff    6
## 45    Sascha day2_coff    6
## 46     Tarek day2_coff    6
## 47   Ulfried day2_coff    0
## 48 Valentino day2_coff    5
## 49     Wanda day2_coff    0
## 50      Xeno day2_coff    3
## 51      Yuna day2_coff    2
## 52     Zaida day2_coff    5

Die Funktion pivot_longer() macht im Prinzip das gleiche, ordnet die Werte dann aber anders:

Studis_coff_rearrange2 <- pivot_longer(Studis_coff, day1_coff:day2_coff, 
                                       names_to = "day", values_to = "cups")
print(Studis_coff_rearrange2[c(1:6), ])
## # A tibble: 6 × 3
##   Name   day        cups
##   <chr>  <chr>     <int>
## 1 Albert day1_coff     0
## 2 Albert day2_coff     4
## 3 Betty  day1_coff     3
## 4 Betty  day2_coff     6
## 5 Carlo  day1_coff     6
## 6 Carlo  day2_coff     5

Diese Art der Darstellung ermöglicht es beispielsweise, die Tagesdurchschnitte einfacher zu vergleichen. Eine beliebte Variante, Vergleiche vorzunehmen, ist durch Kombination der Funktionen group_by() und summarize():

Studis_coff_rearrange <- group_by(Studis_coff_rearrange, day)
summarize(Studis_coff_rearrange, mean(cups))
## # A tibble: 2 × 2
##   day       `mean(cups)`
##   <chr>            <dbl>
## 1 day1_coff         2.77
## 2 day2_coff         2.96

Liegt der Datensatz in diesem langen Format vor, aber man braucht ihn im klassischen Format mit den Fällen pro Zeile, kann die Funktion pivot_wider() genutzt werden:

Studis_coff_rearrange_back <- pivot_wider(Studis_coff_rearrange, names_from = day, values_from = cups)
print(Studis_coff_rearrange_back[c(1:6), ])
## # A tibble: 6 × 3
##   Name   day1_coff day2_coff
##   <chr>      <int>     <int>
## 1 Albert         0         4
## 2 Betty          3         6
## 3 Carlo          6         5
## 4 Dani           0         5
## 5 Emma           1         0
## 6 Fritzi         4         4

Die Ausgangslage ist wieder hergestellt.
Eine weitere Funktion aus dem tidyverse-Package (um genauer zu sein: aus dem dplyr-Package, das Teil des übergeordneten tidyverse-Package ist), kann bei der Datenaufbereitung sehr hilfreich sein: die Funktion separate(df, into = ) kann eine Variable, die mehr als eine Information enthält, in mehrere Variablen auftrennen.

Zur besseren Veranschaulichung werden zwei weitere fiktive Variablen angelegt, nach dem gleichen Muster wie oben. Nehmen wir an, die Studierenden sollten für die beiden Tage nicht nur ihren Kaffeekonsum sondern auch ihren Schlaf protokollieren. Die beiden Variablen stellen also Schlaf in Stunden für den jeweiligen Tag da:

day1_sleep <- sample(c(2:12), 26, replace = TRUE)
day2_sleep <- sample(c(2:12), 26, replace = TRUE)
Studis <- cbind(Studis, day1_sleep, day2_sleep)
Studis_sleep <- select(Studis, Name, day1_coff:day2_sleep) 
Studis_sleep_long <- pivot_longer(Studis_sleep, cols = day1_coff:day2_sleep, names_to = "day", values_to = "value")
print(Studis_sleep_long[c(1:10),])
## # A tibble: 10 × 3
##    Name   day        value
##    <chr>  <chr>      <int>
##  1 Albert day1_coff      0
##  2 Albert day2_coff      4
##  3 Albert day1_sleep    10
##  4 Albert day2_sleep     3
##  5 Betty  day1_coff      3
##  6 Betty  day2_coff      6
##  7 Betty  day1_sleep     9
##  8 Betty  day2_sleep     7
##  9 Carlo  day1_coff      6
## 10 Carlo  day2_coff      5

In der Variable “day” stecken in dieser Darstellung nun zwei Informationen, nämlich zum Tag 1 oder 2 sowie zu Kaffee und Schlaf. Mit der Funktion separate, können diese Informationen in eigenen Variablen gespeichert werden:

Studis_sleep_long_sep <- separate(Studis_sleep_long, day, into = c("day", "condition"))
print(Studis_sleep_long_sep[c(1:10),])
## # A tibble: 10 × 4
##    Name   day   condition value
##    <chr>  <chr> <chr>     <int>
##  1 Albert day1  coff          0
##  2 Albert day2  coff          4
##  3 Albert day1  sleep        10
##  4 Albert day2  sleep         3
##  5 Betty  day1  coff          3
##  6 Betty  day2  coff          6
##  7 Betty  day1  sleep         9
##  8 Betty  day2  sleep         7
##  9 Carlo  day1  coff          6
## 10 Carlo  day2  coff          5

Das ist schon besser im Hinblick auf die Konvention, dass eine Variable nur eine Information enthalten sollte. Allerdings haben wir nun noch das Problem, dass die Werte in der Spalte “Value” gar nicht die selbe Maßeinheit haben. Kaffee wird in “Tassen” angegeben, Schlaf in “Stunden”. Darum sollen die Werte für Kaffee und Schlaf nun auch noch in eigene Variablen gespeichert werden. Im Beispiel wird dies mit der Funktion spread(df, key = , value =) gemacht, die das Gegenstück zur gather-Funktion von oben darstellt. Es könnte auch pivot_wider() genutzt werden.

Studis_sleep_long_sep_spread <- spread(Studis_sleep_long_sep, key = condition, value = value)
print(Studis_sleep_long_sep_spread[c(1:10),])
## # A tibble: 10 × 4
##    Name   day    coff sleep
##    <chr>  <chr> <int> <int>
##  1 Albert day1      0    10
##  2 Albert day2      4     3
##  3 Betty  day1      3     9
##  4 Betty  day2      6     7
##  5 Carlo  day1      6     7
##  6 Carlo  day2      5     5
##  7 Dani   day1      0     4
##  8 Dani   day2      5     5
##  9 Emma   day1      1    10
## 10 Emma   day2      0    10

Dahin zu kommen, war nun etwas umständlich (im folgenden Abschnitt wird ein Weg behandelt, die Schritte effizienter durchzuführen), zum Zwecke der Veranschaulichung der einzelnen Funktionen aber nötig. Mit dem Ergebnis lässt sich jetzt immerhin arbeiten. So könnte zum Beispiel der Zusammenhang (die Korrelation, siehe Abschnitt XY) zwischen Kaffeekonsum und Schlaf berechnet werden:

cor.test(Studis_sleep_long_sep_spread$coff, Studis_sleep_long_sep_spread$sleep)
## 
##  Pearson's product-moment correlation
## 
## data:  Studis_sleep_long_sep_spread$coff and Studis_sleep_long_sep_spread$sleep
## t = 0.96846, df = 50, p-value = 0.3375
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  -0.1424830  0.3940037
## sample estimates:
##       cor 
## 0.1356936

Auch können beide Variablen zum Beispiel in einem Streudiagramm angezeigt werden:

plot(Studis_sleep_long_sep_spread$coff, Studis_sleep_long_sep_spread$sleep)

9.7 Der Pipe-Operator

>%> - So sieht er aus, der Pipe-Operator aus dem tidyverse-package. Er dient dazu, Code effizienter und übersichtlicher zu gestalten, in dem Verschachtelungen eingespart werden, sparsamer mit der Anlage (dem Zwischespeichern) von Objekten umgegangen wird und insgesamt mehr Übersichtlichkeit durch das nacheinander-Abarbeiten von Schritten hergestellt wird.

Schauen wir uns ein einfaches Beispiel an, wie wir es bisher kennengelernt haben. Mit der Kombination der group_by() sowie der summarize()-Funktion können wir uns zum Beispiel Gruppenunterschiede hinsichtlich des Mittelwerts anschauen. So können wir uns das Durchschnittsalter für die Geschlechter unseres Studis-Datensatzes anschauen:

Studis.sex <- group_by(Studis, Geschlecht)
summarize(Studis.sex, age.avg = mean(Alter))
## # A tibble: 3 × 2
##   Geschlecht age.avg
##   <chr>        <dbl>
## 1 d             20  
## 2 m             26.3
## 3 w             22

Mit dem Pipe-Operator lässt sich der Code sparsamer abbilden, weil kein extra Objekt angelegt wird:

Studis %>%
  group_by(Geschlecht) %>% 
  summarize(age.avg = mean(Alter))
## # A tibble: 3 × 2
##   Geschlecht age.avg
##   <chr>        <dbl>
## 1 d             20  
## 2 m             26.3
## 3 w             22

In der ersten Code-Zeile wird dabei für alle folgenden Zeilen der Dataframe definiert, auf den sich die Berechnungen beziegen sollen. Es folgt der Pipe-Operator. Wird anschließend abgeentert, wird die nächste Zeile automatisch eingerückt. Das sorgt für Übersichtlichkeit. Für den folgenden Befehl muss den Dataframe nicht erneut nennen, sondern nur die Variable(n) und ggf. die auszuführenden Argumente. Mehrere Funktionen nacheinander durch den Pipe-Operator verbunden, werden wie ein Koch-Rezept Schritt für Schritt ausgeführt.

Erinnern wir uns an den etwas umständlichen Code aus dem vorherigen Abschnitt, um zum Schluss die Kaffee- und Schlafvariablen in eine Form zu bekommen, mit der wir beispielsweise eine Streudiagramm anfertigen können. Auf dem Weg zu diesem Ergebnis wurden mehrere Objekte als Zwischenschritte angelegt, die später gar nicht mehr gebraucht werden (nachdem sie für den folgenden Schritt verwendet wurden). Wie angekündigt, lässt sich auch dieser umständliche Code etwas eleganter und vor allem effizienter gestalten:

Studis_sleep %>%
  pivot_longer(cols = day1_coff:day2_sleep, names_to = "day", values_to = "value") %>%
  separate(day, into = c("day", "condition")) %>%
  spread(key = condition, value = value) %>%
  print()
## # A tibble: 52 × 4
##    Name   day    coff sleep
##    <chr>  <chr> <int> <int>
##  1 Albert day1      0    10
##  2 Albert day2      4     3
##  3 Betty  day1      3     9
##  4 Betty  day2      6     7
##  5 Carlo  day1      6     7
##  6 Carlo  day2      5     5
##  7 Dani   day1      0     4
##  8 Dani   day2      5     5
##  9 Emma   day1      1    10
## 10 Emma   day2      0    10
## # ℹ 42 more rows

Die Schritte, wie wir sie oben durchgeführt haben, werden hier nun nacheinander ausgeführt: pivot_longer, separate und spread. Aber ohne unnötig angelegte Zwischenobjekte und auch dadurch deutlich besser nachvollziehbar.

10 Mittelwertvergleiche

Oft interessieren in der Forschung Unterschiede zwischen Gruppen, zum Beispiel ob sich zwei Gruppen hinsichtlich ihres für eine Variable ermittelten Mittelwertes unterscheiden. Es werden dafür zwei Arten von Tests unterschieden: die parametrischen Tests sowie die nicht-parametrischen Test. Welche Art von Test zu wählen ist, hängt von der Erfüllung bestimmter Voraussetzungen ab, die im Vorhinein zu testen sind. Auch die Art der Gruppen für den Vergleich spielt eine wichtige Rolle, je nachdem ob es sich bei den Gruppen um abhängige oder unabhängige Stichproben handelt, sind unterschiedliche Tests zu wählen. Abhängig sind Stichproben zum Beispiel dann, wenn es sich um dieselbe Gruppe von Fällen handelt, die zu unterschiedlichen Zeitpunkten untersucht worden sind, beispielsweise Personen, die mehrfach befragt worden sind. Unabhängige Stichproben können zum Beispiel zwei Gruppen innerhalb einer Untersuchung sein, zum Beispiel Personen mit unterschiedlichem Geschlecht innerhalb einer Befragung.

10.1 Parametrische Tests

Für die parametrischen Tests müssen drei Voraussetzungen erfüllt sein, die im Vorfeld der eigentlichen Analyse überprüft werden müssen: (1) Die abhängige Variable ist metrisch skaliert. (2) Es liegt Normalverteilung (bei der abhängigen Variablen) vor. (3) Das Kriterium der Varianzhomogenität ist erfüllt.

10.1.1 Prüfung der Voraussetzungen

Ob eine Variable metrisch skaliert ist, sollte sich beim Blick auf die Ausprägungen der Werte erkennen lassen. Das lässt sich zum Beispiel mit der table() Funktion überprüfen. Die Prüfung der anderen beiden Voraussetzungen ist etwas aufwendiger.

10.1.1.1 Test auf Normalverteilung

Eine Möglichkeit, um auf Normalverteilung zu testen, ist die Durchführung des Shapiro-Wilk-Test. Dieser eignet sich auch für kleinere Stichproben (während beispielsweise der Chi-Quadrat-Test nur für größere Stichproben geeignet ist). Die Nullhypothese für den Shapiro-Wilk-Test lautet: Normalverteilung liegt vor. Für das Beispiel überprüfen wir die Normalverteilung der Variablen zur Social Media Nutzung. Es bietet sich in der Regel an, auch ein Histogramm zu erstellen, um einen visuellen Eindruck von der Verteilung zu bekommen. Dazu wird im folgenden das Histogramm für die Socialmedianutzung mit der Verteilungskurve dargestellt.
(Da der Beispieldatensatz recht klein ist, ist die Visualisierung nur bedingt aussagekräftig. Nichtsdestotrotz soll sie hier noch mal gezeigt werden, insbesondere um die Funktion vorzuführen, mit der die Normalverteilungskurve über das Histogramm gelegt werden kann.
Zur besseren Lesbarkeit wird die Variable im ersten Schritt als x festgelegt. Die Sequenz für die Darstellung der X-Achse als x2 definiert. Beide Werte werden dann für die Funktion dnorm() benötigt, die mithilfe von Mittelwert und Standardabweichung die Verteilungskurve definiert. Für das Histogramm muss dann das Argument “prob = TRUE” gesetzt werden. Das Einzeichnen der Kurve mit den vorher festgesetzen Parametern findet im letzten Schritt mit der Funktion lines() statt.)

x = Studis$Socialmedia
x2 <- seq(min(x), max(x), length = 10)
fun <- dnorm(x2, mean = mean(x), sd = sd(x))

hist(x, prob = TRUE,
     main = "Histogramm Social Media Nutzung", 
     xlab = "Social Media Nutzung in Minuten", ylab = "Density",
     col = "white", 
     breaks = 15, xaxt='n')
lines(x2, fun, col = 2, lwd = 2) 

Nun wird der Shapiro-Wilk-Test durchgeführt. Die Variable x ist im Schritt oben ja bereits für die Socialmedianutzung definiert worden, daher kann hier damit weiter gearbeitet werden:

shapiro.test(x) 
## 
##  Shapiro-Wilk normality test
## 
## data:  x
## W = 0.95518, p-value = 0.3056

Das Ergebnis gibt einen p-Wert von 0.3056 aus. Es liegt also ein nicht-signifikantes Ergebnis vor. Wir erinnern uns: Die Nullhypothese des Shapiro-Wilk-Test lautet, dass Normalverteilung vorliegt. Bei einem nicht-signifikanten p-Wert kann die Nullhypothese nicht verworfen werden. Entsprechend kann hier Normalverteilung angenommen werden. Die Voraussetzung ist also erfüllt.

Zum Vergleich soll noch das Alter betrachtet werden:

shapiro.test(Studis$Alter) 
## 
##  Shapiro-Wilk normality test
## 
## data:  Studis$Alter
## W = 0.51942, p-value = 3.934e-08

Der Wert ist sehr klein, entsprechend muss die Nullhypothese verworfen werden und es kann keine Normalverteilung angenommen werden. Das ergibt auch Sinn, da die Gruppe von Studierenden bis auf einen Ausreißer eher homogen ist in Bezug auf das Alter.

10.1.1.2 Test auf Varianzhomogenität

Bei der Varianz handelt es sich um die quadrierte Standardabweichung. Beide Werte sind wichtige Maße für die Streuung der Daten, also der durchschnittlichen Abweichung der Datenpunkte vom Mittelwert. Wenn wir von Varianzhomogenität sprechen, dann soll also die Streuung der Daten möglichst ähnlich sein. Dabei ist erstmal nicht wichtig, ob die Streuung groß oder klein sein, sie muss nur ähnlich sein für die Gruppen, die verglichen werden sollen.

Für das Beispiel soll die Socialmedianutzung zwischen männlichen und weiblichen Studierenden im Datensatz verglichen werden. Weil wir noch ein drittes Geschlecht im Datensatz haben, muss zuerst ein Filter gesetzt werden, um ein Subset anzulegen:

Studis.2Geschl <- filter(Studis, Geschlecht != "d")

Anschließend können wir mit der describeBy()-Funktion aus dem “psych”-Package die deskriptiven Statistiken betrachten und mit Blick auf die Standardabweichungen schon einen ersten Eindruck bekommen, wie ähnlich diese bei den beiden Gruppen sind:

library(psych)
describeBy(Studis.2Geschl$Socialmedia, Studis.2Geschl$Geschlecht)
## 
##  Descriptive statistics by group 
## group: m
##    vars  n mean    sd median trimmed   mad min max range  skew kurtosis   se
## X1    1 14   73 35.33   83.5   74.67 31.88   0 126   126 -0.53    -0.82 9.44
## ------------------------------------------------------------ 
## group: w
##    vars  n mean    sd median trimmed   mad min max range  skew kurtosis    se
## X1    1 10 90.4 38.45    104    92.5 20.76  21 143   122 -0.62       -1 12.16

Mit 35.33 und 38.45 sind die Standardabweichungen der Gruppen nicht allzuweit auseinander. Das gibt einen ersten Hinweis auf Varianzhomogenität. Diese wird nun noch mit dem Levene-Test konkret überprüft. Dazu nutzen wir die Funktion leveneTest() aus dem “car”-Package. Die Nullhypthese des Levene-Test ist die Annahme der Varianzhomogenität.

leveneTest(Studis.2Geschl$Socialmedia, Studis.2Geschl$Geschlecht, center = mean)
## Levene's Test for Homogeneity of Variance (center = mean)
##       Df F value Pr(>F)
## group  1  0.0221 0.8832
##       22

Der p-Wert des Tests ist nicht-signifikant mit 0.8832, entsprechend kann die Nullhypothese nicht verworfen werden und es kann Varianzhomogenität angenommen werden.

10.1.2 t-Test für unabhängige Stichproben (2 Gruppen)

Sind die Voraussetzungen erfüllt, kann für unabhängige Stichproben anschließend ein t-Test durchgeführt werden mit der Funktion t.test(). Die Nullhypothese des t-Tests lautet: Die Gruppen unterscheiden sich nicht (bzw. es gibt keinen von Null verschiedenen Unterschied). Das Beispiel zeigt den t-Test für die Socialmedianutzung bei den männlichen und weiblichen Studierenden. (Zu beachten ist die Verbindung der beiden Variablen in der Funktion durch das Tilde-Zeichen “~”)

t.test(Studis.2Geschl$Socialmedia ~ Studis.2Geschl$Geschlecht)
## 
##  Welch Two Sample t-test
## 
## data:  Studis.2Geschl$Socialmedia by Studis.2Geschl$Geschlecht
## t = -1.1302, df = 18.476, p-value = 0.2728
## alternative hypothesis: true difference in means between group m and group w is not equal to 0
## 95 percent confidence interval:
##  -49.68388  14.88388
## sample estimates:
## mean in group m mean in group w 
##            73.0            90.4

Der Output gibt die Mittelwerte für beide Gruppen an, sowie die t-Statistik, die Freiheitsgrade und den p-Wert. Die Unterschiede im Mittelwert scheinen auf den ersten Blick recht groß, der p-Wert ist jedoch nicht signifikant, entsprechend kann kein signifikanter Unterschied bei den Gruppen angenommen werden. Das heißt, wäre unser Datensatz eine Stichprobe aus einer größeren Gruppe von Studierenden könnten wir anhand dieser Stichprobe nicht schließen, dass es in der Grundgesamtheit einen von Null verschiedenen Unterschied zwischen den beiden Gruppen bei ihrer Socialmedianutzungsdauer gibt.

10.1.3 t-Test für abhängige Stichproben

Sollen beispielsweise die Werte einer Gruppe zu unterschiedlichen Messzeitpunten auf Veränderung geprüft werden, kann - bei gegebenen Voraussetzungen - der t-Test für unabhängige Stichproben(oder auch: verbundene Stichproben) verwenden werden. Im Beispiel soll überprüft werden, ob sich der Kaffeekonsum von Tag 1 und Tag 2 bei unseren Beispielstudierenden unterscheidet. Die Voraussetzungen sind gegeben, sodass der Test wie folgt durchgeführt werden kann. Die zu nutzende Funktion ist ebenfalls t.test() mit dem Unterschied, dass nun das Argument “paired = TRUE” eingefügt wird, um deutlich zu machen, dass es sich um verbundene Stichproben handelt:

t.test(Studis_coff$day1_coff, Studis_coff$day2_coff, paired = TRUE)
## 
##  Paired t-test
## 
## data:  Studis_coff$day1_coff and Studis_coff$day2_coff
## t = -0.36745, df = 25, p-value = 0.7164
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
##  -1.2701871  0.8855717
## sample estimates:
## mean difference 
##      -0.1923077

Die Nullhypothese lautet, dass es keinen Unterschied (bzw. keinen von Null verschiedenen Unterschied) zwischen den Gruppen gibt. Der oben angegebene p-Wert übersteigt die Grenze von 0.05, sodass die Nullhypothese nicht verworfen werden kann. Es liegt kein signifikanter Unterschied zwischen dem Kaffeekonsum an Tag 1 und Tag 2 vor.

10.1.4 Einfaktorielle Varianzanalyse (ANOVA) für unabhängige Stichproben

Sollen mehr als zwei Gruppen verglichen werden, kann eine einfaktorielle Varianzanalyse durchgeführt werden, wenn die entsprechenden Voraussetzungen erfüllt sind. Für das folgende Beispiel soll die Socialmedianutzung verglichen werden für die drei unterschiedliche Gruppen nach Bildungsabschluss (Abi, BA und MA). Der Beispieldatensatz ist sehr klein, weshalb alle folgenden Schritte rein der Illustration der Durchführung dienen sollen und die tatsächlichen Werte weniger relevant sind. Zur Prüfung auf Normalverteilung bei mehr als zwei Gruppen bietet es sich an eine Funktion aus der Familie der apply-Funktionen zu wählen. tapply() führt eine (in den Argumenten definierte) Funktion für alle angegebenen Gruppen aus. In diesem Fall wird also der Shapiro-Wilk-Test für die Socialmedianutzung der drei zu vergleichenden Gruppen ausgeführt.

tapply(Studis$Socialmedia, Studis$Abschluss, FUN = shapiro.test)
## $Abi
## 
##  Shapiro-Wilk normality test
## 
## data:  X[[i]]
## W = 0.96458, p-value = 0.8569
## 
## 
## $BA
## 
##  Shapiro-Wilk normality test
## 
## data:  X[[i]]
## W = 0.88324, p-value = 0.1144
## 
## 
## $MA
## 
##  Shapiro-Wilk normality test
## 
## data:  X[[i]]
## W = 0.9545, p-value = 0.7564

Für alle drei Gruppen ergeben sich p-Werte, die größer sind als 0.05, also kann die Nullhypothese (H0 = Normalverteilung liegt vor) nicht verworfen werden.

Der Levene-Test auf Varianzhomogenität kann auch für drei Gruppen durchgeführt werden:

leveneTest(Studis$Socialmedia, Studis$Abschluss, center = "mean")
## Warning in leveneTest.default(Studis$Socialmedia, Studis$Abschluss, center =
## "mean"): Studis$Abschluss coerced to factor.
## Levene's Test for Homogeneity of Variance (center = "mean")
##       Df F value  Pr(>F)  
## group  2  3.4123 0.05038 .
##       23                  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Der p-Wert ist in diesem Beispiel 0.05, also gerade an der üblichen Grenze, aber für unser Beispiel akzeptabel, sodass als nächstes das Modell für die ANOVA spezifiert werden kann. Das heißt, dass mit der Funktion aov() die Berechnung durchgeführt wird und das Ergebnis in ein Objekt gespeichert wird. Anschließend wird das Ergebnis mit der summary() Funktion aufgerufen.

model  <- aov(Studis$Socialmedia ~ Studis$Abschluss)
summary(model)
##                  Df Sum Sq Mean Sq F value Pr(>F)
## Studis$Abschluss  2   3478    1739    1.36  0.276
## Residuals        23  29402    1278

Der Blick auf den p-Wert zeigt hier kein signifikanten Unterschied. Wäre der p-Wert kleiner als 0.05, müsste ein post-hoc Test durchgeführt werden mit der Funktion pairwise.t.test(), um zu überprüfen, zwischen welchen Gruppen der signifikante Unterschied konret besteht. Denn es kann sein, dass sich nur bestimmte Gruppen voneinader unterscheiden, aber nicht alle. Zur Illustration wird der post-hoc Test hier trotzdem gezeigt. Mit dem Argument “p.adjust.method =” kann die Methode für den Vergleich gewählt werden. Es gibt unterschiedliche Möglichkeiten, die je nach Daten abgewogen werden sollte (Kriterien sind Gruppengrößen und Varianzheterogenität). Für das Beispiel wird die Methode nach Bonferroni gewählt, die als sehr konservativ, d.h. sehr streng bei der Erreichung von Signifikanzwerten, gilt.

pairwise.t.test(Studis$Socialmedia, Studis$Abschluss, p.adjust.method = "bonferroni")
## 
##  Pairwise comparisons using t tests with pooled SD 
## 
## data:  Studis$Socialmedia and Studis$Abschluss 
## 
##    Abi  BA  
## BA 1.00 -   
## MA 0.36 0.85
## 
## P value adjustment method: bonferroni

Für jeden Kombination der Gruppen wird hier ein eigener p-Wert angegeben. Dabei kann es - wie gesagt - sein, dass es zwischen manchen Gruppen einen signifikanten Unterschied gibt, zwischen anderen aber nicht.

10.2 Nicht-parametrische Tests

Wenn die Voraussetzungen für die Annahme der Normalverteilung und Varianzhomogenität nicht erfüllt sind, können die nicht-parametrischen Tests als Alternative genutzt werden. Für zwei verbundene Stichproben würde man dann den Wilcoxon-Test nehmen mit der Funktion wilcox.test(var1, var2, paired = TRUE). Für zwei unverbundene Stichproben dient der Mann-Whitney-U-Test. Die Funktion sieht ähnlich aus mit wilcox.test(var1~var2), wobei “var2” die Gruppierungsvariable darstellt.

11 Korrelationen

Bei der Betrachtung von Korrelationen geht es um Zusammenhänge zwischen zwei Variablen. Im Studis-Datensatz kann man sich beispielsweise anschauen, ob einen Zusammenhang zwischen Alter und der Dauer der Socialmedianutzung gibt, zum Beispiel die Hypothese prüfend, ob mit zunehmendem Alter mehr oder weniger Socialmedianutzung erfolgt. Zur Exploration von Zusammenhängen bietet es sich in der Regel an, sich ein Streudiagramm der beiden Variablen anzuschauen:

plot(Studis$Alter, Studis$Socialmedia, xlab = "Alter", ylab = "Social Media Nutzung in Minuten", 
     main = "Social Media Nutzung und Alter")

Wir sehen eine Punktwolke auf der linken Seite sowie einen Ausreißer einer Person, die offenbar sehr viel älter ist als der Rest und sehr viel weniger Socialmedianutzung hat. Diese grafische Erkenntnis sollte für die folgenden Schritte schon zur Vorsicht raten.

Eine einfache Korrelation lässt sich mit der Funktion cor() errechnen. Die Default-Einstellung zur Methode ist hier: Korrelation nach Pearson für metrisch skallierte Daten.

cor(Studis$Alter, Studis$Socialmedia)
## [1] -0.3486723

Der Pearson-Koeffizient reicht von 1 (perfekter positiver Zusammenhang) bis -1 (perfekter negativer Zusammenhang). Das Ergebnis von -0.349 weist also auf einen moderaten negativen Zusammenhang: je älter desto weniger Socialmedianutzung.

Für die inferenzstatistische Prüfung des Zusammenhangs wird die Funktion cor.test() genutzt.

cor.test(Studis$Alter, Studis$Socialmedia)
## 
##  Pearson's product-moment correlation
## 
## data:  Studis$Alter and Studis$Socialmedia
## t = -1.8225, df = 24, p-value = 0.08086
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  -0.64844585  0.04471941
## sample estimates:
##        cor 
## -0.3486723

Zusätzlich zum Koeffizienten wird uns hier nun auch noch die t-Statistik, die Freiheitsgrade sowie der p-Wert ausgegeben. Der p-Wert liegt in diesem Fall mit 0.08 knapp über der Grenze des Signifikanzniveaus von 0.05.

Nun hatte die grafische Darstellung aber einen klaren Ausreißer angezeigt, von dem wir ausgehen müssen, dass er bei der kleinen Stichprobengröße einen großen Einfluss aufs Ergebnis hat. Für ein solches Beispiel ergibt es durchaus Sinn, den einzelnen starken Ausreißer auszuschließen.

young.studis <- filter(Studis, Alter < 60)
plot(young.studis$Alter, young.studis$Socialmedia)

cor(young.studis$Alter, young.studis$Socialmedia)
## [1] 0.2079759
cor.test(young.studis$Alter, young.studis$Socialmedia)
## 
##  Pearson's product-moment correlation
## 
## data:  young.studis$Alter and young.studis$Socialmedia
## t = 1.0197, df = 23, p-value = 0.3185
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  -0.2039121  0.5573084
## sample estimates:
##       cor 
## 0.2079759

Die Korrelation ist nun leicht positiv, der p-Wert allerdings ist deutlich größer (bei dieser kleinen Stichprobe aber auch nicht verwunderlich).

Sind die zu untersuchenden Variablen nicht metrisch, muss die Methode zur Berechnung eines Korrelationskoeffizienten angepasst werden. Die Korrelationskoeffizienten Kendall-Tau und Spearman Rho untersuchen, ob es einen ungerichteten Zusammenhang zwischen zwei ordinalen oder einer ordinalen und einer metrischen Variablen gibt. Sie zeigen entweder einen positiven Zusammenhang, einen negativen Zusammenhang oder gar keinen Zusammenhang. In der Nullhypothese gehen sie von keinem Zusammenhang aus.
Für das Beispiel soll der Zusammenhang von Socialmedianutzung und bisherigem Abschluss betrachtet werden. Das geht für den Studis-Datensatz aber nicht ohne weiteres, was an der Anlage der Variablen liegt.

class(Studis$Abschluss)
## [1] "character"

Die Abschlussvariable ist nämlich als “character”-Variable hinterlegt und muss in eine ordinale Variable umkodiert werden:

Studis$Abschluss.ord <- factor(Studis$Abschluss, order = TRUE,
                           levels = c("Abi", "BA", "MA"))
class(Studis$Abschluss.ord) 
## [1] "ordered" "factor"
table(Studis$Abschluss, Studis$Abschluss.ord) 
##      
##       Abi BA MA
##   Abi   7  0  0
##   BA    0 11  0
##   MA    0  0  8

Das Ergebnis der Funktion class() zeigt nun “ordered”/ “factor” an und mithilfe der Kreuztabelle kann kontrolliert werden, ob die Werte korrekt umkodiert worden sind. In der cor-Funktion muss dann die Methode spezifiziert werden. Außerdem muss die unabhängige Variable, die eben in eine ordinale Variable umgewandelt worden ist, noch als nummerisch klassifiziert werden, damit R die Rangfolge der Elemente erstellen kann, die für diesen Test nötig ist.

cor(Studis$Socialmedia, as.numeric(Studis$Abschluss.ord), method = "kendall")
## [1] 0.2357996

Auch bei der Funktion cor.test() muss die Methode explizit spezifiziert werden:

cor.test(Studis$Socialmedia, as.numeric(Studis$Abschluss.ord), method = "kendall")
## Warning in cor.test.default(Studis$Socialmedia,
## as.numeric(Studis$Abschluss.ord), : Cannot compute exact p-value with ties
## 
##  Kendall's rank correlation tau
## 
## data:  Studis$Socialmedia and as.numeric(Studis$Abschluss.ord)
## z = 1.4923, p-value = 0.1356
## alternative hypothesis: true tau is not equal to 0
## sample estimates:
##       tau 
## 0.2357996

Für den Zusammenhang von nominalen Variablen kann ein Chi-Quadrat-Test gemacht werden, der auf den theoretisch-erwarteten und tatsächlichen Verteilungen in einer Kreuztabelle basiert. Dazu sind allerdings mindestens 5 Beobachtungen pro Zelle nötig, was bei dem kleinen Studis-Datensatz zum Beispiel nicht erreicht werden kann für die dahin enthaltenen Variablen. Die Funktion für den Chi-Quadrat-Test lautet chisq.test(var1, var2).

12 Lineare Regression

Die lineare Regression kommt zur Anwendung, wenn nicht nur ein Interesse an dem Zusammenhang zwischen Merkmalen besteht, sondern es darüber hinaus auch das Ziel ist, aus dem Wissen über den Zusammenhang Vorhersagen machen zu können. Dazu wird die gerichtete Beziehung zwischen zwei Merkmalen betrachtet: der unabhängigen Variablen x und der abhängigen (metrischen) Variablen y. Ziel ist die Verbesserung der Vorhersagequalität von y, wenn die Informationen von x herangezogen werden.

12.1 Pokemon-Beispiel

pok <- read.csv("pokRdex_comma.csv")

Wir schauen uns die Variablen attack und defense an. Wir nehmen an, dass Pokemon stärker im Angriff sind, wenn sie auch stark in der Verteidigung sind. Die Punktwolke im Streudiagramm verläuft von links unten nach rechts oben, was einen ersten Hinweis auf einen positiven Zusammenhang zwischen beiden Variablen liefert.

plot(pok$defense, pok$attack)

Nun wird eine Gerade gesucht, die die Punkte möglichst gut repräsentiert, d.h. den kleinst-möglichen Abstand zu allen Punkten hat.

Die Regressiongerade lautet y = a + b*x

  • y ist die abhängige Variable
  • y ist die unabhängige Variable
  • a ist die Konstante (Schnittpunkt mit der y-Achse, wenn x = 0)
  • b ist der Regressionskoeffizient (Steigung der Gerade, Erhöhung für y wenn x um 1 steigt)

Je stärker ein Zusammenhang zwischen der unabhängigen und abhängigen Variable, desto näher liegen die Datenpunkte an der Geraden. De Restabstand zwischen den Datenpunkten und der Geraden wird als Residuen bezeichnet. Zur Ermittlung des kleinstmöglichen Abstandes wird die Methode des kleines Quadrate (ordinary least square) verwendet.

Um die Güte einer Regression einschätzen zu können, wird der Determinationskoeffizient R-Quadrat betrachtet. Dieser beschreibt den Anteilswert, der die erklärte Varianz in Prozent angibt. Damit gibt er für das Gesamtmodell die Stärke des statistischen Zusammenhangs an: Je näher die Datenpunkte an der Geraden liegen, desto größer ist R-Quadrat.

Das Lineare Model wird mit der Funktion lm() berechnet:

model <- lm(pok$attack~pok$defense)
summary(model)
## 
## Call:
## lm(formula = pok$attack ~ pok$defense)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -140.57  -18.89   -4.45   15.96  125.78 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 45.03996    2.61638   17.21   <2e-16 ***
## pok$defense  0.45881    0.03276   14.00   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 29.01 on 809 degrees of freedom
## Multiple R-squared:  0.1951, Adjusted R-squared:  0.1941 
## F-statistic: 196.1 on 1 and 809 DF,  p-value: < 2.2e-16

In diesem Beispiel beträgt der Wert für die Konstante (Intercept) rund 45.04. Der Regressionkoeffizient b beträgt 0.459 und sagt aus, dass die Angriffskraft (y = attack) um 0.459 Punkte steigt, wenn die Verteidigungskraft (x = defense) um einen Punkt steigt. R-Quadrat als Maßzahl für die Güte des Modells beträgt 0.1941, also 19.41 Prozent der Varianz wird mithilfe des Modells erklärt.

Plot mit Regressionsgerade

plot(pok$attack, pok$defense)
abline(model, col = "red")

12.2 Starwars-Beispiel

In einem weiteren Bespiel soll der Zusammenhang von Größe und Gewicht bei den Starwars-Figuren ermittelt werden. Klassischerweise wird die Hypothese zugrunde gelegt, dass größere Figuren auch schwerer sind (und das die Größe Einfluss auf das Gewicht hat und nicht umgekehrt).

(Der Einfachheit halber wird aber Jabba the Hutt ausgeschlossen, der beim Gewicht einen starken Extremwert darstellt.)

starwars <- read.csv("starwars.csv")
starwars <- filter(starwars, mass < 1000)

Streudiagramm für Größe und Gewicht:

plot(starwars$height, starwars$mass)

Lineares Model

model_sw <- lm(starwars$mass~starwars$height)
summary(model_sw)
## 
## Call:
## lm(formula = starwars$mass ~ starwars$height)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -39.382  -8.212   0.211   3.846  57.327 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)     -32.54076   12.56053  -2.591   0.0122 *  
## starwars$height   0.62136    0.07073   8.785 4.02e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 19.14 on 56 degrees of freedom
## Multiple R-squared:  0.5795, Adjusted R-squared:  0.572 
## F-statistic: 77.18 on 1 and 56 DF,  p-value: 4.018e-12

Aus dem Output kann entnommen werden, dass das Gewicht um 0.621 kg steigt, wenn die Größe um eine Einheit steigt. R-Quadrat beträgt 57,2 Prozent. Die Größe hat also eine hohe Erklärkraft für das Gewicht bei den Starwars-Figuren.

12.3 Multiple lineare Regression

Soll der gerichteter Einfluss mehrerer unabhängiger Variablen auf eine abhängige (metrische) Variable betrachtet werden, wird die multiple Regression herangezogen. Eine wichtige Grundvoraussetzung dafür ist: Es gibt eine theoretisch begründete Auswahl von Variablen und angenommene Zusammenhänge (Modell). Ziel ist es weiterhin, eine Gerade zu finden, die den kleinst-möglichen Abstand zu allen Datenpunkten hat. Die lineare Regressionsgerade der multiplen Regression lautet:

y = a + b1x1 + b2x2 + … + bnxn

Die Regressionskoeffizienten der einzelnen Variablen (b1 bis bn) sind partielle Regressionskoeffizienten, die unter rechnerischer Kontrolle der anderen UVs zustande kommen.

Um weitere Variablen in das Modell aufzunehmen, werden diese im Code der lm() Funktion durch das + Zeichen angefügt. Im folgenden Beispiel soll das Geschlecht als weitere Variable aufgenommen werden. Dazu muss diese als character Variable angelegte Variable in eine nummerische dichotome Variable umgewandelt werden.

12.3.1 Nominale Variablen

Es ist möglich, nominale Variablen in ein lineares Regressionsmodell mit metrischer AV aufzunehmen. Dazu müssen alle Ausprägungen der nominalen Variablen in dichotome Variablen (z.B. 0 = kommt nicht vor; 1 = kommt vor) umgewandelt werden. Es werden dann alle dichotomen Variablen bis auf eine der ursprünglichen Ausprägungen in die Berechnung einbezogen. Die Ausprägung, die nicht in das Modell übernommen wurde, dient bei der Interpretation als Referenz. Alle anderen Ausprägungen der nominalen Variable werden nicht mit ihrem direkten Einflüss auf die AV ausgegeben, sondern sind in Relation zur Referenzkategorie zu interpretiere. Betrachten wir das Beispiel Geschlecht als weitere Variable in einer multiplen Regression.

starwars <- read.csv("starwars.csv")

Die Variable “gender” aus dem Starwars-Datensatz wird in einem ersten Schritt dichotomisiert, sodass “masculine” zur Referenzkategorie wird.

starwars$Geschlecht <- car::recode(starwars$gender, "'masculine' = 0; 'feminine' = 1")

Wir nehmen an, dass für die Vorhersage des Gewicht der Figuren sowohl ihre Größe als auch ihr Geschlecht relevant sind.

model2 <- lm(starwars$mass~starwars$height + starwars$Geschlecht)
summary(model2)
## 
## Call:
## lm(formula = starwars$mass ~ starwars$height + starwars$Geschlecht)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
##  -69.33  -32.18  -27.23   -6.64 1251.73 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)
## (Intercept)          -1.2033   113.5663  -0.011    0.992
## starwars$height       0.6141     0.6344   0.968    0.337
## starwars$Geschlecht -48.1032    62.2480  -0.773    0.443
## 
## Residual standard error: 171.4 on 55 degrees of freedom
##   (29 observations deleted due to missingness)
## Multiple R-squared:  0.02866,    Adjusted R-squared:  -0.006666 
## F-statistic: 0.8113 on 2 and 55 DF,  p-value: 0.4495

Die Regressionskoeffizienten in diesem Output lassen sich nun wie folgt interpetieren: Wie oben gilt, dass wenn sich die Größe um eine Einheit ändert, sich das Gewicht um 0.61 Einheiten ändert. Die Einheit beim Geschlecht kann sich aber nicht ändern, sodass wir den Koeffizienten bei der Variable Geschlecht wie folgt interpretieren müssen: Im Vergleich zur Referenzkategorie “masculine” sind die Frauen um 22.05 kg leichter.

12.3.2 Standardisierte Regressionskoeffizienten

Die hier angegebenen Regressionskoeffizienten sind nicht-standardisierte Regressionskoeffizienten und damit NICHT dimensionslos. Die Einheit der Variable ist ausschlaggebend für die Skalierung der Variable. Es machte also z.B. einen Unterschied, ob die Größe hier in mm, cm, oder m angegeben wäre. Damit sind die Regressionskoeffizienten zwar gut interpretierbar, allerdings untereinander nicht vergleichbar. Die Lösung dieses Problems besteht in der Angabe standardisierter Regressionskoeffizient (= nicht-standardisierter Regressionskoeffizient * (SD UV/ SD AV)) ODER z-Standardisierung der Variablen im Modell. Damit geht zwar die Interpretierbarkeit verloren, allerdings zugunsten der Vergleichbarkeit. Bei der Darstellung der Ergebnisse einer Regression sollten daher immer sowohl die standardsierten als auch nicht-standardisierten Regressionskoeffizienten angebenen werden. Bei R erreichen wir die Ausgabe der standardisierten Regressionskoeffizienten durch z-Standardisierung der Variablen im Modell mit der Funktion scale():

model3 <- lm(scale(starwars$mass)~scale(starwars$height) + scale(starwars$Geschlecht))
summary(model3)
## 
## Call:
## lm(formula = scale(starwars$mass) ~ scale(starwars$height) + 
##     scale(starwars$Geschlecht))
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -0.4091 -0.1899 -0.1607 -0.0392  7.3867 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)
## (Intercept)                -0.007591   0.134062  -0.057    0.955
## scale(starwars$height)      0.126015   0.130177   0.968    0.337
## scale(starwars$Geschlecht) -0.115256   0.149147  -0.773    0.443
## 
## Residual standard error: 1.011 on 55 degrees of freedom
##   (29 observations deleted due to missingness)
## Multiple R-squared:  0.02866,    Adjusted R-squared:  -0.006666 
## F-statistic: 0.8113 on 2 and 55 DF,  p-value: 0.4495

Der Wertebereich der standardisierten Regressionskoeffizienten liegt zwischen -1 und 1. Je näher der Wert an - 1 bzw. 1 herankommt, desto stärker ist der Effekt der unabhänigen Variable auf die abhängige Variable. Damit lässt sich nun entscheiden, welche Variablen um Modell den größeren Einfluss auf die abhängige Variable hat. Im Beispiel ist der Betrag des Koeffizienten der Größe mit rund 0.75 deutlich größer als der Betrag des Wertes der Variable Geschlecht mit 0.28 (Vorzeichen spielt bei der Interpretation der Effektstärke keine Rolle sondern ist nur wichtig für die Richtung des Effekts.).

13 Faktorenanalyse

Für die Illustration der Faktorenanalyse wird der Worlds of Journalism Germany Datensatz verwerdet.

library(haven)

WJS <- read_sav("WJS_Germany.sav")

Darin gibt es eine Itembatterie zum Thema journalistisches Rollenverständnis. Im ersten Schritt wird aus genau dieser Itembatterie ein Subset gebildet:

C12 <- subset(WJS, select = c(C12A:C12Z))
describe(C12)
##      vars   n mean   sd median trimmed  mad min max range  skew kurtosis   se
## C12A    1 771 4.27 0.95      5    4.44 0.00   1   5     4 -1.36     1.41 0.03
## C12B    2 765 4.59 0.73      5    4.76 0.00   1   5     4 -1.98     4.02 0.03
## C12C    3 768 4.31 0.96      5    4.49 0.00   1   5     4 -1.49     1.83 0.03
## C12D    4 746 2.79 1.46      3    2.74 1.48   1   5     4  0.11    -1.37 0.05
## C12E    5 748 2.80 1.39      3    2.76 1.48   1   5     4  0.10    -1.25 0.05
## C12F    6 744 2.10 1.06      2    1.97 1.48   1   5     4  0.58    -0.56 0.04
## C12G    7 752 2.68 1.12      3    2.67 1.48   1   5     4  0.02    -0.75 0.04
## C12H    8 745 2.77 1.21      3    2.75 1.48   1   5     4  0.02    -0.97 0.04
## C12J    9 739 2.27 1.30      2    2.13 1.48   1   5     4  0.61    -0.83 0.05
## C12K   10 738 2.14 1.14      2    2.02 1.48   1   5     4  0.60    -0.66 0.04
## C12L   11 741 1.25 0.57      1    1.11 0.00   1   5     4  2.55     6.95 0.02
## C12M   12 741 1.27 0.58      1    1.14 0.00   1   5     4  2.26     5.23 0.02
## C12O   13 768 3.51 1.09      4    3.58 1.48   1   5     4 -0.42    -0.35 0.04
## C12P   14 766 4.00 0.95      4    4.11 1.48   1   5     4 -0.79     0.14 0.03
## C12R   15 767 3.82 1.06      4    3.94 1.48   1   5     4 -0.70    -0.13 0.04
## C12S   16 760 3.36 1.45      4    3.45 1.48   1   5     4 -0.48    -1.14 0.05
## C12T   17 761 3.10 1.42      3    3.13 1.48   1   5     4 -0.19    -1.26 0.05
## C12U   18 764 3.27 1.24      3    3.34 1.48   1   5     4 -0.34    -0.81 0.04
## C12W   19 762 3.60 1.10      4    3.68 1.48   1   5     4 -0.51    -0.40 0.04
## C12X   20 759 3.56 1.18      4    3.66 1.48   1   5     4 -0.54    -0.56 0.04
## C12Z   21 765 3.80 1.21      4    3.96 1.48   1   5     4 -0.87    -0.13 0.04

Es werden alle Fälle mit fehlenden Werten entfernt.

C12 <- na.omit(C12)

Zunächst muss ein weiteres Package installiert und aufgerufen werden, mit dem die Anzahl der Faktoren bestimmt werden kann:

#install.packages("nFactors")
library(nFactors)

Die olgende Analyse gibt mehrere Vorschläge nach unterschiedlichen Methoden:
- MAP ( minimum average partial: Analyse der gemeinsamen Varianz in der Korrelationsmatrix) - BIC (Bayesian information criterion)

nfactors(C12, rotate = "verimax", fm = "lme")

## 
## Number of factors
## Call: vss(x = x, n = n, rotate = rotate, diagonal = diagonal, fm = fm, 
##     n.obs = n.obs, plot = FALSE, title = title, use = use, cor = cor)
## VSS complexity 1 achieves a maximimum of 0.72  with  1  factors
## VSS complexity 2 achieves a maximimum of 0.82  with  15  factors
## The Velicer MAP achieves a minimum of 0.02  with  2  factors 
## Empirical BIC achieves a minimum of  -487.64  with  6  factors
## Sample Size adjusted BIC achieves a minimum of  -90.35  with  10  factors
## 
## Statistics by number of factors 
##    vss1 vss2   map dof   chisq     prob sqresid  fit RMSEA  BIC SABIC complex
## 1  0.72 0.00 0.027 189 2.5e+03  0.0e+00    16.4 0.72 0.134 1287  1887     1.0
## 2  0.69 0.80 0.023 169 1.8e+03 3.9e-261    11.4 0.80 0.117  648  1185     1.3
## 3  0.67 0.81 0.024 150 1.4e+03 9.4e-195     9.1 0.84 0.108  384   860     1.7
## 4  0.66 0.81 0.024 132 9.3e+02 1.7e-119     7.4 0.87 0.093   64   483     2.0
## 5  0.66 0.81 0.026 115 6.0e+02  3.1e-67     6.4 0.89 0.078 -149   217     2.2
## 6  0.68 0.79 0.031  99 3.1e+02  7.3e-23     5.8 0.90 0.055 -342   -27     2.4
## 7  0.68 0.79 0.037  84 2.0e+02  5.1e-12     5.3 0.91 0.046 -345   -78     2.6
## 8  0.67 0.79 0.042  70 1.6e+02  7.7e-09     4.5 0.92 0.043 -299   -77     2.7
## 9  0.67 0.76 0.047  57 1.1e+02  1.3e-05     3.8 0.93 0.038 -259   -78     2.8
## 10 0.66 0.77 0.061  45 6.1e+01  5.8e-02     3.3 0.94 0.023 -233   -90     2.9
## 11 0.64 0.75 0.075  34 4.0e+01  2.1e-01     3.3 0.94 0.016 -182   -74     2.9
## 12 0.66 0.76 0.095  24 2.5e+01  4.0e-01     2.8 0.95 0.008 -132   -56     3.2
## 13 0.67 0.77 0.122  15 9.2e+00  8.7e-01     3.1 0.95 0.000  -89   -41     2.9
## 14 0.65 0.78 0.151   7 3.1e+00  8.7e-01     2.6 0.95 0.000  -43   -20     3.0
## 15 0.68 0.82 0.168   0 5.3e-01       NA     2.7 0.95    NA   NA    NA     3.5
## 16 0.67 0.78 0.233  -6 4.7e-05       NA     2.7 0.95    NA   NA    NA     3.5
## 17 0.65 0.80 0.281 -11 3.6e-06       NA     2.8 0.95    NA   NA    NA     3.5
## 18 0.65 0.80 0.319 -15 1.3e-06       NA     2.8 0.95    NA   NA    NA     3.5
## 19 0.65 0.80 0.479 -18 7.7e-08       NA     2.8 0.95    NA   NA    NA     3.5
## 20 0.65 0.80 1.000 -20 0.0e+00       NA     2.7 0.95    NA   NA    NA     3.5
##     eChisq    SRMR  eCRMS eBIC
## 1  3.2e+03 1.1e-01 0.1111 1980
## 2  1.5e+03 7.2e-02 0.0804  401
## 3  8.9e+02 5.6e-02 0.0657  -87
## 4  4.8e+02 4.1e-02 0.0513 -384
## 5  2.7e+02 3.1e-02 0.0414 -480
## 6  1.6e+02 2.3e-02 0.0342 -488
## 7  1.1e+02 1.9e-02 0.0301 -444
## 8  6.3e+01 1.5e-02 0.0257 -394
## 9  3.4e+01 1.1e-02 0.0207 -339
## 10 1.6e+01 7.4e-03 0.0160 -278
## 11 9.6e+00 5.8e-03 0.0143 -213
## 12 5.2e+00 4.2e-03 0.0125 -152
## 13 2.4e+00 2.9e-03 0.0108  -96
## 14 5.5e-01 1.4e-03 0.0075  -45
## 15 7.2e-02 5.0e-04     NA   NA
## 16 8.1e-06 5.3e-06     NA   NA
## 17 8.1e-07 1.7e-06     NA   NA
## 18 2.5e-07 9.3e-07     NA   NA
## 19 1.6e-08 2.4e-07     NA   NA
## 20 8.5e-18 5.4e-12     NA   NA

Es folgt die Parallelanalysemit der Berechnung der Eigenwerte: Summe der quadrierten Faktorladungen und Varianzaufklärung

ev <- eigen(cor(C12))
ap <- parallel(subject= nrow(C12),var=ncol(C12),
               rep=100,cent=.05)
nS <- nScree(x=ev$values, aparallel=ap$eigen$qevpea)
plotnScree(nS)

Maximum Likely Analyse

fit1 <- factanal(C12, factors = 4, rotation = "varimax")
print(fit1, digits = 3, cutoff=.3)
## 
## Call:
## factanal(x = C12, factors = 4, rotation = "varimax")
## 
## Uniquenesses:
##  C12A  C12B  C12C  C12D  C12E  C12F  C12G  C12H  C12J  C12K  C12L  C12M  C12O 
## 0.823 0.849 0.764 0.160 0.210 0.496 0.895 0.595 0.512 0.629 0.319 0.272 0.566 
##  C12P  C12R  C12S  C12T  C12U  C12W  C12X  C12Z 
## 0.752 0.848 0.165 0.166 0.545 0.807 0.766 0.501 
## 
## Loadings:
##      Factor1 Factor2 Factor3 Factor4
## C12A  0.366                         
## C12B  0.320                         
## C12C  0.409                         
## C12D  0.850                         
## C12E  0.848                         
## C12F  0.576           0.304         
## C12G                                
## C12H          0.470                 
## C12J  0.546   0.327                 
## C12K  0.306           0.414         
## C12L                  0.808         
## C12M                  0.840         
## C12O                          0.608 
## C12P                          0.485 
## C12R                          0.350 
## C12S  0.502   0.706                 
## C12T  0.405   0.790                 
## C12U  0.315   0.557                 
## C12W          0.324                 
## C12X                          0.347 
## C12Z          0.566           0.303 
## 
##                Factor1 Factor2 Factor3 Factor4
## SS loadings      3.381   2.656   2.029   1.293
## Proportion Var   0.161   0.126   0.097   0.062
## Cumulative Var   0.161   0.287   0.384   0.446
## 
## Test of the hypothesis that 4 factors are sufficient.
## The chi square statistic is 782.01 on 132 degrees of freedom.
## The p-value is 6.97e-93