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.
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
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:
Achten Sie dabei darauf, die richtige Version für Ihr Betriebssystem auszuwählen. Installieren Sie zuerst die Programmiersprache R und dann das Arbeitsumfeld RStudio.
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"
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.
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.
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
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
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
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
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:
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
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."
Ein Vektor ist die Zusammenfassung von Objekten zu einer endlichen Folge. Vektoren sind eindimensional und bestehen nur aus einem Datentyp.
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"
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
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
# 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
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.
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.
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"
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
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
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
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
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"
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.
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.
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"
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.
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)
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
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
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
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)
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
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
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
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)
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
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.
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
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"
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"
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!"
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"
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!"
Worum gehts?
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.
Häufigkeit (frequencies): Darstellung einer Ausprägung im Verhältnis ihres Auftretens
abschluss <- table(Studis$Abschluss)
print(abschluss)
##
## Abi BA MA
## 7 11 8
rel_abschluss <- prop.table(abschluss)
print(rel_abschluss)
##
## Abi BA MA
## 0.2692308 0.4230769 0.3076923
kum_abschluss <- cumsum(rel_abschluss)
print(kum_abschluss)
## Abi BA MA
## 0.2692308 0.6923077 1.0000000
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
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
sd(Studis$Alter)
## [1] 9.358172
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
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
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
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
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
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.
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)
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))
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"))
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
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)
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
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
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.
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.
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
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
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)
>%> - 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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
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")
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.
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.
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.
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.).
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