data(airquality)
18 Datenvorbereitung
Vor jeder statistischen Auswertung ist es notwendig, die Daten entsprechend der angestrebten Analyse aufzubereiten. Beispielsweise kann das beinhalten, Daten zusammenzuführen, zu extrahieren oder zu sortieren, aber auch Variablen umzukodieren oder transformierte Variablen zu erstellen.
In diesem Kapitel wollen wir uns verschiedene Schritte der Datenvorbereitung anschauen. Wir nutzen dafür größtenteils Funktionen aus zwei verschiedenen Paketen: dem Standardpaket base und dem Zusatzpaket dplyr. Zweiteres laden wir mit library(dplyr)
.
Wir fangen mit grundsätzlichen Überprüfungen unserer der Daten an. Nachfolgend arbeiten wir uns in spezifischere Aufbereitungsbereiche vor, die wir in Abhängigkeit unserer geplanten Auswertung ggf. benötigen.
Beispieldatensätze für dieses Kapitel
Für das vorliegende Kapitel nutzen wir mehrere Datensätze, um die unterschiedlichen Verarbeitungsschritte zu demonstrieren.
Den Datensatz airquality werden wir am meisten nutzen. Dieser enthält Daten aus einer Untersuchung der Luftqualität in New York, die von Mai bis September des Jahres 1973 stattfand. Der Datensatz ist standardmäßig in R enthalten und wir bekommen ihn mit der Funktion data()
in unser R-Environment:
head(airquality)
Ozone Solar.R Wind Temp Month Day
1 41 190 7.4 67 5 1
2 36 118 8.0 72 5 2
3 12 149 12.6 74 5 3
4 18 313 11.5 62 5 4
5 NA NA 14.3 56 5 5
6 28 NA 14.9 66 5 6
Der Datensatz enthält 6 Variablen:
Ozone
: mittlere Ozonkonzentration in ppb (parts per billion)Solar.R
: Sonneneinstrahlung in Langley (Einheit)Wind
: durchschnittliche Windgeschwindigkeit in Meilen pro StundeTemp
: maximale Temperatur in Grad FahrenheitMonth
: Monatsangabe als Zahl (1-12)Day
: Tagesangabe als Zahl (1-31)
Mehr Informationen zum Datensatz finden wir hier.
Den Datensatz PWE_data, welcher Daten einer psychometrischen Erhebung enthält, werden wir auch häufiger nutzen. Um den Datensatz und das Codebuch herunterzuladen, klicken wir auf diesen Link. Dann gehen wir in den Ordner PWE_data und lesen data.csv ein:
library(readr) # zum Einlesen der csv-Datei
<- read_table("Dateipfad/data.csv") PWE_data
names(PWE_data)
[1] "Q1A" "Q1I" "Q1E" "Q2A" "Q2I"
[6] "Q2E" "Q3A" "Q3I" "Q3E" "Q4A"
[11] "Q4I" "Q4E" "Q5A" "Q5I" "Q5E"
[16] "Q6A" "Q6I" "Q6E" "Q7A" "Q7I"
[21] "Q7E" "Q8A" "Q8I" "Q8E" "Q9A"
[26] "Q9I" "Q9E" "Q10A" "Q10I" "Q10E"
[31] "Q11A" "Q11I" "Q11E" "Q12A" "Q12I"
[36] "Q12E" "Q13A" "Q13I" "Q13E" "Q14A"
[41] "Q14I" "Q14E" "Q15A" "Q15I" "Q15E"
[46] "Q16A" "Q16I" "Q16E" "Q17A" "Q17I"
[51] "Q17E" "Q18A" "Q18I" "Q18E" "Q19A"
[56] "Q19I" "Q19E" "country" "introelapse" "testelapse"
[61] "surveyelapse" "TIPI1" "TIPI2" "TIPI3" "TIPI4"
[66] "TIPI5" "TIPI6" "TIPI7" "TIPI8" "TIPI9"
[71] "TIPI10" "VCL1" "VCL2" "VCL3" "VCL4"
[76] "VCL5" "VCL6" "VCL7" "VCL8" "VCL9"
[81] "VCL10" "VCL11" "VCL12" "VCL13" "VCL14"
[86] "VCL15" "VCL16" "education" "urban" "gender"
[91] "engnat" "age" "screenw" "screenh" "hand"
[96] "religion" "orientation" "race" "voted" "married"
[101] "familysize" "major"
Der Datensatz enthält 102 Variablen. Einige davon schauen wir uns im Laufe des Kapitels noch genauer an. Im Codebook, welches sich ebenfalls im Ordner PWE_data befindet, finden wir eine Erklärung zu den Variablen. Mehr Informationen zum Erhebungsinstrument, der Protestant Work Ethic Scale, finden wir in Mirels & Garrett (1971) (nur über HU-VPN zugänglich).
Achtung: Im Abschnitt Plausibilitäts-Check werden die Kodierungen einiger Variablen noch korrigiert. Außerdem müssen die Werte der Variablen
Q9A
,Q13A
undQ15A
noch invertiert werden, da diese negativ gepolt sind. Das passiert im Abschnitt Umkodieren.
Die Datensätze vornamen_13 und vornamen_14 enthalten die Vornamen der Neugeborenen in München, jeweils für die Jahre 2013 und 2014. Diese werden wir nur für 2. Datensätze zusammenführen nutzen.
Zuerst laden wir die csv-Dateien für 2013 und 2014 runter und lesen sie dann folgendermaßen in R ein:
<- read.csv("Dateipfad/vornamen-von-neugeborenen2013.csv")
vornamen_13 <- read.csv("Dateipfad/vornamen-von-neugeborenen2014.csv") vornamen_14
head(vornamen_13)
vorname anzahl geschlecht
1 Maximilian 166 m
2 Felix 124 m
3 Anna 109 w
4 David 109 m
5 Sophia 108 w
6 Emilia 103 w
head(vornamen_14)
vorname anzahl geschlecht
1 Maximilian 178 m
2 Felix 134 m
3 Anna 119 w
4 Emma 113 w
5 Lukas 109 m
6 Emilia 109 w
Die Datensätze enthalten jeweils die gleichen 3 Variablen:
vorname
: Vorname (kann doppelt vorkommen, wenn Name für beide Geschlechter gegeben wurde)anzahl
: Häufigkeit des Vornamens in diesem Jahrgeschlecht
: (binäres) Geschlecht der Kinder mit diesem Vornamen
Achtung: Es kommt häufiger zu Problemen bei der Ausführung der Funktionen
filter()
,select()
undsummarise()
aus dem Paket dpylr, wenn die Pakete stats (Basispaket;filter()
), MASS (select()
) oder plyr (summarise()
) ebenfalls geladen sind. Auch bei anderen gleichnamigen Funktionen aus verschiedenen geladenen Paketen kann es durch die sogenannte Maskierung zu Problemen kommen. Weil wir auch Pakete mit gleich benannten Funktionen nutzen, greifen wir teils auf die eindeutige Auswahl von Funktionen mittels::
zurück. Für mehr Informationen zum Maskieren können wir im gleichnamigen Abschnitt im Kapitel Pakete nachschauen. Mehr Informationen zu dem speziellen Problem mit dpylr finden wir in diesem Forumseintrag.
18.1 Grundlegende erste Schritte
Zuerst widmen wir unsere Aufmerksamkeit der Überprüfung wichtiger übergreifender Punkte der Datenvorbereitung. Diese sind: ob unser Datensatz als Dataframe vorliegt, ob unsere Daten plausibel und fehlende Werte korrekt kodiert sind, ob nominal- und v.a. ordinalskalierte Variablen faktorisiert sind, und ob wir unser bestehendes Tabellenformat ggf. ändern müssen. Die Schritte sind bereits in einer sinnvollen Abfolge angeordnet (z.B. ist es vorteilhaft, erst unplausible und fehlende Werte ausfindig zu machen und umzukodieren, bevor man Variablen faktorisiert).
Optional können wir vorher unser Wissen zum Messen von Merkmalen und der korrekten Darstellung dieser in R auffrischen.
Wir schauen uns nachfolgend nur die Datensätze airquality und PWE_data an.
18.1.1 Recap: Kodierung von Daten
Generell wenn wir mit Daten arbeiten, ist es ratsam, sich zuallererst Gedanken darüber zu machen, welche Informationen wir diesen entnehmen können (Messniveau) und ob sie so gespeichert sind, dass R sie richtig erkennt (Datentypen und -strukturen).
Die nachfolgende Wiederholung ist eine verkürzte Variante des Abschnitts Daten aus dem Kapitel zu Einführung in R.
18.1.1.1 Messniveaus
Das Messniveau (oder auch Skalenniveau) ist eine wichtige Eigenschaft von Merkmalen (Variablen) von Untersuchungseinheiten. Es beschreibt, welche Informationen in unseren Messwerten abgebildet werden und damit auch welche mathematischen Transformationen mit den Messwerten sinnvoll sind (z.B. das Berechnen von Mittelwerten). Somit begrenzt das Messniveau auch die zulässigen Datenauswertungsverfahren unserer Variablen.
Die Kodierung von nominalskalierten Merkmalen ist insofern willkürlich, als dass lediglich auf Gleichheit versus Ungleichheit geachtet werden muss (z.B. 1
, 4
, 9
oder A
, Y
, M
).
airquality: -
PWE_data: u.a.school
, urban
, gender
Die Kodierung von ordinalskalierten Merkmalen geschieht der Größe nach, d.h. dass die Rangfolge der Kodierungen einzelner Gruppen relevant ist (z.B. 1
< 4
< 9
oder A
< M
< Y
). Man kann aber auch eine eigene Sortierung festlegen, die nicht der “natürlichen” Rangfolge entspricht (z.B. Y
< A
< M
). Ein Realschulabschluss ist beispielsweise besser als ein Hauptschulabschluss. Wir können aber nicht festlegen, wie viel besser er ist.
airquality: -
PWE_data: education
Bei der Kodierung von intervallskalierten Merkmalen sind sowohl die Rangfolge als auch die Abstände zwischen den Ausprägungen relevant (z.B. 1
, 4
, 7
; jeweils mit gleichem Abstand zueinander; oder 1.4
, 1.5
, 2.3
; jeweils mit verschiedenen Abständen zueinander). Ein Beispiel dafür ist die Temperatur in Grad Celsius oder Grad Fahrenheit.
airquality: Temp
(Temperatur in Grad Fahrenheit)
PWE_data: u.a. Antworten (1-5) auf die Items des PWE (Q1A
, …, Q19A
), Antworten (1-7) auf die Items des TIPI (TIPI1
, …, TIPI10
)
Bei der Kodierung von verhältnisskalierten Merkmalen ist zusätzlich noch ein Nullpunkt vorhanden. Dieser erlaubt es, dass Quotienten zwischen Werten gebildet werden können. Ein beliebtes Beispiel ist die Kelvin Skala. Bei dieser ist bei 0°K keine Bewegungsenergie mehr vorhanden und 20°K sind doppelt so viel wie 40°K.
airquality: Ozone
, Solar.R
, Wind
PWE_data: age
Zu guter Letzt gibt es noch absolutskalierte Merkmale, welche sowohl einen eindeutigen Nullpunkt als auch eine eindeutige Einheit der Skala (z.B. Anzahl der Kinder) vorweisen kann. Die Kodierung entspricht der natürlichen Einheit.
airquality: -
PWE_data: familysize
Nachfolgend finden wir eine Tabelle der möglichen Unterscheidungen der jeweiligen Messiniveaus.
(Un-) Gleichheit | Rangordnung | Abstände | Verhältnisse | natürliche Einheit | |
---|---|---|---|---|---|
Nominal | X | ||||
Ordinal | X | X | |||
Intervall | X | X | X | ||
Verhältnis | X | X | X | X | |
Absolut | X | X | X | X | X |
18.1.1.2 Datentypen und Datenstrukturen
Achtung: Die Unterteilung nach “Datentyp” und “Datenstruktur” sind getreu des Manuals von R. Man stößt in anderen Quellen aber auch auf abweichende Unterteilungen bzw. Benennungen.
Der Datentyp gibt an, um was für Daten es sich handelt, d.h. welche Werte(bereiche) diese haben und welche Operationen wir auf sie anwenden können. Wir nutzen zumeist Zeichen, Wahrheitswerte und Zahlen. Diese werden in R als character, logical, integer und double gespeichert, wobei letztere beiden häufig als numeric zusammengefasst werden.
Die Datenstruktur bestimmt die Organisation und Speicherung von Daten(typen). In R gibt es z.B. Vektoren, Matrizen, Dataframes, Listen und Faktoren. Beispielsweise können Vektoren und Matrizen jeweils nur einen Datentypen enthalten, während Dataframes mehrere Datentypen enthalten können.
Datentyp und -struktur sind ausschlaggebend dafür, welche Funktionen wir anwenden können bzw. welchen Output wir bekommen.
- Beispiel 1:
Man kann nur mit numeric deskriptiv-statistische Kennwerte bilden. - Beispiel 2:
Wenn mir nominal- bzw. ordinalskalierte Daten nicht adäquat kodieren, kann es zu ungewollten Konsequenzen kommen.
Wenn wir z.B. eine Variable \(X\) haben, welche eine Gruppenzugehörigkeit mit 1, 2 und 3 kodiert, und diese als Prädiktor in ein Regressionsmodell aufnehmen, dann wird \(X\) als kontinuierliche Variable behandelt und wir würden keine separaten Schätzungen für die Mittelwertsdifferenzen der Gruppen bekommen.
Nachfolgend finden wir eine Übersicht zu den Möglichkeiten der Kodierung von Merkmalen mit verschiedenen Skalenniveaus.
Nominal- | Ordinal- | Intervall- | Verhältnis- | Absolut- | ||
---|---|---|---|---|---|---|
Datentyp: | character | X | X2 | |||
Datentyp: | numeric | X1 | X2 | X | X | X |
1 Faktorisieren (unordered factor) notwendig wenn keine Indikatorvariable(n) genutzt 2 Faktorisieren (ordered factor) notwendig |
Mit der Funktion str()
können wir uns eine kompakte Übersicht der enthaltenen Variablen (d.h. ihr Datentyp bzw. die Datenstruktur Faktor sowie jeweils die ersten 10 Werte) der Datenstruktur (hier: Dataframe) ausgeben lassen. So können wir schauen, ob die Daten auch entsprechend ihres Messniveaus kodiert sind.
str(airquality)
'data.frame': 153 obs. of 6 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
$ Month : int 5 5 5 5 5 5 5 5 5 5 ...
$ Day : int 1 2 3 4 5 6 7 8 9 10 ...
Für den Beispieldatensatz airquality liegen die verhältnisskalierten Variablen Ozone
,Solar.R
, Wind
, Month
und Day
korrekterweise als integer bzw. numeric vor. Die intervallskalierte Variable Temp
liegt auch wie erwartet als integer vor (weil die Temperatur in Fahrenheit und nicht in Kelvin gemessen wurde ist die Variable nicht verhältnisskaliert).
Für den Datensatz PWE_data schauen wir uns exemplarisch nur die letzten 15 Variablen an:
ls.str(PWE_data[,88:102])
age : num [1:1350] 24 66 17 23 19 40 50 21 16 27 ...
education : num [1:1350] 3 2 2 2 2 4 4 2 1 2 ...
engnat : num [1:1350] 1 1 2 1 1 1 1 1 1 1 ...
familysize : num [1:1350] 2 5 4 3 2 3 3 1 3 2 ...
gender : num [1:1350] 1 2 2 2 2 2 1 3 2 1 ...
hand : num [1:1350] 1 2 1 2 1 1 1 3 3 1 ...
major : chr [1:1350] "Computer" NA NA "dietetics" NA "Philosophy" "engineering" ...
married : num [1:1350] 1 3 1 1 1 3 2 1 1 2 ...
orientation : num [1:1350] 1 1 1 1 1 2 1 5 2 1 ...
race : num [1:1350] 11 16 16 16 16 16 16 17 16 16 ...
religion : num [1:1350] 6 6 2 2 1 7 6 4 1 2 ...
screenh : num [1:1350] 1080 640 1024 1080 615 ...
screenw : num [1:1350] 1920 360 1280 1920 1093 ...
urban : num [1:1350] 2 3 2 2 1 3 1 2 3 2 ...
voted : num [1:1350] 2 2 2 2 2 2 1 2 2 1 ...
Wir nutzen hier für PWE_data die Funktion ls.str()
, weil der Output für ein Tibble Dataframe so weniger ausführlich ist. Allerdings werden die Variablen mit dieser Funktion alphabetisch sortiert (im Gegensatz zu str()
, welche die Variablen der Spaltennummerierung nach darstellt).
Wie wir sehen, liegen fast alle Variablen als numeric vor, obwohl viele nominalskaliert sind. So würden sie von R nicht entsprechend ihres Messniveaus behandelt werden. Wir müssen diese also entweder in character umwandeln oder faktorisieren (später mehr dazu).
18.1.2 Dataframe
Für viele Anwendungen in R (z.B. für das [Erstellen von Grafiken mit ggplot2][Grafiken ggplot]) ist es notwendig, dass der Datensatz als Dataframe vorliegt. Folgendermaßen können wir prüfen, ob ein Datensatz ein Dataframe ist:
is.data.frame(airquality)
[1] TRUE
is.data.frame(PWE_data)
[1] TRUE
Was genau ist ein Dataframe?
Ein Dataframe ist eine Datenstruktur, die es uns erlaubt, Daten tabellarisch zu speichern. Der Dataframe ist eine Liste aus Vektoren mit gleicher Länge. Listen können (im Gegensatz zu Matrizen) Variablen mit unterschiedlichen Datentypen speichern.
Mehr Informationen gibt es im vorhergehenden Abschnitt zu Datentypen und Datenstrukturen.
Falls unser Datensatz kein Dataframe ist, können wir ihn folgendermaßen umwandeln:
<- as.data.frame(airquality) airquality
Die Funktion as.data.frame()
enthält das Argument stringsAsFactors
, mit dem wir bestimmen können, ob Zeichenketten (character) zu Faktoren umgewandelt werden sollen (TRUE
). Falls wir ordinalskalierte Daten haben, müssen wir den Faktor dann aber noch zusätzlich ordnen. Generell ist es sinnvoll, erst nach der Überprüfung auf unplausible und fehlende Werte zu faktorisieren, damit eventuell vorhandene Fehlkodierungen nicht als Faktorstufen behandelt werden.
18.1.3 Plausibilitäts-Check
Im nächsten Schritt lohnt es sich zu überprüfen, ob in den vorangegangen Schritten der Erhebung und Kodierung unserer Daten, Fehler passiert sind. Das ist wichtig damit wir solche Fehler nicht in unsere Analyse übertragen, wo sie sehr viel unwahrscheinlicher auffallen werden. Dafür überprüfen wir, ob die Messniveaus und Ausprägungen unserer interessierenden Variablen auch unseren Erwartungen entsprechen.
Entweder man hat die Daten selbst erhoben und somit Wissen darüber, welche Werte möglich sind, oder man schaut sich die Dokumentation zu den Daten an.
Für den Datensatz airquality finden wir die Dokumentation hier.
Konkretisieren wir einmal einen hypothetischen Fall an der Variable Day
. Wir wissen, dass diese mit Zahlen von 1-31 kodiert sein kann. Es wäre also unplausibel, wenn andere Werte (z.B. 0 oder 32) vorliegen würden.
Das Auftauchen von unplausiblen Werten ist z.B. wahrscheinlicher, wenn Daten manuell digitalisiert (d.h. eingetippt) wurden (z.B. Paper-and-Pencil Tests). Gerade in diesen Fällen sollte man sicher gehen, und die Daten auf unplausible Werte hin überprüfen.
Die Funktion unique()
gibt uns einen Überblick über alle enthaltenen Ausprägungen eines Vektors. Wenn wir diese mit sapply()
kombinieren, können wir das für jede Variable im Datensatz anwenden. Wenn wir den Output wiederum erneut an sapply()
übergeben, und sort()
anwenden, werden die Ausprägungen aufsteigend sortiert (weil der Default decreasing = FALSE
ist), was die Überprüfung erleichtert. In sort()
können wir außerdem na.last=TRUE
nutzen, um uns das Vorhandensein von NA
s am Ende der Ausprägungen anzeigen zu lassen.
sapply(sapply(airquality, unique), sort, na.last=TRUE)
$Ozone
[1] 1 4 6 7 8 9 10 11 12 13 14 16 18 19 20 21 22 23 24
[20] 27 28 29 30 31 32 34 35 36 37 39 40 41 44 45 46 47 48 49
[39] 50 52 59 61 63 64 65 66 71 73 76 77 78 79 80 82 84 85 89
[58] 91 96 97 108 110 115 118 122 135 168 NA
$Solar.R
[1] 7 8 13 14 19 20 24 25 27 31 36 37 44 47 48 49 51 59
[19] 64 65 66 71 77 78 81 82 83 91 92 95 98 99 101 112 115 118
[37] 120 127 131 135 137 138 139 145 148 149 150 153 157 167 175 183 186 187
[55] 188 189 190 191 192 193 194 197 201 203 207 212 213 215 220 222 223 224
[73] 225 229 230 236 237 238 242 244 248 250 252 253 254 255 256 258 259 260
[91] 264 266 267 269 272 273 274 275 276 279 284 285 286 287 290 291 294 295
[109] 299 307 313 314 320 322 323 332 334 NA
$Wind
[1] 1.7 2.3 2.8 3.4 4.0 4.1 4.6 5.1 5.7 6.3 6.9 7.4 8.0 8.6 9.2
[16] 9.7 10.3 10.9 11.5 12.0 12.6 13.2 13.8 14.3 14.9 15.5 16.1 16.6 18.4 20.1
[31] 20.7
$Temp
[1] 56 57 58 59 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
[26] 82 83 84 85 86 87 88 89 90 91 92 93 94 96 97
$Month
[1] 5 6 7 8 9
$Day
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
[26] 26 27 28 29 30 31
Einen kompakten Überblick über die Verteilung der Variablen können wir mit der Funktion summary()
bekommen. Hierbei interessieren uns vor allem die Extremwerte (Min. und Max.), d.h. der Range der Variablen, und die Missings (NA
).
summary(airquality)
Ozone Solar.R Wind Temp
Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00
1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00
Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88
3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
NA's :37 NA's :7
Month Day
Min. :5.000 Min. : 1.0
1st Qu.:6.000 1st Qu.: 8.0
Median :7.000 Median :16.0
Mean :6.993 Mean :15.8
3rd Qu.:8.000 3rd Qu.:23.0
Max. :9.000 Max. :31.0
Insgesamt sehen die Daten plausibel aus. Für eine spezifischere Einschätzung der Wetter-Variablen (Ozone
, Solar.R
, Wind
und Temp
) könnte man sich zusätzlich Vergleichsdaten von anderen Erhebungen in einem ähnlichen Zeitraum und Gebiet anschauen.
Für den Datensatz PWE_data schauen wir uns hier wieder nur einige Variablen an. Informationen zu den Variablen finden wir in der Codebook, welches sich im Ordner PWE_data befindet.
sapply(sapply(PWE_data[,88:101], unique), sort)
$education
[1] 0 1 2 3 4
$urban
[1] 0 1 2 3
$gender
[1] 0 1 2 3
$engnat
[1] 0 1 2
$age
[1] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
[26] 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
[51] 63 64 65 66 67 69 70 71 72 74 75 76 77 81 90
$screenw
[1] 0 320 347 360 375 393 396 412 414 424 455 486 505 570 586
[16] 600 601 640 720 768 780 800 810 834 864 948 960 962 1024 1080
[31] 1093 1111 1138 1152 1200 1242 1280 1300 1348 1360 1364 1366 1368 1376 1400
[46] 1422 1423 1440 1477 1493 1500 1504 1536 1541 1600 1676 1680 1707 1821 1824
[61] 1920 2048 2400 2560 2561 3072 3840
$screenh
[1] 0 320 347 360 414 480 486 505 568 569 570 576 592 597 601
[16] 614 615 618 640 648 658 667 672 692 698 702 720 731 732 736
[31] 740 741 744 747 748 752 753 758 759 760 762 768 774 780 786
[46] 800 803 808 809 812 819 820 831 846 848 853 854 863 864 866
[61] 869 879 888 892 896 900 902 912 933 943 945 960 962 1000 1003
[76] 1022 1024 1050 1080 1112 1152 1194 1200 1280 1350 1366 1440 1536 1728 1920
[91] 2160
$hand
[1] 0 1 2 3
$religion
[1] 0 1 2 3 4 5 6 7 8 9 10 11 12
$orientation
[1] 0 1 2 3 4 5
$race
[1] 10 11 12 13 14 15 16 17
$voted
[1] 0 1 2
$married
[1] 0 1 2 3
$familysize
[1] 0 1 2 3 4 5 6 7 8 9 10 12 13 14 33
Es fällt auf, dass alle dargestellten Variablen (bis auf age
), die Ausprägung 0
enthalten, obwohl diese im Codebook für diese Variablen nicht definiert ist. Auch für die anderen Variablen im Datensatz, mit Ausnahme von VCL1
, …, VCL16
, gilt, dass 0
nicht als mögliche Ausprägung gegeben wird, obwohl sie vorhanden ist. Wir können daher annehmen, dass 0
wahrscheinlich eine alternative Kodierung für NA
ist. Besser wäre es natürlich, wenn wir die Wissenschaftler*innen, welche die Daten erhoben haben, kontaktieren und nachfragen würden.
Wie wir sehen, geht die Überprüfung von plausiblen und fehlenden Werten häufig ineinander über. Werte, die außerhalb des Ranges der betrachteten Variable liegen, können eine Kodierung für fehlende Werte darstellen.
Wenn wir unplausible Werte in unseren Daten finden, können wir zu 5. Kodierung ändern springen, und diese umkodieren. Wenn wir fehlkodierte Missings finden, können wir auch case_when()
oder alternative Möglichkeiten aus dem Abschnitt [Sind die Missings einheitlich kodiert][Sind die Missings einheitlich kodiert?] des Kapitels Fehlende Werte nutzen (z.B. die im Folgenden illustrierte Umkodierung des gesamten Datensatzes).
Weil wir in PWE_data sehr viele Variablen haben und es zu umständlich wäre, alle einzeln umzukodieren, kodieren wir erst im gesamten Datensatz 0
zu NA
um, und ändern danach wieder die Kodierung für die Variablen VCL1
bisVCL16
.
# Umkodierung für gesamten Datensatz
== 0] <- NA
PWE_data[PWE_data
# "Rückkodierung" für Variablen, die regulär 0 enthalten
# library(dplyr)
$VCL1 <- case_when(is.na(PWE_data$VCL1) ~ 0, # Umkodierung von NA zu 0
PWE_data$VCL1 == 1 ~ 1) # bleibt gleich
PWE_data$VCL2 <- case_when(is.na(PWE_data$VCL2) ~ 0, PWE_data$VCL2 == 1 ~ 1)
PWE_data$VCL3 <- case_when(is.na(PWE_data$VCL3) ~ 0, PWE_data$VCL3 == 1 ~ 1)
PWE_data$VCL4 <- case_when(is.na(PWE_data$VCL4) ~ 0, PWE_data$VCL4 == 1 ~ 1)
PWE_data$VCL5 <- case_when(is.na(PWE_data$VCL5) ~ 0, PWE_data$VCL5 == 1 ~ 1)
PWE_data$VCL6 <- case_when(is.na(PWE_data$VCL6) ~ 0, PWE_data$VCL6 == 1 ~ 1)
PWE_data$VCL7 <- case_when(is.na(PWE_data$VCL7) ~ 0, PWE_data$VCL7 == 1 ~ 1)
PWE_data$VCL8 <- case_when(is.na(PWE_data$VCL8) ~ 0, PWE_data$VCL8 == 1 ~ 1)
PWE_data$VCL9 <- case_when(is.na(PWE_data$VCL9) ~ 0, PWE_data$VCL9 == 1 ~ 1)
PWE_data$VCL10 <- case_when(is.na(PWE_data$VCL10) ~ 0, PWE_data$VCL10 == 1 ~ 1)
PWE_data$VCL11 <- case_when(is.na(PWE_data$VCL11) ~ 0, PWE_data$VCL11 == 1 ~ 1)
PWE_data$VCL12 <- case_when(is.na(PWE_data$VCL12) ~ 0, PWE_data$VCL12 == 1 ~ 1)
PWE_data$VCL13 <- case_when(is.na(PWE_data$VCL13) ~ 0, PWE_data$VCL13 == 1 ~ 1)
PWE_data$VCL14 <- case_when(is.na(PWE_data$VCL14) ~ 0, PWE_data$VCL14 == 1 ~ 1)
PWE_data$VCL15 <- case_when(is.na(PWE_data$VCL15) ~ 0, PWE_data$VCL15 == 1 ~ 1)
PWE_data$VCL16 <- case_when(is.na(PWE_data$VCL16) ~ 0, PWE_data$VCL16 == 1 ~ 1) PWE_data
Nun schauen wir uns noch die Verteilungen der Variablen an.
summary(PWE_data[,88:101])
education urban gender engnat
Min. :1.000 Min. :1.000 Min. :1.000 Min. :1.000
1st Qu.:2.000 1st Qu.:2.000 1st Qu.:1.000 1st Qu.:1.000
Median :3.000 Median :2.000 Median :1.000 Median :1.000
Mean :2.653 Mean :2.135 Mean :1.528 Mean :1.283
3rd Qu.:3.000 3rd Qu.:3.000 3rd Qu.:2.000 3rd Qu.:2.000
Max. :4.000 Max. :3.000 Max. :3.000 Max. :2.000
NA's :17 NA's :19 NA's :7 NA's :2
age screenw screenh hand
Min. :13.00 Min. : 320 Min. : 320.0 Min. :1.000
1st Qu.:20.00 1st Qu.: 414 1st Qu.: 736.0 1st Qu.:1.000
Median :26.00 Median :1366 Median : 800.0 Median :1.000
Mean :29.74 Mean :1169 Mean : 850.3 Mean :1.165
3rd Qu.:36.00 3rd Qu.:1536 3rd Qu.: 948.8 3rd Qu.:1.000
Max. :90.00 Max. :3840 Max. :2160.0 Max. :3.000
NA's :2 NA's :2 NA's :5
religion orientation race voted
Min. : 1.000 Min. :1.000 Min. :10.00 Min. :1.00
1st Qu.: 2.000 1st Qu.:1.000 1st Qu.:16.00 1st Qu.:1.00
Median : 4.000 Median :1.000 Median :16.00 Median :2.00
Mean : 4.452 Mean :1.606 Mean :15.43 Mean :1.51
3rd Qu.: 6.000 3rd Qu.:2.000 3rd Qu.:16.00 3rd Qu.:2.00
Max. :12.000 Max. :5.000 Max. :17.00 Max. :2.00
NA's :15 NA's :25 NA's :11
married familysize
Min. :1.000 Min. : 1.000
1st Qu.:1.000 1st Qu.: 2.000
Median :1.000 Median : 2.000
Mean :1.358 Mean : 2.616
3rd Qu.:2.000 3rd Qu.: 3.000
Max. :3.000 Max. :33.000
NA's :6 NA's :28
Hier sehen wir auch, dass für nominalskalierte Merkmale, wie z.B. urban
, orientation
und married
, deskriptiv-statistische Kennwerte wie der Mittelwert gebildet werden (d.h. diese werden als mindestens intervallskaliert behandelt), weil sie als numeric vorliegen. Später werden wir diese noch faktorisieren.
18.1.4 Fehlende Werte
Generell werden fehlende Werte (Missings) in R mit NA
dargestellt. In anderen Programmen mag das anders sein (z.B. werden Missings in Unipark mit 99
oder -99
kodiert). Wie im vorhergehenden Abschnitt demonstriert, überschneidet sich die Überprüfung von plausiblen und fehlenden Werten häufig.
Neben der im letzten Abschnitt vorgestellten Varianten, Missings mit summary()
zu finden, gibt es noch weitere Optionen.
Beispielsweise können wir mit der Kombination von colSums()
und is.na()
spaltenweise Missings zählen.
colSums(is.na(airquality))
Ozone Solar.R Wind Temp Month Day
37 7 0 0 0 0
colSums(is.na(PWE_data))
Q1A Q1I Q1E Q2A Q2I Q2E
1 1 1 1 1 1
Q3A Q3I Q3E Q4A Q4I Q4E
1 1 1 1 1 1
Q5A Q5I Q5E Q6A Q6I Q6E
1 1 1 1 1 1
Q7A Q7I Q7E Q8A Q8I Q8E
1 1 1 1 1 1
Q9A Q9I Q9E Q10A Q10I Q10E
1 1 1 1 1 1
Q11A Q11I Q11E Q12A Q12I Q12E
1 1 1 1 1 1
Q13A Q13I Q13E Q14A Q14I Q14E
1 1 1 1 1 1
Q15A Q15I Q15E Q16A Q16I Q16E
1 1 1 1 1 1
Q17A Q17I Q17E Q18A Q18I Q18E
1 1 1 1 1 1
Q19A Q19I Q19E country introelapse testelapse
1 1 1 0 0 0
surveyelapse TIPI1 TIPI2 TIPI3 TIPI4 TIPI5
0 8 10 13 9 9
TIPI6 TIPI7 TIPI8 TIPI9 TIPI10 VCL1
8 9 8 8 13 0
VCL2 VCL3 VCL4 VCL5 VCL6 VCL7
0 0 0 0 0 0
VCL8 VCL9 VCL10 VCL11 VCL12 VCL13
0 0 0 0 0 0
VCL14 VCL15 VCL16 education urban gender
0 0 0 17 19 7
engnat age screenw screenh hand religion
2 0 2 2 5 15
orientation race voted married familysize major
25 0 11 6 28 448
Achtung: Wenn wir Variablen mit Missings für unsere Analysen nutzen wollen, sollten wir überprüfen, ob die Missings zufällig sind und in Abhängigkeit davon unseren Umgang anpassen, um systematischen Verzerrungen der Analysen entgegenzuwirken.
Einen ausführlichen Überblick zu Missings finden wir im Kapitel Fehlende Werte.
18.1.5 Faktorisieren
Wir schauen uns das Faktorisieren exemplarisch an zwei Variablen aus dem Datensatz PWE_data an:
- nominalskaliert:
gender
- “What is your gender?”:
1
= Male,2
= Female,3
= Other
- “What is your gender?”:
- ordinalskaliert:
education
- “How much education have you completed?”:
1
= Less than high school,2
= High school,3
= University degree,4
= Graduate degree.
- “How much education have you completed?”:
Zuerst erstellen wir einen (neuen) unsortierten (d.h. nominalskalierten) Faktor. Dafür benötigen wir nur die Funktion factor()
, der wir den zu faktorisierenden Vektor übergeben.
# faktorisieren (unsortiert)
$gender_uf <- factor(PWE_data$gender) PWE_data
Nun erstellen wir einen (neuen) sortierten (d.h. ordinalskalierten) Faktor. Dafür ergänzen wir das Argument ordered=TRUE
.
# faktorisieren (sortiert; natürliche Sortierung)
$education_of <- factor(PWE_data$education, ordered=TRUE) PWE_data
Mit dem Argument ordered=TRUE
wird eine Variable nach ihrer “natürlichen” Rangfolge sortiert. Bei Zahlen (integer und numeric) bedeutet das, dass größere Zahlen eine höhere Hierarchieebene haben z.B. 1 < 2. Bei einzelnen Buchstaben und Zeichenketten (character) bedeutet das, dass später im Alphabet auftauchende (Anfangs-)Buchstaben eine höhere Hierarchiebene haben z.B. “Hans” < “Rene”.
Manchmal wollen wir diese Sortierung aber nicht übernehmen, sondern eine eigene Hierarchie erstellen, die nicht der natürlichen Rangfolge entspricht. Das können wir machen, indem wir zusätzlich das Argument levels
spezifizieren, dem wir einen Vektor mit unserer gewünschten Sortierung übergeben.
# faktorisieren (sortiert; eigene "non-sense" Sortierung)
$education_of_s <- factor(PWE_data$education, ordered=TRUE,
PWE_datalevels=c(1,4,2,3))
Abschließend vergleichen wir die ursprüngliche numeric-Variable (education
) mit den unsortierten (education_uf
) und sortierten (education_of
und education_of_s
) Faktor-Variablen.
ls.str(PWE_data[,c(88, 90, 103:105)])
education : num [1:1350] 3 2 2 2 2 4 4 2 1 2 ...
education_of : Ord.factor w/ 4 levels "1"<"2"<"3"<"4": 3 2 2 2 2 4 4 2 1 2 ...
education_of_s : Ord.factor w/ 4 levels "1"<"4"<"2"<"3": 4 3 3 3 3 2 2 3 1 3 ...
gender : num [1:1350] 1 2 2 2 2 2 1 3 2 1 ...
gender_uf : Factor w/ 3 levels "1","2","3": 1 2 2 2 2 2 1 3 2 1 ...
Wir sehen, dass alle Variablen des gleichen Merkmals zwar die gleichen Werte (gender: 1, 2, 3 und education: 1, 2, 3, 4) haben, aber in unterschiedlichen Datentypen bzw. -strukturen (numeric, factor, Ordered factor) und teils unterschiedlichen Sortierungen vorliegen.
Außerdem sehen wir, dass die selbst sortierten Faktoren intern eine neue Kodierung bekommen haben (siehe education_of_s
). Wir sehen diese nur mit str()
bzw. ls.str()
. Diese interne Kodierung richtet sich danach, wie die Faktorstufen sortiert sind. Die erste Ausprägung (nach der eigenen Sortierung) beginnt mit 1.
18.1.6 Wide- und Long-Format
In Abhängigkeit unserer Daten und der Analyse, die wir durchführen wollen, ist es ggf. erforderlich, dass unsere Daten in ein anderes Tabellenformat überführt werden müssen. Es gibt das Wide- und das Long-Format.
Die Unterscheidung von Wide- und Long-Format ist von Bedeutung, wenn unsere Daten eine genestete Struktur aufweisen, das heisst jeweils mehrere Messungen von derselben Untersuchungseinheit vorliegen (z.B. bei Längsschnitterhebungen, mehrere Ratern oder Schülern in Klassen).
Im Wide-Format liegen Messungen einer Untersuchungseinheit in einer Zeile vor. Jeder Messzeitpunkt bzw. jede Messung ist eine eigene Variable.
Beispiel 1 Wide-Format: Messzeitpunkte
Untersuchungseinheit | t1 | t2 | t3 |
---|---|---|---|
1 | 4 | 3 | 1 |
2 | 5 | 2 | 3 |
Beispiel 2 Wide-Format: Rater
Untersuchungseinheit | self | friend | parent |
---|---|---|---|
1 | 2 | 1 | 3 |
2 | 3 | 4 | 2 |
Im Long-Format liegen Messungen einer Untersuchungseinheit in mehreren Zeilen vor. Alle Messzeitpunkte bzw. Messungen von unterschiedlichen Ratern liegen in einer Variable vor und die Messzeitpunkte bzw. Rater werden in einer separaten Variable kodiert.
Beispiel 1 Long-Format: Messzeitpunkte
Untersuchungseinheit | Zeitpunkt | Messung |
---|---|---|
1 | 1 | 4 |
1 | 2 | 3 |
1 | 3 | 1 |
2 | 1 | 5 |
2 | 2 | 2 |
2 | 3 | 3 |
Beispiel 2 Long-Format: Rater
Untersuchungseinheit | Rater | Messung |
---|---|---|
1 | self | 2 |
1 | friend | 1 |
1 | parent | 3 |
2 | self | 3 |
2 | friend | 4 |
2 | parent | 2 |
Im Beispieldatensatz PWE_data gibt es keine wiederholte Messungen. Psychometrische und demographische Daten wurden einmalig erhoben. Hierfür gibt es keine Notwendigkeit der Formatierung vom Long- ins Wide-Format oder vice-versa.
Im Beispieldatensatz airquality gibt es wiederholte Messungen der Untersuchungseinheiten (Ozone
, Solar.R
, Wind
und Temp
) zu unterschiedlichen Zeiten, die in Month
und Day
kodiert werden. Jede dieser Untersuchungseinheiten liegt in mehreren Zeilen vor. Es handelt sich folglich um einen Datensatz im Long-Format. Im Wide-Format hätten wir z.B. die Variablen Ozone_5_1
(Monat 5, Tag 1), Ozone_5_2
(Monat 5, Tag 2), …, Solar.R_5_1
(Monat 5, Tag 1), etc. Je nachdem, wie wir die Daten auswerten wollen, ist es notwendig bzw. nicht notwendig, die Daten umzuformatieren.
Im Kapitel zum (Wide- und Long-Format) erfahren wir, wie wir beide Formate ineinander überführen können. Hierzu werden jeweils zwei Möglichkeiten vorgestellt: reshape()
aus dem Standardpaket stats und spread()
bzw. gather()
aus dem Paket tidyr.
18.2 Datensätze zusammenführen
Synonyme: Mergen, Fusionieren, Integrieren
Nicht immer haben wir das Glück, dass die für uns relevanten Daten in einem gemeinsamen Dataframe vorliegen. Daher schauen wir uns nachfolgend an, wie man Dataframes zusammenführen kann. Es gibt dabei zwei Szenarien, die man unterscheiden kann:
- die selben Variablen von verschiedenen Fällen
- z.B. von einer erneuten Aufnahme von Personen in eine Studie
- hier werden die Zeilen “ergänzt”
- verschiedene Variablen von den selben Fällen
- z.B. wenn einzelne Abschnitte einer Studie (Tests, Fragebögen) in unterschiedlichen Datensätzen gespeichert wurden
- hier werden die Spalten “ergänzt”
Wir schauen uns wieder Funktionen aus zwei verschiedenen Paketen an: merge()
aus dem Basispaket base und bind_rows()
bzw. die _join()
-Funktionen aus dem Zusatzpaket dplyr.
Wir nutzen dafür die Datensätze vornamen_13
und vornamen_14
, in denen die Vornamen der Neugeborenen in München, jeweils für die Jahre 2013 und 2014 enthalten sind. Diese Datensätze eignen sich für beide Szenarien, weil sowohl dieselben Variablen (vorname
, anzahl
und geschlecht
) als auch dieselben Fälle (d.h. Vornamen) in beiden Datensätzen vorkommen (z.B. Maximilian
). Die Untersuchungseinheiten sind hier also nicht einzelne Personen, sondern Vornamen. Was bei der Untersuchung einzelner Personen die ID-Variable ist, ist hier die Variable vorname
.
Achtung: Es kann sein, dass wir einen Dataframe nach dem Zusammenführen noch in ein anderes Format überführen müssen, um unsere Auswertung durchführen zu können (siehe Wide- und Long-Format).
18.2.1 Selbe Variablen, unterschiedliche Fälle
Die beiden nachfolgend vorgestellten Funktionen unterscheiden sich bezüglich einiger Funktionalitäten.
Ein wichtiger Unterschied ist, dass merge()
beim Zusammenführen der Dataframes gleiche Fälle (d.h. Fälle mit gleichen Ausprägungen in den Variablen) nur einmalig übernimmt (d.h. Dopplungen löscht) während bind_rows()
alle Fälle übernimmt, auch wenn sich diese doppeln.
In Abhängigkeit der geplanten Nutzung der Daten sollten wir individuell entscheiden, welche Funktion wir nutzen wollen.
merge()
Mit merge()
können wir zwei Dataframes, deren Namen wir der Funktion übergeben, vertikal zusammenführen.
Wir müssen dabei unbedingt all = TRUE
spezifizieren, weil der Default (all = FALSE
) nur Zeilen behält, die in beiden Dataframes mit genau der gleichen Ausprägung auf den Variablen vorhanden sind (und von diesen jeweils nur eine Version).
Mit all.x = TRUE
bzw. all.y = TRUE
würden wir, neben den Fällen mit den gleichen Ausprägungen in beiden Datensätzen, auch alle nur im ersten bzw. zweiten Datensatz enthaltenen Fälle behalten.
<- merge(vornamen_13, vornamen_14, all = TRUE) vornamen_merge_row
Die Reihenfolge der Zeilen im gemeinsamen Dataframe richtet sich nach der natürlichen Sortierung der ersten Variable (im zuerst übergebenen Datensatz). Für unser Beispiel sind die Vornamen nach dem Alphabet (beginnend mit “A”) sortiert.
Mit der Funktion dim()
können wir überprüfen, wie viele Zeilen und Spalten unser Dataframe beinhaltet.
dim(vornamen_merge_row)
[1] 7496 3
Hier sehen wir den eingangs erwähnten Unterschied von merge()
und bind_rows()
. vornamen_13
beinhaltet 4012 und vornamen_14
4032 Zeilen. Insgesamt würden wir also 8044 erwarten. Die Funktion übernimmt aber bei komplett gleichen Fällen (d.h. gleichen Ausprägungen auf allen Variablen) in den beiden Dataframes nur eine Version (so dass es keine Dopplung gibt).
Beispielsweise kommt folgender Fall in beiden Dataframes vor:
vorname anzahl geschlecht
1310 Aadhya 1 w
vorname anzahl geschlecht
2704 Aadhya 1 w
So verschwindet die gleiche Anzahl an Fällen, die wir mit dem Default-Verhalten der Funktion (all = FALSE
) behalten hätten.
Achtung: Wir sollten mit
merge()
demnach auch nur Dataframes zusammenführen, die genau dasselbe Set an Variablen haben (d.h. ein Dataframe sollte nicht noch eine zusätzliche Variable besitzen) oder wir sollten eine ID-Variable haben, deren IDs nur einmal vorkommen (sowohl innerhalb eines Dataframes als auch zwischen den Dataframes). Sonst kann es zum ungewollten Nicht-Übernehmen von Fällen kommen.
Schauen wir uns das Problem an einem Beispiel an:
Nehmen wir an, dass ein Dataframe x
eine zusätzliche Variable a
hat. Für die Fälle des anderen Dataframe y
würden auf a
im zusammengeführten Objekt nur fehlende Werte (NA
) stehen. So würde die Funktion solche Fälle (im Vergleich der beiden Dataframes), die bis auf die Variable a
die gleichen Ausprägungen haben, nicht übernehmen.
x
und y
haben jeweils drei Fälle. x
hat drei Variablen; y
hat zwei.
x
c b a
1 5 5 5
2 6 6 6
3 7 7 7
y
c b
1 5 7
2 7 5
3 6 6
<- merge(x, y, all = TRUE)
merge_xy merge_xy
c b a
1 5 5 5
2 5 7 NA
3 6 6 6
4 7 5 NA
5 7 7 7
Man könnte denken, dass 6 Fälle in merge_xy
zu finden sind. Weil aber jeweils ein Fall (x
: Zeile 2; y
Zeile 3) bis auf a
die gleichen Ausprägungen in den beiden Dataframes hat, wird dieser nicht mit übernommen.
bind_rows()
Der Funktion bind_rows()
übergeben wir einfach die Dataframes, die wir vertikal aneinander reihen wollen. Die Reihenfolge der übergebenen Dataframes entscheidet dabei auch über die Reihenfolge der Zeilen des zusammengeführten Dataframes (z.B. erst alle Zeilen von vornamen_13
, dann von vornamen_14
).
Optional können wir der Funktion das Argument .id
übergeben, mit dem wir eine ID-Variable erstellen, welche die Datensätze kodiert (beginnend mit 1).
# library(dplyr)
<- bind_rows(vornamen_13, vornamen_14, .id = "id") vornamen_bind
id | vorname | anzahl | geschlecht | |
---|---|---|---|---|
1 | 1 | Maximilian | 166 | m |
4013 | 2 | Maximilian | 178 | m |
Hier sehen wir die ID-Variable. Von den Zeilen 1 bis 4012 ist diese 1
; von den Zeilen 4012 bis 8044 ist sie 2
.
Es ist auch möglich, der Funktion mehr als zwei Dataframes zu übergeben, welche in einem gemeinsamen Dataframe gespeichert werden.
Wir können beliebig viele Dataframes mit bind_rows()
zusammenführen, solange diese mindestens eine gemeinsame Variable haben.
Zur Überprüfung schauen wir uns mit dim()
wieder die Anzahl der Zeilen und Spalten an.
dim(vornamen_bind)
[1] 8044 4
vornamen_13
und vornamen_14
überein, d.h. es wurden alle Fälle übernommen.
18.2.2 Unterschiedliche Variablen, selbe Fälle
Im Gegensatz zu merge()
und bind_rows()
unterscheiden sich merge(..., by)
und die _join()
-Funktionen nicht in ihrer Funktionalität, sondern nur in der Reihenfolge der Fälle im zusammengeführten Dataframe.
Allerdings kann man die _join()
-Funktionen auch alternativ zu bind_rows()
nutzen. Dann unterscheidet sich das Ergebnis im Vergleich zu merge()
auch lediglich in der Reihenfolge der Fälle. Mehr Infos dazu finden wir im Abschnitt [_join()
].
merge(..., by)
Die Funktion merge()
können wir auch nutzen, um Spalten aneinander zu heften.
Mit dem Argument by
geben wir an, welche ID-Variable die (selben) Fälle kodiert.
Wenn es unterschiedliche Benennungen der gleichen ID-Variablen in den beiden Datensätzen gibt, müssen wir by.x
und by.y
nutzen. Die Benennung von by.x
wird dann übernommen.
x
und y
spielen auf die Reihenfolge an, in welcher wir die Datensätze an die Funktion merge()
übergeben. x
ist der zuerst übergebene Datensatz; y
der als zweites übergebene.
Wir wollen auch hier wieder die Daten aus beiden Dataframes übernehmen, und geben das mit all = TRUE
an.
Wir könnten aber hier ebenso all.x = TRUE
bzw. all.y = TRUE
oder all = FALSE
nutzen.
<- merge(vornamen_13, vornamen_14, by = "vorname", all = TRUE) vornamen_merge_col
dim(vornamen_merge_col)
[1] 6371 5
Nun schauen wir uns einmal die (ersten 6 Fälle der) neu erstellten Variablen an.
vorname | anzahl.x | geschlecht.x | anzahl.y | geschlecht.y |
---|---|---|---|---|
+ | NA | NA | 9 | w |
+ | NA | NA | 7 | m |
Aadhavan | NA | NA | 2 | m |
Aadhya | 1 | w | 1 | w |
Aahana | NA | NA | 1 | w |
Aahel | 1 | m | NA | NA |
Wie kommt man auf die Anzahl der Fälle \(N = 6371\)?
In den beiden Dataframes vornamen_13
und vornamen_14
wurden genau die gleichen drei Variablen (vorname
, anzahl
und geschlecht
) erhoben. Wenn wir die beiden zusammenführen, können drei unterschiedliche Szenarien mit Hinblick auf vorname
und geschlecht
auftreten.
Nachfolgend schauen wir uns jeweils ein Beispiel sowie die Anzahl der Fälle dieser Szenarien an.
vorname
undgeschlecht
sind in beiden Dataframes gleich
vorname anzahl.x geschlecht.x anzahl.y geschlecht.y
6 Aahel 1 m NA <NA>
nrow(vornamen_merge_col[which(
$geschlecht.x == vornamen_merge_col$geschlecht.y),]) vornamen_merge_col
[1] 1672
geschlecht
beivorname
unterscheidet sich zwischen den Dataframes
vorname anzahl.x geschlecht.x anzahl.y geschlecht.y
38 Abdullah 7 m 4 m
nrow(vornamen_merge_col[which(
$geschlecht.x != vornamen_merge_col$geschlecht.y),]) vornamen_merge_col
[1] 112
vorname
undgeschlecht
eines Falles sind nur in einem Dataframe enthalten (für die Daten des anderen sindNA
s angegeben)
2,] vornamen_merge_col[
vorname anzahl.x geschlecht.x anzahl.y geschlecht.y
2 + NA <NA> 7 m
colSums(is.na(vornamen_merge_col)) # spaltenweise Missings gezählt
vorname anzahl.x geschlecht.x anzahl.y geschlecht.y
0 2305 2305 2282 2282
Wir können uns hier nur die Missings in den einzelnen Spalten anschauen, um die Häufigkeiten für dieses Szenario zu bekommen, weil es vorher in den einzelnen Dataframes keine Missings gab.
Wenn wir nun alle Werte aufsummieren, kommen wir auf die Anzahl der Zeilen im gemeinsamen Dataframe:
1672 + 112 + 2305 + 2282
[1] 6371
_join()
Die _join()
-Funktionen aus dplyr sind danach differenziert, welche Daten wir aus den Datensätzen übernehmen möchten. Diese Unterscheidung ist analog zu dem Argument all
in merge()
.
Für Daten aus beiden Datensätzen nutzt man full_join()
. Analog zu all = TRUE
in merge()
.
Für Daten aus dem ersten bzw. zweiten Datensatz und den überlappenden Fällen nutzt man left_join()
bzw. right_join()
. Analog zu all.x = TRUE
bzw. all.y = TRUE
in merge()
.
Für Daten, die in beiden Datensätzen überlappen nutzt man inner_join()
. Analog zu all = FALSE
in merge()
.
# library(dplyr)
<- full_join(vornamen_13, vornamen_14, by="vorname") vornamen_join
Warning in full_join(vornamen_13, vornamen_14, by = "vorname"): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 3 of `x` matches multiple rows in `y`.
ℹ Row 1292 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
"many-to-many"` to silence this warning.
Nun überprüfen wir wieder die Dimensionen des neu erstellten Dataframes.
dim(vornamen_join)
[1] 6371 5
Wir sehen, dass der mit full_join(..., by = "vorname")
zusammengeführte Datensatz genau die gleichen Dimensionen hat wie der mit merge(..., by = "vorname", all = TRUE)
zusammengeführte. Die beiden Funktionen unterscheiden sich nur in der Sortierung der Fälle (welcher Dataframe zuerst eingegeben wurde vs. natürliche Sortierung der Fälle).
full_join()
als Alternative zu bind_rows()
Mit full_join(x, y)
bekommen wir (bis auf die Sortierung der Fälle) das gleiche Ergebnis wie bei merge(x, y, all = TRUE)
<- full_join(vornamen_13, vornamen_14) vornamen_join_row
Joining with `by = join_by(vorname, anzahl, geschlecht)`
Zur Demonstration der Übereinstimmung schauen wir uns die Dimensionen und den Aufbau des Dataframes (am Beispiel der ersten 6 Zeilen) an.
dim(vornamen_join_row)
[1] 7496 3
vorname | anzahl | geschlecht |
---|---|---|
Maximilian | 166 | m |
Felix | 124 | m |
Anna | 109 | w |
David | 109 | m |
Sophia | 108 | w |
Emilia | 103 | w |
dim(vornamen_merge_row)
[1] 7496 3
vorname | anzahl | geschlecht |
---|---|---|
+ | 7 | m |
+ | 9 | w |
Aadhavan | 2 | m |
Aadhya | 1 | w |
Aahana | 1 | w |
Aahel | 1 | m |
18.3 Daten extrahieren
Synonyme: Splitten, Subsetten, Filtern, Selektieren, Extrahieren
Manchmal möchten wir nur bestimmte Variablen bzw. bestimmte Fälle aus einem Datensatz betrachten. Generell bietet es sich an, dafür reguläre Ausdrücke (regular expressions z.B. die Metacharactere .*
, |
, ^
und $
) und logische Operatoren (logical operators z.B. >
, <
und ==
) zu nutzen.
Wie wir Variablen (Spalten) und Fälle (Zeilen) selektieren und in einem neuen Dataframe speichern können, schauen wir uns nun an.
18.3.1 Variablen
Wenn wir nur einige Variablen aus einem bzw. aus mehreren Datensätzen benötigen, können wir diese mit verschiedenen Möglichkeiten entnehmen. Im Folgenden schauen wir uns dafür Möglichkeiten aus dem Standardpacket base und dem Zusatzpaket dplyr an.
Unten befindet sich eine Übersicht, der wir entnehmen können, welche Methode wir wählen sollten in Abhängigkeit davon, ob die Variablen, die wir extrahieren wollen, ähnlich oder unterschiedlich sind.
... sich ähnlich | ... unterschiedlich |
---|---|
`grep()` | `$`, `select()` |
z.B. enthalten den Buchstaben 'o': `Ozone`, `Solar.R`, `Month` | |z.B. `Month` und `Day` |
grep()
Wenn Variablen eines Datensatzes eine Gemeinsamkeit (z.B. einen gemeinsamen Wortstamm) aufweisen, können wir diese mit der Funktion grep()
extrahieren.
grep(pattern, names(Datensatz))
Die Funktion durchsucht die Namen der Variablen eines Dataframes - names(Datensatz)
- nach bestimmten Zahlen- oder Zeichenketten (pattern
). Diese müssen wir in " "
angeben (weil Variablennamen als Character gespeichert werden).
Wir wollen beispielshalber alle Variablen extrahieren, die irgendwo ein o im Namen haben.
# Selektion der Namen
<- grep(pattern="o", names(airquality))
var_mit_o
# Anwenden der Selektion auf den Dataframe
<- airquality[var_mit_o] df_var_mit_o
Mit dieser Methode haben wir gleich den Vorteil, dass die Namen der Variablen im neuen Datensatz gleich denen im ursprünglichen Datensatz ist.
Wir können unsere Suche mitgrep()
auch noch spezifischer machen, indem wir die regulären Operatoren nutzen. Mit ^o
suchen wir Variablen, die mit einem “o” beginnen; mit o$
jene die mit “o” enden. Mit ^o|o$
suchen wir Variablen, die entweder mit einem “o” beginnen oder enden. Mit ^o.*o$
suchen wir Variablen, die mit einem “o” beginnen und enden.
$
Einzelne Variablen, die keine Gemeinsamkeit (z.B. einen gemeinsamen Wortstamm) aufweisen, kann man mit dem $
-Operator extrahieren.
Diesen wendet man an, indem man die Form Data Frame$Variable
nutzt. Die Variablen können folglich aus unterschiedlichen Datensätzen stammen, da wir jede Variable jeweils neu ansprechen müssen.
Wir entnehmen die Variablen Wind
und Ozone
und speichern diese in einem neuen Dataframe.
<- data.frame(airquality$Wind, airquality$Ozone) df_wind_ozone
Mit dieser Methode haben wir den Nachteil, dass die Variablen im neu erstellten Dataframe nicht mit ihrem ursprünglichen Namen, sondern in der Form Datensatz.Variable
benannt sind.
colnames(Datensatz) <- c("Var1", "Var2", ...)
wieder ihren ursprünglichen (oder einen neuen) Namen geben.
select()
Die Funktion select()
kann unterschiedliche Variablen aus dem selben Dataframe extrahieren. Sie ist dabei kompakter zu handhaben als die Extraktion mit $
.
Man übergibt der Funktion zuerst den Dataframe und anschließend die Namen der Variablen, welche man extrahieren möchte. Man kann diese sogar gleich umbenennen.
Wir erstellen einen neuen Dataframe mit den Variablen Month
und Day
. Die Variable Month
werden wir zu Mon
umbenennen.
# library(dplyr)
<- select(airquality,
df_month_day Mon = Month, # neuer Name = alter Name
Day)
Wenn wir bis auf einige wenige Variablen alle übernehmen wollen, können wir das realisieren, indem wir jeweils ein -
vor die ungewollten Variablennamen setzen. Wenn der ersten Variable, die wir select()
übergeben, ein -
vorangestellt wurde, übernimmt die Funktion alle Variablen mit Ausnahme jener, die mit -
angegeben werden.
Schauen wir uns das für den Fall an, dass wir Month
und Day
aus dem Dataframe entfernen wollen.
# library(dplyr)
<- select(airquality,
df_without_month_day -Month,
-Day)
18.3.1.1 Datums-Variablen splitten
Für den Fall, dass wir eine Datums-Variable in unserem Datensatz haben, welche in einem für uns unangemessenen Format vorliegt, können wir diese mit dem Paket lubridate umformatieren. Auf dieser Seite wird der Umgang mit den im Paket enthaltenen Funktionen ymd()
und mdy()
erklärt.
18.3.2 Fälle
Wir schauen uns nachfolgend einige Möglichkeiten der Extraktion von Fällen mit spezifischen Ausprägungen (die man z.B. für eine Subgruppenanalyse benötigt) an. Auch hier schauen wir uns wieder sowohl Funktionen aus dem Standardpaket base als auch aus dem Zusatzpaket dplyr an.
Unten befindet sich eine Übersicht, der wir entnehmen können, welche Methode wir wählen sollten in Abhängigkeit davon, ob die Fälle, die wir extrahieren wollen, ähnlich oder unterschiedlich sind.
... die selben Zeichen | ... einen gemeinsamen Wertebereich |
---|---|
`grep()` | logische Operatoren, `filter()` |
z.B. enthalten die Zahl 6: 67, 86, ... | z.B. genau 57 oder >= 15 |
grep()
Wenn wir Ausprägungen suchen, die sich nicht durch logische Operatoren, sondern durch Ähnlichkeiten (z.B. ein gleiches Zeichen) filtern lassen, dann können wir dafür grep()
nutzen.
grep(pattern, x, value)
Die Funktion durchsucht Elemente eines Vektor (x
) nach bestimmten Zahlen- oder Zeichenketten (pattern
). Mit grep()
werden in Abhängigkeit des Arguments value
entweder Indizes (FALSE
; voreingestellt), oder konkrete Werte (TRUE
) ausgegeben. Die Werte schauen wir uns zur Überprüfung an; die Indizes benötigen wir zur Extraktion jener Fälle aus dem Datensatz.
Unsere gesuchten Zahlen- oder Zeichenketten, die wir an das Argument pattern
übergeben, sowie die ausgegeben Werte, werden immer als Character behandelt und von daher in " "
ausgegeben.
Wenn eine Ausprägung irgendwo eine bestimmten Zahlen- oder Zeichenketten enthalten soll, geben wir diese einfach ein.
Wir durchsuchen die Variable Temp
nach den Tagen, an denen eine 6 im Messwert war.
grep("6", airquality$Temp) # Indizes
[1] 1 4 5 6 7 9 10 12 13 14 16 17 19 20 23 24 28 31 34
[20] 49 51 53 54 55 85 88 90 96 103 104 110 118 122 135 140 141 142 144
[39] 147 148 152 153
grep("6", airquality$Temp, value=TRUE) # Werte
[1] "67" "62" "56" "66" "65" "61" "69" "69" "66" "68" "64" "66" "68" "62" "61"
[16] "61" "67" "76" "67" "65" "76" "76" "76" "76" "86" "86" "86" "86" "86" "86"
[31] "76" "86" "96" "76" "67" "76" "68" "64" "69" "63" "76" "68"
Wenn eine Ausprägung eine bestimmte Zahlen- oder Zeichenketten zu Beginn enthalten soll, setzen wir ein ^
vor diese.
Wenn wir beispielsweise alle Tage suchen, an denen die Temperatur (Temp
) im Bereich 60-69°F, dann können wir das folgendermaßen tun:
Diese Suche könnten wir auch mit den logischen Operatoren durchführen.
grep("^6", airquality$Temp) # Indizes
[1] 1 4 6 7 9 10 12 13 14 16 17 19 20 23 24 28 34 49 140
[20] 142 144 147 148 153
grep("^6", airquality$Temp, value=TRUE) # Werte
[1] "67" "62" "66" "65" "61" "69" "69" "66" "68" "64" "66" "68" "62" "61" "61"
[16] "67" "67" "65" "67" "68" "64" "69" "63" "68"
Wenn eine Ausprägung eine bestimmte Zahlen- oder Zeichenketten am Ende enthalten soll, setzen wir ein $
ans Ende.
Wenn wir beispielsweise alle Tage suchen, an denen die Temperaturangabe (Temp
) mit einer 6 endet, dann können wir das folgendermaßen tun:
grep("6$", airquality$Temp) # Indizes
[1] 5 6 13 17 31 51 53 54 55 85 88 90 96 103 104 110 118 122 135
[20] 141 152
grep("6$", airquality$Temp, value=TRUE) # Werte
[1] "56" "66" "66" "66" "76" "76" "76" "76" "76" "86" "86" "86" "86" "86" "86"
[16] "76" "86" "96" "76" "76" "76"
Wie beim Extrahieren von Variablen können wir auch hier mit grep()
verschiedene Bestandteile einer Ausprägung anhand des logischen Operators |
suchen.
Als Beispiel suchen wir Temperaturangaben, die zu Beginn eine 5 enthalten oder mit einer 5 enden.
grep("^5|5$", airquality$Temp) # Indizes
[1] 5 7 8 15 18 21 25 26 27 36 49 56 63 81 86 97 115 132 151
grep("^5|5$", airquality$Temp, value=TRUE) # Werte
[1] "56" "65" "59" "58" "57" "59" "57" "58" "57" "85" "65" "75" "85" "85" "85"
[16] "85" "75" "75" "75"
Achtung: Die Zahlen bzw. Zeichenketten dürfen nicht durch Freizeichen getrennt werden, z.B. würden mit
"6| ^7"
nur Temperaturangaben gefiltert werden, die eine 6 enthalten.
Die Suche mit "^x|x$"
ergibt gemeinsam die globale Suche nach "x"
.
Wenn wir hingegen mehrere Bedingungen verknüpfen wollen, z.B. "^x"
und "x\$"
, dann nutzen wir .*
, z.B. "^x.x\$"
(für ein Beispiel siehe [mutate()
: Zusammenfassung aller Fälle]).
Ähnlich zum Abschnitt zu Variablen mit grep()
extrahieren wenden wir die Selektion mit der Form Datensatz[grep(),]
an, und speichern diese in einem neuen Objekt.
Wir können hierfür nur den Indizes-Vektor (value=FALSE
; Default) nutzen.
<- airquality[grep("^5|5$", airquality$Temp),] df_temp5
Logische Operatoren
Wenn wir logische Operatoren auf einzelne Variablen anwenden, können wir Fälle mit bestimmten Ausprägungen filtern.
Hier finden wir eine Einführung zu logischen Operatoren mit Übungsfragen.
Um nur diese Fälle im gesamten Datensatz zu extrahieren, nutzen wir folgende Syntax:
Datensatz[Variable Operator Ausprägung,]
Wenn wir Fälle (d.h. Zeilen) exrahieren wollen, müssen wir nach den Indizes immer ein Komma angeben z.B. extrahiert df[2,]
die zweite Zeile aus df
. Das kommt daher, dass in einem zweidimensionalen Objekt immer erst die Zeilen und dann die Variablen angegeben werden z.B. sehen wir das auch bei der Reihenfolge der Dimensionen unserer Objekte im Environment (bei Data).
Beispielsweise können wir mit dem Gleichheits-Operator ==
nach exakt einer Ausprägung in einer Variablen suchen.
Wir filtern die Variable Temp
(Temperatur in Grad Fahrenheit) nach Fällen mit der Ausprägung 57
.
<- airquality[airquality$Temp == 57,] df_temp57
Wir können mittels |
(oder) auch mehrere Ausprägungen gleichzeitig auswählen.
Nun wollen wir zusätzlich zu nach Fällen mit der Ausprägung 57
auch jene mit der Ausprägung 66
extrahieren.
<- airquality[airquality$Temp == 57 | airquality$Temp == 66,] df_temp57_66
!=
(nicht), <
(kleiner) und >=
(größer gleich).
filter()
Mit filter()
können wir verschiedene Variablen nach bestimmten Kriterien filtern. Dabei greifen wir wieder auf die logischen Operatoren zurück.
Man übergibt der Funktion zuerst den Dataframe und anschließend die Namen der Variablen mit den Bedingungen, die auf diese jeweils zutreffen sollen.
Wir wollen nur jene Fälle, die in der zweiten Hälfte des Junis erhoben wurden.
<- dplyr::filter(airquality, Month == 6, Day >= 15) df_month6_day15ff
Zu Beginn haben wir erläutert, warum wir manchmal ::
nutzen sollten.
18.4 Daten sortieren
Für manche Vorhaben, wie z.B. grafische Darstellungen oder dem Quantifizieren von Heteroskedastizität, benötigt man sortierte Daten.
Wir schauen uns nachfolgend zwei Möglichkeiten an, einen Dataframe nach den Ausprägungen seiner Variablen zu sortieren.
order()
Nachfolgend sehen wir, wie man mit order()
aufsteigend sortiert.
Weil mit order()
nur Zeilenindizes ausgegeben werden, müssen wir diese noch auf den Dataframe anwenden. Das machen wir mit der Form Datensatz[order(Variable),]
.
<- airquality[order(airquality$Temp),] df_ascend_temp
Wenn wir Temp
-Werte mit der gleichen Ausprägung zusätzlich noch nach der (aufsteigenden) Variable Wind
sortieren wollen, können wir diese einfach ergänzen.
<- airquality[order(airquality$Temp, airquality$Wind),] df_ascend_temp_wind
Beispielsweise sehen wir hier, dass die Fälle 27, 25 und 18 (auf Seite 1 in den Zeilen 2, 3 und 4) anders sortiert sind als oben.
Wenn wir nach den absteigenden Werte der Variablen sortieren wollen, hängen wir jeweils ein -
vor diese. Alternativ können wir auch das Argument decreasing=TRUE
setzen (dann werden aber, im Gegensatz zu unserem Beispiel, alle Variablen absteigend sortiert).
<- airquality[order(-airquality$Temp, airquality$Wind),] df_descend_temp_wind_1
arrange()
Die Funktion arrange()
aus dem Paket dplyr hat ein sehr ähnliches Prinzip wie order()
. Sie ist dabei in der Handhabung übersichtlicher, weil man der Funktion den Namen des Dataframes einmalig übergibt und nachfolgend nur noch die Variablen angeben muss, nach denen sortiert werden soll. Außerdem muss man den Output nicht zusätzlich auf den Dataframe anwenden, weil arrange()
das ohnehin macht.
Standardmäßig wird hier ebenso wie bei order()
aufsteigend sortiert solange man das nicht mit dem Voranstellen eines -
ändert.
Schauen wir uns das für das letzte Beispiel im vorhergehenden Abschnitt an. Wir sortieren den Dataframe absteigend nach Temp
und gleiche Werte aufsteigend nach Wind
.
<- arrange(airquality, -Temp, Wind) df_ascend_temp_wind_2
18.5 Kodierung ändern
Wenn die Daten nicht in einer angemessenen Kodierung vorliegen, muss man diese nachträglich anpassen bzw. neu erstellen. Die Kodierung einer Variablen ist von ihrem Messniveau abhängig.
Für dieses Kapitel beschränken wir uns auf die Nutzung des Datensatzes PWE_data, welchen wir zu Beginn heruntergeladen haben.
18.5.1 Umkodieren
Wenn wir zum Beispiel Messwerte in Meter zu Messwerten in Zentimeter ändern oder negativ gepolte Items umpolen wollen, spricht man von Umkodieren.
Wir können die Kodierung von Merkmalen in R auf verschiedene Arten ändern. Nachfolgend schauen wir uns zwei Funktionen an, die wir nutzen können.
recode()
In der Funktion recode()
(auch Recode()
möglich) aus dem Paket car müssen wir grundsätzlich zwei Argumente spezifizieren: var
und recodes
. Ersterem übergeben wir die umzukodierende Variable, zweiterem die alte und die neue Kodierung der Variablen.
Wir müssen hierbei einige syntaktische Besonderheiten von recodes
beachten:
recodes = "alt_1 = neu_k; alt_2 = neu_k-1; ...; alt_k = neu_1"
(Ausprägungen der Kodierung von 1 bis \(k\))
- die
Input=Output
-Parameter müssen gemeinsam als Zeichenkette (d.h. in" "
) vorliegen - die verschiedenen
Input=Output
-Parameter müssen mit Semikolon (;
) getrennt werden
Wir invertieren im Folgenden die Werte der Variablen Q9A
, Q13A
und Q15A
(numeric). Die Information, dass die Variablen negativ kodiert sind, finden wir in Mirels & Garrett (1971) (nur über HU-VPN zugänglich). Informationen zur Messskala finden wir auch im Codebuch.
So sehen die Daten (der ersten 10 Personen) bisher aus:
Jetzt invertieren wir die Skalen:
library(car)
$Q9A <- recode(var=PWE_data$Q9A,
PWE_datarecodes="1=5; 2=4; 3=3; 4=2; 5=1")
$Q13A <- recode(var=PWE_data$Q13A,
PWE_datarecodes="1=5; 2=4; 3=3; 4=2; 5=1")
$Q15A <- recode(var=PWE_data$Q15A,
PWE_datarecodes="1=5; 2=4; 3=3; 4=2; 5=1")
Abschließend überprüfen wir (visuell), ob die Umkodierung geklappt hat:
Wenn die Kodierung aus Zeichenketten (character) besteht, müssen wir diese jeweils noch mit ' '
umschließen.
Schauen wir uns an, wie man die Ausprägungen von eduaction
(1,
2,
3,
4) zu den Beschreibungen (
Less than High School,
High School,
University Degree,
Graduate Degree`) ändert.
Mit dem Parameter as.factor
legen wir fest, ob die (neue) rekodierte Variable als Faktor gespeichert werden soll.
Achtung: Leider können wir so aber nur ungeordnete (nominalskaliert) und keine geordneten (ordinalskaliert) Faktoren erstellen. Dafür müssten wir auf die Funktion
factor(..., ordered = TRUE, levels)
zurückgreifen (siehe Abschnitt Faktorisieren).
# aus Gründen der Darstellung speichern wir die Kodierungen zuerst in einem String:
<- c("1='Less than High School';2='High School';3='University Degree';4='Graduate Degree'")
recode $education_new <- recode(var=PWE_data$education,
PWE_dataas.factor=TRUE,
recodes=recode)
Abschließend vergleichen wir die Daten (der ersten 10 Personen) von education
und education_new
:
case_when()
Mit der Funktion case_when()
aus dem Paket dplyr können wir für verschiedene Fälle (d.h. Bedingungen) der ursprünglichen Variable angeben, wie diese umkodiert werden soll. Auf die linke Seite schreiben wir eine logische Bedingung (z.B. größer als mit >
); auf die rechte Seite die neue Kodierung. Verbunden werden beide mit einer Tilde (~
).
Im Gegensatz zu recode()
können wir durch die Verwendung von logischen Operatoren (z.B. >
) ganzen Zahlenintervallen dieselbe Kodierung zuweisen.
Als Beispiel bilden wir Kategorien für das intervallskalierte Merkmal Q1E
(mit Frage Q1 verbrachte Zeit in Millisekunden). Den Range des Merkmals erfahren wir mit range(PWE_data$Q1E, na.rm=TRUE)
(195 - 181.246). Wir teilen Q1E
in vier gleich breite Kategorien ein: \([195, 45457.75), [45457.75, 90720.5), [90720.5, 135983.2), [135983.2, 181247)\).
Hinweis: [ heißt inklusive, ) heißt exklusive. Bei der oberen Grenze wird aufgerundet.
# library(dplyr)
$Q1E_kat <- case_when(
PWE_data$Q1E < 45457.75 ~ 1, # kleiner damit exklusiv
PWE_data$Q1E < 90720.5 ~ 2,
PWE_data$Q1E < 135983.2 ~ 3,
PWE_data$Q1E < 181247 ~ 4)
PWE_data
str(PWE_data$Q1E_kat) # Überprüfung Datentyp
num [1:1350] 1 1 1 1 1 1 1 1 1 1 ...
unique(PWE_data$Q1E_kat) # um alle Kategorien zu sehen
[1] 1 2 4 3 NA
Nun haben wir eine neue Variable Q1E_kat
erstellt, welche zusammengefasste Informationen aus Q1E
enthält. Die neu erstellte Variable liegt als numeric vor, d.h., dass wir diese noch faktorisieren und ordnen müssen, damit sie als ordinalskaliert gehandhabt wird.
$Q1E_kat <- factor(PWE_data$Q1E_kat, ordered=TRUE)
PWE_data
str(PWE_data$Q1E_kat) # Überprüfung Datentyp
Ord.factor w/ 4 levels "1"<"2"<"3"<"4": 1 1 1 1 1 1 1 1 1 1 ...
18.5.2 Indikatorvariablen: Kodierung nominaler Merkmale
Nominale Merkmale kann man in Form von Dummy-, Effekt- und Kontrastkodierungen repräsentieren. Eine solche Repräsentation ist vor allem im Rahmen des Allgemeinem Linearen Modells (ALM) von Interesse.
Untenstehende Tabelle gibt einen groben Überblick über die Interpretation der Parameter des ALM in Abhängigkeit der Kodierung.
Intercept b<sub>0</sub> | Steigung b<sub>j</sub> | mögliche Anwendung | |
---|---|---|---|
<b>Dummy</b> | Mittelwert in der Referenzgruppe | Differenz des Mittelwerts der j-ten Gruppe zur Referenzgruppe | Vergleich von Experimental- und Kontrollgruppe |
<b>Effekt</b> | Mittelwert der Gruppenmittelwerte (Gesamtmittelwert) | Differenz des Mittelwertes der j-ten Gruppe zum Gesamtmittelwert | Vergleich von Gruppen in varianzanalytischen Designs |
<b>Kontrast</b> | Mittelwert über die Mittelwerte der Kontrastgruppen | lässt sich als Funktion der Kontrastkoeffizienten darstellen, die den jeweiligen Kontrast kodieren | |Gezielte Einzelvergleiche von (Kombinationen) von Gruppen |
Die Interpretation der Mittelwerte und Differenzen hängt zusätzlich davon ab, ob ein balanciertes oder unbalanciertes Design vorliegt (d.h. ob die Gruppengrößen gleich oder ungleich sind). |
Für mehr Informationen zu Indikatorvariablen können wir z.B. folgende Quelle nutzen:
Bortz, J., & Schuster, C. (2010). Allgemeines lineares Modell. In J. Bortz, & C. Schuster (Eds.), Statistik für Sozialwissenschaftler (S.363-384). Heidelberg: Springer
(für HU-Studierende über ub.hu-berlin.de zugänglich)
Im Folgenden schauen wir uns an, wie man konkret bei der Erstellung der verschiedenen Arten der Indikatorvariablen vorgehen kann.
Dafür nutzen wir die im Abschnitt Faktorisieren erstellte Variable gender_uf
aus PWE_data_fac.
Zusätzlich rechnen wir jeweils eine einfache lineare Regression mit den verschiedenen Kodierungen, um die Unterschiede zwischen den Koderierungsarten zu veranschaulichen.
Wir regredieren dafür die Zeit in Sekunden, die die Probanden auf der Instruktionsseite verbracht haben (introelapse
), auf ihr Geschlecht (gender_uf
).
Achtung: Die nachfolgend vorgestellten Funktionen lassen sich auch auf ordinalskalierte Merkmale (d.h. sortierte Faktoren) anwenden. Bei diesen unterscheidet sich aber die Interpretation der geschätzten Koeffizienten der Regression. Wir bekommen Schätzungen für lineare (L), quadratische (Q) und kubische (C) Trends. Daher behandeln wir im folgenden Abschnitt nur die Kodierung von nominalskalierten Merkmalen.
Dummy-Kodierung
Viele Funktionen in R (z.B. lm()
, lme()
und lmer()
) kodieren nominalskalierte Variablen intern automatisch nach der Dummy-Kodierung um, wenn diese vorher als Faktoren deklariert wurden. Dabei wird die erste Kategorie als Referenzkategorie genutzt. Wenn wir eine andere Referenzkategorie haben wollen, können wir dafür die im Folgenden vorgestellten Funktionen (C(..., contr.treatment(n, base))
oder relevel()
) nutzen.
Man benötigt für eine Dummykodierung mit \(k\)-Kategorien \(k-1\) Indikatorvariablen. Die jeweils interessierende Gruppe wird in der jeweiligen Indikatorvariablen mit 1 kodiert; die anderen mit 0. Als Referenzkategorie (von der die Abweichung berechnet wird) gilt jene, welche in allen Indikatorvariablen mit 0 kodiert wird.
Die Indikatorvariablen erstellt uns die Funktion contr.treatment()
automatisch, wenn wir die Anzahl der Faktorstufen (n
) und die Referenz (base
) angeben.
Um die faktorisierte Variable gender_uf
, welche 3 Ausprägungen hat, zu kodieren, benötigen wir 2 Dummy-Variablen. Wir wählen die erste Kategorie (1
= Male) als Referenzkategorie.
contr.treatment(n=3, base=1)
2 3
1 0 0
2 1 0
3 0 1
C(Faktor, contr.treatment(n, base))
setzt die Konstraste für den kategorialen Prädiktor innerhalb von lm()
:
<- lm(introelapse ~ C(gender_uf, contr.treatment(3, 1)), PWE_data)
lm_ctsummary(lm_ct)
Call:
lm(formula = introelapse ~ C(gender_uf, contr.treatment(3, 1)),
data = PWE_data)
Residuals:
Min 1Q Median 3Q Max
-2248 -2235 -781 -764 961254
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2249 1038 2.166 0.0305 *
C(gender_uf, contr.treatment(3, 1))2 -1466 1496 -0.980 0.3274
C(gender_uf, contr.treatment(3, 1))3 -1847 4339 -0.426 0.6703
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 26970 on 1340 degrees of freedom
(7 observations deleted due to missingness)
Multiple R-squared: 0.0007689, Adjusted R-squared: -0.0007224
F-statistic: 0.5156 on 2 and 1340 DF, p-value: 0.5973
Nun vergleichen wir das Regressionsmodell mit den Dummy-kodierten Indikatorariablen (C(gender_uf, contr.treatment(n=4, base=1)
) mit dem Regressionsmodell mit dem unsortierten Faktor (gender_uf
), bei dem ebenfalls die erste Kategorie als Referenzkategorie genutzt wird.
<- lm(introelapse ~ gender_uf, PWE_data)
lm_uf summary(lm_uf)
Call:
lm(formula = introelapse ~ gender_uf, data = PWE_data)
Residuals:
Min 1Q Median 3Q Max
-2248 -2235 -781 -764 961254
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2249 1038 2.166 0.0305 *
gender_uf2 -1466 1496 -0.980 0.3274
gender_uf3 -1847 4339 -0.426 0.6703
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 26970 on 1340 degrees of freedom
(7 observations deleted due to missingness)
Multiple R-squared: 0.0007689, Adjusted R-squared: -0.0007224
F-statistic: 0.5156 on 2 and 1340 DF, p-value: 0.5973
Da die Referenzkategorie identisch ist, sehen wir, dass wir bei beiden die gleichen Ergebnisse erhalten.
Bei der Dummykodierung entspricht unser Interzept \(b_0\) dem ungewichteten Mittelwert in der Referenzkategorie (1
= Male). Die partiellen Steigungsgewichte \(b_j\) (C(...)2
und C(...)3
bzw. gender_uf2
und gender_uf3
) entsprechen den ungewichteten Mittelwertsunterschieden zwischen der jeweiligen Gruppe (2
= Female bzw. 3
= Other) und der Referenzgruppe (1
= Male).
Alternativ zu C(..., contr.treatment())
können wir mit der Funktion relevel()
die Referenzkategorie eines unsortierten Faktors ändern. Standardmäßig ist immer die erste Gruppe nach der natürlichen Reihenfolge (bei Zahlen aufsteigend und bei Buchstaben alphabetisch) die Referenzkategorie.
Dem Argument ref
übergeben wir die derzeitige Position der gewünschten Referenzkategorie.
# Beispiel: Female als Referenzkategorie
$gender_uf_ref <- relevel(PWE_data$gender_uf, ref = 2)
PWE_data
ls.str(PWE_data[,c(103, 106)]) # zum Überprüfen
education_new : Factor w/ 4 levels "Graduate Degree",..: 4 2 2 2 2 1 1 2 3 2 ...
gender_uf : Factor w/ 3 levels "1","2","3": 1 2 2 2 2 2 1 3 2 1 ...
Effektkodierung
Für diese Kodierung benötigen wir ebenfalls \(k-1\) Indikatorvariablen. Die jeweils zutreffende Gruppe wird in der jeweiligen Indikatorvariablen mit 1 kodiert; die nicht zutreffende mit 0 (ebenso wie bei der Dummy-Kodierung). Die “Referenzkategorie” (in unserem Beispiel No
) wird in allen Indikatorvariablen mit -1 kodiert.
Es gibt eigentlich keine echte Referenzkategorie (wie bei der Dummy-Kodierung). Vielmehr entspricht der Interzept dem Mittelwert über alle Gruppen hinweg.
Analog zu contr.treatment(n, ...)
bei der Dummy-Kodierung können wir contr.sum(n)
nutzen, um eine effektkodierte Matrix eines Faktors zu erstellen.
contr.sum(n=3)
[,1] [,2]
1 1 0
2 0 1
3 -1 -1
Wir müssen lediglich in n
spezifizieren, wie viele Faktorstufen es gibt. Auch hier übergeben wir den Output der Funktion an C()
, wenn wir z.B. eine lineare Regression mit lm()
berechnen wollen.
<- lm(introelapse ~ C(gender_uf, contr.sum(3)), PWE_data)
lm_cs summary(lm_cs)
Call:
lm(formula = introelapse ~ C(gender_uf, contr.sum(3)), data = PWE_data)
Residuals:
Min 1Q Median 3Q Max
-2248 -2235 -781 -764 961254
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1144.4 1490.2 0.768 0.443
C(gender_uf, contr.sum(3))1 1104.5 1606.2 0.688 0.492
C(gender_uf, contr.sum(3))2 -361.5 1614.8 -0.224 0.823
Residual standard error: 26970 on 1340 degrees of freedom
(7 observations deleted due to missingness)
Multiple R-squared: 0.0007689, Adjusted R-squared: -0.0007224
F-statistic: 0.5156 on 2 and 1340 DF, p-value: 0.5973
Bei der Effektkodierung entspricht unser Interzept \(b_0\) dem Mittelwert der ungewichteten Gruppenmittelwerte. Die partiellen Steigungsgewichte \(b_j\) (C(...)1
und C(...)2
) entsprechen der Differenz des Mittelwerts der jeweiligen Gruppe (1
= Male bzw. 2
= Female) zum Mittelwert der ungewichteten Gruppenmittelwerte.
Leider können wir mit contr.sum()
nur die erste Faktorstufe als “Referenzkategorie” nutzen. Mit ref()
können wir jedoch wieder die Sortierung der Faktorstufen ändern und den umsortierten Faktor dann wieder an C(..., contr.sum(n))
übergeben.
# Beispiel: Female als Referenzkategorie
$gender_uf_ref <- relevel(PWE_data$gender_uf, ref = 2)
PWE_data
ls.str(PWE_data[,c(103, 106)]) # zum Überprüfen
Konstrastkodierung
Bei der Kontrastkodierung können wir uns eigens gewählte Kontraste zwischen verschiedenen Gruppen anschauen. Die Gewichte \(c_i\) eines Kontrastes müssen der Bedingung genügen, dass die Summe der Gewichte über die Anzahl der zu kodierenden Kategorien \(i\) null ist, d.h. \(\sum\limits_{i} c_i = 0\).
Die jeweilige Kodierung mit 0 in einer Indikatorvariablen sorgt dafür, dass eine Gruppe bzw. ein Fall nicht mit in einen Kontrast eingeht.
Bei multiplen Kontrasten (d.h. mindestens zwei kontrastkodierten Variablen) können wir orthogonale (d.h. unkorrelierte) und nicht orthogonalen (d.h. korrelierte) Kontraste unterscheiden.
Zwei Kontraste \(j\) und \(j'\) sind orthogonal wenn zusätzlich zur oberen Bedingung gilt: \(\sum\limits_{i} \, c_{ij} \cdot c_{ij'} = 0\)
Im Folgenden werden wir nur einen Kontrast erstellen. Für mehr Informationen zur Orthogonalität von multiplen Kontrasten können wir z.B. bei Bortz & Schuster (2010) nachschauen.
Wir kontrastieren Männer und Frauen hinsichtlicher der verbrachten Zeit auf der Instruktionsseite (introelapse
).
Dafür sortieren wir den Datensatz PWE_data_fac zuerst mit order()
nach gender_uf
. Die Variable hat eine Zahlen-Kodierung: Männer sind gender_uf = 1
, Frauen gender_uf = 2
und Andere gender_uf = 3
.
<- PWE_data[order(PWE_data$gender_uf),]
PWE_data # natürliche (aufsteigende) Sortierung: 1, 2, 3, NA
Anschließend erstellen wir mit rep()
die kontrastkodierte Variable.
Alternativ könnten wir auch recode()
oder case_when()
nutzen (siehe Umkodieren).
# Anzahl der Fälle in den einzelnen Ausprägungen in Erfahrung bringen:
table(PWE_data$gender_uf, useNA = 'ifany')
1 2 3 <NA>
675 627 41 7
# Kontrast erstellen
$gender_kontrast <- c(rep(1/675, 675), # Male (1)
PWE_datarep(-1/627, 627), # Female (2)
rep(0, 48)) # Other (3) & NA; nicht von Interesse
rep(Gewichtung, Gruppengröße)
: Die jeweilige Gewichtung einer Gruppe (bzw. Kombination von Gruppen) richtet sich nach ihrer Größe.
Wie genau funktioniert rep()
?
Für die manuelle Erstellung von Indikatorvariablen kann man die Funktion rep()
nutzen, welche die ihr übergebenen Zahlen bzw. Zahlenfolgen (oder auch Zeichen bzw. Zeichenketten) beliebig häufg wiederholt.
Schauen wir uns die Funktionsweise der Funktion an einigen Beispielen an.
Die Zahl 1 wird 10 mal (times
) wiederholt:
rep(1, 10) # das gleiche wie: rep(x=1, times=10)
[1] 1 1 1 1 1 1 1 1 1 1
Die Zahlenfolge 0, 1
wird 10 mal (times
) wiederholt:
rep(0:1, 10)
[1] 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
Wenn wir erst 10 mal die 0
und anschließend 10 mal die 1
haben wollen, nutzen wir das Argument each
:
rep(0:1, each=10) # das gleiche wie c(rep(0, 10), rep(1, 10))
[1] 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1
Die Gewichte einer kontrastierten Gruppe (hier: jeweils Männer bzw. Frauen) werden so gewählt, dass sie aufsummiert 1 bzw. -1 ergeben. Wenn wir die gleiche Anzahl an Fällen in den zu kontrastierenden Gruppen haben, können wir die einzelnen Gewichte auch zu 1 bzw. -1 vereinfachen.
Achtung: Wenn wir ungleich große Gruppen haben, wie in unserem Beispiel, dann liegen die einzelnen Gewichtungen als Brüche vor z.B. \(c_{Männer} = \frac{1}{675}\) und \(c_{Frauen} = -\frac{1}{627}\). Wenn wir die Elemente unserer kontrastkodierten Variablen
gender_kontrast
aufsummieren, erhalten wir -5.5944832^{-17}. Damit genügen wir de facto der Bedingung \(\sum\limits_{i} c_i = 0\) nicht, aber die Zahl ist so klein (d.h. so nah an 0), dass wir sie vernachlässigen können.
Jetzt nehmen wir die kontrastkodierte Variable als Prädiktor in ein neues Regressionsmodell auf.
<- lm(introelapse ~ gender_kontrast, PWE_data)
lm_kontr summary(lm_kontr)
Call:
lm(formula = introelapse ~ gender_kontrast, data = PWE_data)
Residuals:
Min 1Q Median 3Q Max
-2464 -2451 -998 -985 961038
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1758.6 775.3 2.268 0.0235 *
gender_kontrast 476517.1 513617.1 0.928 0.3537
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 28490 on 1348 degrees of freedom
Multiple R-squared: 0.0006381, Adjusted R-squared: -0.0001032
F-statistic: 0.8608 on 1 and 1348 DF, p-value: 0.3537
18.6 Summary-Variablen
Wenn wir nicht mit den Rohdaten arbeiten wollen, sondern Informationen von mehreren, aggregierten Variablen (z.B. Summenwerte, Mittelwerte) oder anderweitig transformierten Variablen (z.B. standardisierte Werte) auswerten wollen, müssen wir Summary-Variablen erstellen.
Wir nutzen zu diesem Zweck den Datensatz PWE_data, welcher aus einer psychometrischen Erhebung stammt. Zu Beginn des Kapitels haben wir den Datensatz heruntergeladen.
Achtung: Die Werte der Variablen
Q9A
,Q13A
undQ15A
, die wir im Folgenden nutzen werden, wurden im Abschnitt Umkodieren rekodiert, weil sie negativ gepolt sind.
rowSums()
, rowMeans()
und select()
: Summen- und Mittelwerte
Wir schauen uns im Folgenden an, wie man Summen- und Mittelwerte von mehreren Variablen erstellt. Dieses Vorgehen ist beispielsweise für die Erstellung von Skalenwerten in der Testkonstruktion von großer Relevanz.
Wir schauen uns den Summen- sowie den Mittelwert über alle Items der Protestant Work Ethic Scale an. Die Fragen wurden auf einer intervallskalierten Skala - von 1 (stimme nicht zu) bis 5 (stimme zu) - beantwortet und in den Variablen, die mit einem Q beginnen und einem A enden gespeichert.
Um einen Summenwert, d.h. eine Summe einer Person über mehrere Variablen, zu bilden, können wir auf die Funktion rowSums()
zurück greifen. Wenn wir fehlende Werte in den Variablen haben, setzen wir das Argument na.rm=TRUE
.
Es ist zusätzlich sinnvoll, mit einer Kombination aus select()
, matches()
und den regulären Ausdrücken, die relevanten Variablen auszuwählen. Dafür laden wir das Paket dplyr.
matches()
ist eine select helper Funktion, welcher man reguläre Ausdrücke übergeben kann. Es gibt noch weitere Hilfsfunktionen, die man auch als Alternative zu regulären Ausdrücken nutzen kann z.B. starts_with(x)
anstatt ^x
. Wenn wir aber mehrere Bedingungen haben, sollten wir matches()
und den regulären Ausdruck .*
(zur konjunktiven Verknüpfung) nutzen.
# library(dplyr)
$sum <- rowSums(select(PWE_data, matches("^Q.*A$")), na.rm=TRUE) PWE_data
Nun schauen wir uns die neu erstellte Variable (für die die ersten 10 Personen) einmal an:
Wenn wir hingegen nicht den Summen- sondern den Mittelwert einer Person über Variablen bilden wollen, nutzen wir rowMeans()
. Der Rest bleibt analog zum Vorgehen oben.
$mean <- rowMeans(select(PWE_data, matches("^Q.*A$")), na.rm=TRUE) PWE_data
Abschließend schauen wir uns wieder die neu erstellte Variable (für die die ersten 10 Personen) an:
ifelse()
: Auswahl bestimmter Fälle
Jetzt schauen wir uns an, wie wir eine Variable nur für eine bestimmte Gruppe erstellen können.
Dafür nutzen wir die Funktion ifelse(test, yes, no)
. Mit dieser testen wir, ob eine oder mehrere Bedingungen (test
) zutreffen und geben an, was mit den Fällen passieren soll, auf die die Bedingung(en) zutreffen (yes
) und jene, auf die diese nicht zutreffen (no
).
Für unser Beispiel sollen alle Fälle, auf die die Bedingung(en) nicht zutreffen, ein NA
auf der neu erstellten Variablen erhalten.
Wenn wir mehrere Bedingungen nutzen wollen, können wir auf die logischen Operatoren zurückgreifen. Mit &
geben wir an, dass beide Bedingungen zutreffen sollen; mit |
dass eine Bedingung zutreffen soll. Mit runden Klammern können wir Bedingungen noch differenzierter angeben z.B. (A | B) & C
heißt, dass entweder A oder B eine (noch festzulegende) Ausprägung erfüllen müssen und zusätzlich noch C. Mehr Informationen zu logischen Operatoren finden wir im gleichnamigen Abschnitt.
Wir nutzen das gleiche Beispiel wie im Abschnitt vorher, nur dass wir jetzt nur den Summenwert für weibliche (gender == 2
) Buddhistinnen (religion == 3
) bilden wollen.
$sum_gr <- ifelse(PWE_data$gender == 2 & PWE_data$religion == 3, # test
PWE_datarowSums(select(PWE_data, matches("^Q.*A$")), # yes
na.rm=TRUE),
NA) # no
Schauen wir uns die neu erstellte Variable sowie die Gruppierungsvariablen (der Personen 687 bis 691 an, unter denen sich 2 von insgesamt 9 weibliche Buddhistinnen befinden) einmal an:
mutate()
: Summary-Variablen als Funktion von anderen Variablen erstellen
Wenn wir neue Variablen erstellen wollen, die Funktionen von bestehenden Variablen sind, können wir die Funktion mutate()
aus dem Paket dplyr nutzen. Besonders nützlich hierbei ist, dass wir mit dem Parameter .keep
festlegen können, welche Variablen wir im (neu erstellten) Datensatz behalten wollen.
Nachdem wir schon den Mittelwert der Skale berechnet haben (mean
), wollen wir noch eine neue Variable erstellen, die den z-standardisierten Mittelwert der Personen widergibt.
# für z-Standardisierung notwendige Kennwerte berechnen:
<- mean(PWE_data$mean, na.rm=TRUE) # Mittelwert über alle Personenmittelwerte
mean_all <- sd(PWE_data$mean, na.rm=TRUE) # Standardabweichung der Mittelwerte
sd_all # library(dplyr)
<- mutate(PWE_data,
PWE_data_mean sw_mean = (mean - mean_all) / sd_all,
.keep = "used") # alle benutzten und neu erstellen Variablen
Abschließend können wir uns den neu erstellten Datensatz PWE_data_mean
(für die ersten 10 Personen) einmal anschauen:
18.7 Weitere wichtige Hinweise
18.7.1 Cheat Sheet dplyr
Wer Gefallen an den tidyverse-Funktionen select()
, filter()
, mutate()
, und summarise()
gefunden hat, kann ein Cheat Sheet zur Data Transformation mit dplyr in deutsch oder englisch herunterladen.
18.7.2 Stichprobengröße
Es ist generell sehr wichtig, auch bei der Datenvorbereitung, ein Auge auf die Stichprobengröße zu haben. Teilweise werden bei der Datenvorbereitung einige Untersuchungseinheiten aus der Analyse exkludiert und damit sinkt die Stichprobengröße \(N\).
Wenn wir Auswertungen machen, in denen wir Ergebnisobjekte bekommen (z.B. bei der Regression mit lm()
), können wir die Information zu \(N\) daraus ablesen. Dazu klicken wir auf das Ergebnisobjekt im Environment (z.B. lm_kontr
). Unter model
sehen wir die Dimensionalität des genutzten Teil des Datensatzes und können anhand der Anzahl der Zeilen \(N\) ablesen (z.B. bei lm_kontr
: [116 x 3]
d.h. 116 Fälle).
18.7.3 Replizierbarkeit
Wir sollten unsere R-Skripte generell großzügig kommentieren (mit #
), damit wir (und ggf. auch Dritte) schnell nachvollziehen können, was wir da eigentlich gemacht haben.
Es lohnt sich auch, wenn man einen Datensatz (teil-)aufbereitet hat, diesen zu speichern, d.h. als neue Datei außerhalb von R zu exportieren (z.B. wenn man einen Datensatz vom Wide- ins Long-Format gebracht hat).
Außerdem ist es sinnvoll, alle Schritte der Datenvorbereitung sowie Datenauswertung im gleichen Programm durchzuführen, um möglichen Kompatibilitätsproblemen zwischen verschiedenen Programmen vorzubeugen.
18.8 Übung
Im Folgenden wollen wir einige Aufgaben bearbeiten, die in den Bereich der Datenvorbereitung fallen. Dazu gehören u.a. das Extrahieren und Sortieren von Daten, die Änderung der Kodierung von Daten sowie das Erstellen von Summary-Variablen. Zuallererst sollten wir uns jedoch immer mit dem genutzten Datensatz vertraut machen.
Dazu nutzen wir einen Datensatz, der im Rahmen eines Projektes zur Untersuchung des Zusammenhangs des Bedürfnisses nach Privatsphäre und verschiedenen Persönlichkeitseigenschaften erhoben wurde. Mehr Informationen zum Projekt und zur Publikation finden wir hier.
Den Datensatz sowie das dazugehörige Codebuch finden wir im Open Science Framework. Mehr Informationen zu OSF, der Replikationskrise und der Open Science Bewegung finden wir hier.
Den Datensatz können wir, nachdem wir ihn heruntergeladen haben, folgendermaßen in R einlesen:
<- read.csv("Dateipfad/data.csv") # hier den eigenen Dateipfad einfügen data
So sollte der Datensatz aussehen:
18.8.1 Übung 1: Erste Schritte
Zuallererst wollen wir uns mit dem Datensatz vertraut machen. Dazu benötigen wir das Codebuch, welches uns Informationen über die erhobenen Variablen sowie deren Messung gibt. Am besten überfliegen wir das Codebuch und den Datensatz einmal, um uns damit vertraut zu machen, bevor wir die nachfolgenden Aufgaben bearbeiten.
Achtung: Es sind nicht alle Variablen, die im Codebuch auftauchen, auch im Datensatz.
1.) Es gibt zwei Variablen im Datensatz, die nicht im Codebuch zu finden sind. Welche sind das?
Lösung
Die Variable sex
taucht nicht im Datensatz auf. Nur die Variable male
, welche die Ausprägungen 0
und 1
besitzt. Schätzungsweise soll mit beiden dieselbe Information koderit werden: das biologische Geschlecht der befragten Personen.
Die Variable time
taucht nicht im Codebuch auf. Möglicherweise ist das die individuelle Bearbeitungszeit für den Fragebogen in Sekunden. Vor der Nutzung der der Variablen time
müssten wir deren Bedeutung klären.
2.) Wie viele Variablen und Beobachtungen enthält der Datensatz?
Tipp
Standardmäßig sind Variablen die Spalten und Beobachtungen die Zeilen eines Datensatzes.Lösung
Wir finden die Information im R Studio Feld Environment …
… oder indem wir folgende Funktionen nutzen:
ncol(data) # Variablen = Anzahl der Spalten
[1] 70
nrow(data) # Beobachtungen (Personen; N) = Anzahl der Zeilen
[1] 296
3.) Liegen alle Variablen in einem ihrem Messniveau angemessenen Datentyp vor?
Tipp
Im Codebuch finden wir Informationen zu den Variablen. Über die Funktionen str()
bekommen wir Informationen zum Datentyp.
Lösung
str(data)
'data.frame': 296 obs. of 70 variables:
$ id : int 1 2 3 4 5 6 7 8 9 10 ...
$ male : int 0 1 0 0 0 0 1 1 0 0 ...
$ age : int 19 19 20 19 22 20 20 18 19 19 ...
$ inc : int 2 3 1 2 3 2 1 1 1 3 ...
$ time : int 2456 1414 828 1043 1806 1133 1625 2319 7129 1343 ...
$ pri_nee_gen_1: int 6 7 5 6 4 5 6 5 4 4 ...
$ pri_nee_gen_2: int 6 2 5 3 5 4 6 6 3 4 ...
$ pri_nee_gen_3: int 6 6 4 6 7 6 5 7 4 6 ...
$ pri_nee_gen_4: int 7 7 5 6 7 6 7 7 6 6 ...
$ pri_nee_soc_1: int 2 4 5 2 5 4 5 4 1 6 ...
$ pri_nee_soc_2: int 4 5 4 2 5 4 6 4 2 6 ...
$ pri_nee_soc_3: int 3 6 4 3 5 3 4 6 2 6 ...
$ pri_nee_soc_4: int 1 6 4 2 4 2 5 4 2 4 ...
$ pri_nee_soc_5: int 1 4 4 5 4 2 6 6 3 6 ...
$ pri_nee_soc_6: int 1 3 5 5 2 4 4 4 2 2 ...
$ pri_nee_soc_7: int 1 1 3 3 1 2 2 6 1 2 ...
$ pri_nee_soc_8: int 1 7 4 2 2 2 2 6 3 2 ...
$ pri_nee_soc_9: int 1 3 4 2 6 5 6 7 1 6 ...
$ pri_nee_int_1: int 1 1 4 4 2 4 2 5 2 5 ...
$ pri_nee_int_2: int 1 6 5 5 1 2 5 5 1 3 ...
$ pri_nee_int_3: int 7 7 5 5 6 2 4 3 3 6 ...
$ pri_nee_int_4: int 3 6 2 7 5 5 4 4 4 6 ...
$ pri_nee_int_5: int 5 6 4 7 2 5 2 3 6 6 ...
$ pri_nee_int_6: int 3 6 3 5 2 3 3 2 3 3 ...
$ pri_nee_int_7: int 7 7 5 6 2 5 6 6 6 4 ...
$ pri_nee_int_8: int 1 7 4 5 5 3 5 5 6 3 ...
$ pri_nee_int_9: int 4 7 3 3 5 5 5 5 4 3 ...
$ soc_1 : int 5 5 3 2 2 3 2 5 3 2 ...
$ soc_2 : int 4 4 6 6 6 6 5 2 6 6 ...
$ soc_3 : int 4 4 2 3 2 2 5 6 4 6 ...
$ soc_4 : int 4 6 4 7 6 6 3 5 3 4 ...
$ soc_5 : int 7 5 2 6 4 2 2 4 2 3 ...
$ soc_6 : int 4 5 6 5 2 6 2 4 3 4 ...
$ soc_7 : int 3 2 2 2 2 2 2 5 3 2 ...
$ soc_8 : int 3 4 6 6 5 4 2 2 7 5 ...
$ itg_1 : int 1 1 2 3 2 3 1 4 3 1 ...
$ itg_2 : int 2 1 4 4 4 6 2 4 1 1 ...
$ itg_3 : int 5 7 4 5 4 5 4 4 7 1 ...
$ itg_4 : int 2 7 6 2 6 6 7 4 3 7 ...
$ itg_5 : int 1 1 2 1 2 2 1 4 1 1 ...
$ itg_6 : int 4 1 2 3 2 2 4 4 2 1 ...
$ itg_7 : int 1 6 2 2 3 2 1 4 6 1 ...
$ itg_8 : int 5 2 4 5 4 4 6 4 3 1 ...
$ itg_9 : int 5 1 1 2 6 5 2 4 7 2 ...
$ itg_10 : int 5 2 2 7 6 5 7 4 7 6 ...
$ itg_11 : int 4 4 4 5 5 5 6 4 4 1 ...
$ anx_1 : int 1 1 3 5 5 2 6 5 3 6 ...
$ anx_2 : int 5 4 3 1 5 6 3 3 5 5 ...
$ anx_3 : int 1 1 5 3 3 2 6 3 2 6 ...
$ anx_4 : int 4 3 3 3 3 3 5 1 7 2 ...
$ anx_5 : int 6 6 2 5 5 3 6 3 1 2 ...
$ anx_6 : int 6 7 3 3 5 6 2 5 6 3 ...
$ anx_7 : int 3 6 5 6 4 2 2 1 2 5 ...
$ anx_8 : int 6 4 4 3 5 7 4 6 7 2 ...
$ ria_1 : int 1 7 5 5 6 3 4 3 7 6 ...
$ ria_2 : int 5 7 5 4 4 6 6 5 6 6 ...
$ ria_3 : int 1 6 5 6 6 3 3 5 5 3 ...
$ ria_4 : int 6 3 3 4 5 5 3 4 6 6 ...
$ ria_5 : int 4 2 5 6 4 2 6 6 5 5 ...
$ ria_6 : int 5 6 3 3 5 6 2 3 4 5 ...
$ ria_7 : int 5 7 5 4 5 5 1 7 5 6 ...
$ ria_8 : int 6 7 5 5 5 6 6 5 4 6 ...
$ tra_1 : int 5 7 3 3 2 5 3 4 4 5 ...
$ tra_2 : int 6 2 6 6 6 4 6 4 7 5 ...
$ tra_3 : int 5 7 4 3 5 5 5 4 5 5 ...
$ tra_4 : int 7 6 5 7 4 5 6 4 5 2 ...
$ tra_5 : int 5 7 4 3 5 5 2 4 4 6 ...
$ tra_6 : int 7 1 4 6 6 5 6 4 6 2 ...
$ tra_7 : int 4 7 4 3 5 4 2 4 2 6 ...
$ tra_8 : int 3 7 4 3 4 5 5 4 5 6 ...
Alle Variablen liegen als integer vor. Für die Fragebogenitems (pri_nee_
, soc_
, itg_
, anx_
, ria_
und tra_
), die intervallskaliert sein sollen, ist das korrekt. Die soziodemographischen Variablen male
und inc
hingegen sind nominal- bzw. ordinalskaliert. Das bedeutet, dass sie noch faktorisiert werden müssen, um in R als solche erkannt zu werden.
# nominalskaliert (unsortierter Faktor):
$male <- factor(data$male)
datastr(data$male)
Factor w/ 2 levels "0","1": 1 2 1 1 1 1 2 2 1 1 ...
Achtung: Nicht verwirren lassen: Der Faktor
male
hat die Kodierungen0
und1
(wie schon die integer-Variable vorher), aber die interne Kodierung des Faktors ist1
und2
.
# ordinalskaliert (sortierter Faktor):
$inc <- factor(data$inc, ordered=TRUE)
datastr(data$inc)
Ord.factor w/ 5 levels "1"<"2"<"3"<"4"<..: 2 3 1 2 3 2 1 1 1 3 ...
levels(data$inc)
[1] "1" "2" "3" "4" "5"
Achtung: Aus dem Codebuch ist leider nicht ersichtlich, welche Kategorie (z.B.
< $500
) für welche Kodierung steht (z.B.1
). Wir gehen hier davon aus, dass beide aufsteigend gepaart wurden, z.B.< $500
=1
, aber die interne Kodierung eines Faktors beginnt bei1
, d.h. in unserem Fall gibt es1
und2
.
4.) Wie heißt die Variable, die kodiert, inwieweit die befragte Person gerne viele Menschen um sich herum hat? Welche Antwortoption auf der Messskala (hiermit ist nicht die Kodierung der Daten gemeint) haben die meisten befragten Personen angekreuzt?
Tipp 1
Den Namen der Variablen sowie deren Messskala finden wir im Codebuch. Die Häufigkeiten der jeweiligen Ausprägungen der Variablen bringen wir in R in Erfahrung.Tipp 2
Mit der Funktiontable()
können wir uns die Häufigkeiten der Ausprägungen einer Variablen ausgeben lassen. Die Funktion ist auch bereits sehr hilfreich, um einen schnellen Überblick über die möglichen Ausprägungen zu bekommen.
Lösung
Die Variable heißt soc_2
und hat eine Messskala, welche von -3 bis 3 (inklusive 0) geht (Codebuch S.20).
table(data$soc_2)
1 2 3 4 5 6 7
2 14 36 69 72 61 27
Die Kodierung 5
kommt am häufigsten vor. Die meisten befragten Personen haben damit eine 1 auf der Messskala angegeben.
5.) Gibt es Werte von Variablen im Datensatz, die unplausibel erscheinen? Wenn ja, entferne die entsprechenden Personen aus dem Datensatz.
Tipp 1
Hiervoll ist es sinnvoll, sich eine Übersicht der Ausprägungen aller Variablen anzuschauen und diese ggf. mit den Angaben im Codebuch zu vergleichen.
Tipp 2
Es gibt einen Wert einer Variablen der heraussticht.
Lösung
sapply(sapply(data, unique), sort, na.last=TRUE) # sortierte Ausprägungen der Variablen
$id
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
[37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
[55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
[73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
[91] 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
[109] 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
[127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
[145] 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
[163] 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
[181] 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
[199] 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
[217] 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
[235] 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
[253] 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
[271] 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
[289] 289 290 291 292 293 294 295 296
$male
[1] 0 1 <NA>
Levels: 0 1
$age
[1] 9 18 19 20 21 22 23 28 29 56 NA
$inc
[1] 1 2 3 4 5 <NA>
Levels: 1 < 2 < 3 < 4 < 5
$time
[1] 3 5 7 8 9 10 12 13 21 31
[11] 86 129 165 194 240 259 313 351 369 371
[21] 384 439 443 455 456 458 484 539 562 585
[31] 587 593 601 602 606 624 638 639 663 677
[41] 694 706 725 741 760 778 814 815 828 829
[51] 840 847 851 852 875 877 890 892 899 910
[61] 934 986 991 992 999 1035 1039 1040 1043 1052
[71] 1056 1061 1071 1073 1075 1077 1088 1090 1094 1097
[81] 1104 1118 1119 1133 1141 1143 1144 1148 1152 1155
[91] 1157 1158 1166 1167 1176 1177 1187 1197 1200 1209
[101] 1211 1219 1220 1222 1227 1236 1243 1248 1250 1255
[111] 1258 1259 1265 1290 1292 1303 1313 1314 1316 1318
[121] 1319 1332 1343 1345 1349 1363 1367 1372 1376 1387
[131] 1388 1397 1402 1405 1411 1414 1419 1425 1438 1448
[141] 1453 1460 1465 1481 1492 1496 1497 1499 1514 1524
[151] 1525 1560 1581 1595 1596 1602 1606 1625 1628 1633
[161] 1651 1655 1656 1660 1669 1671 1672 1683 1695 1702
[171] 1706 1713 1714 1716 1732 1734 1735 1745 1753 1756
[181] 1772 1778 1780 1803 1806 1815 1820 1831 1845 1847
[191] 1869 1879 1889 1905 1930 1932 1945 1949 1985 1994
[201] 2022 2077 2125 2135 2139 2142 2144 2183 2185 2197
[211] 2248 2255 2319 2342 2348 2360 2373 2393 2420 2447
[221] 2456 2493 2495 2515 2518 2523 2537 2597 2606 2649
[231] 2654 2673 2759 2769 2927 3204 3224 3273 3386 3466
[241] 3739 3803 3808 3858 3913 4084 4436 4702 4748 5031
[251] 5108 5229 5469 5927 5944 6064 6282 6598 6685 6849
[261] 7129 7246 8016 8817 9274 12533 13928 17371 17973 22834
[271] 50047 54973 96391 101191 110358 192301 197593 342508 590589 688498
$pri_nee_gen_1
[1] 1 2 3 4 5 6 7 NA
$pri_nee_gen_2
[1] 1 2 3 4 5 6 7 NA
$pri_nee_gen_3
[1] 2 3 4 5 6 7 NA
$pri_nee_gen_4
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_1
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_2
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_3
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_4
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_5
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_6
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_7
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_8
[1] 1 2 3 4 5 6 7 NA
$pri_nee_soc_9
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_1
[1] 1 2 3 4 5 6 NA
$pri_nee_int_2
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_3
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_4
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_5
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_6
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_7
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_8
[1] 1 2 3 4 5 6 7 NA
$pri_nee_int_9
[1] 1 2 3 4 5 6 7 NA
$soc_1
[1] 1 2 3 4 5 6 7 NA
$soc_2
[1] 1 2 3 4 5 6 7 NA
$soc_3
[1] 1 2 3 4 5 6 7 NA
$soc_4
[1] 1 2 3 4 5 6 7 NA
$soc_5
[1] 1 2 3 4 5 6 7 NA
$soc_6
[1] 1 2 3 4 5 6 7 NA
$soc_7
[1] 1 2 3 4 5 6 7 NA
$soc_8
[1] 1 2 3 4 5 6 7 NA
$itg_1
[1] 1 2 3 4 5 6 NA
$itg_2
[1] 1 2 3 4 5 6 7 NA
$itg_3
[1] 1 2 3 4 5 6 7 NA
$itg_4
[1] 1 2 3 4 5 6 7 NA
$itg_5
[1] 1 2 3 4 5 6 7 NA
$itg_6
[1] 1 2 3 4 5 6 7 NA
$itg_7
[1] 1 2 3 4 5 6 7 NA
$itg_8
[1] 1 2 3 4 5 6 7 NA
$itg_9
[1] 1 2 3 4 5 6 7 NA
$itg_10
[1] 1 2 3 4 5 6 7 NA
$itg_11
[1] 1 2 3 4 5 6 7 NA
$anx_1
[1] 1 2 3 4 5 6 7 NA
$anx_2
[1] 1 2 3 4 5 6 7 NA
$anx_3
[1] 1 2 3 4 5 6 7 NA
$anx_4
[1] 1 2 3 4 5 6 7 NA
$anx_5
[1] 1 2 3 4 5 6 7 NA
$anx_6
[1] 1 2 3 4 5 6 7 NA
$anx_7
[1] 1 2 3 4 5 6 7 NA
$anx_8
[1] 1 2 3 4 5 6 7 NA
$ria_1
[1] 1 2 3 4 5 6 7 NA
$ria_2
[1] 1 2 3 4 5 6 7 NA
$ria_3
[1] 1 2 3 4 5 6 7 NA
$ria_4
[1] 1 2 3 4 5 6 7 NA
$ria_5
[1] 1 2 3 4 5 6 7 NA
$ria_6
[1] 1 2 3 4 5 6 7 NA
$ria_7
[1] 1 2 3 4 5 6 7 NA
$ria_8
[1] 1 2 3 4 5 6 7 NA
$tra_1
[1] 1 2 3 4 5 6 7 NA
$tra_2
[1] 1 2 3 4 5 6 7 NA
$tra_3
[1] 1 2 3 4 5 6 7 NA
$tra_4
[1] 1 2 3 4 5 6 7 NA
$tra_5
[1] 1 2 3 4 5 6 7 NA
$tra_6
[1] 1 2 3 4 5 6 7 NA
$tra_7
[1] 1 2 3 4 5 6 7 NA
$tra_8
[1] 1 2 3 4 5 6 7 NA
which(data$age == 9)
[1] 232
Die Person mit der Zeile 232 hat (vermutlich versehentlich) als Alter 9
Jahre angegeben. Wir entfernen diese Person aus dem Datensatz.
<- data[-which(data$age == 9),] data
6.) Enthält der Datensatz fehlende Werte (“Missings”; NA
) und wenn ja, wie viele insgesamt und auf welchen Variablen?
Tipp
Das Thema “Fehlende Werte” wird im Kapitel zur Datenvorbereitung nur kurz angerissen, weil es ein eigenständiges Kapitel dazu gibt.Lösung
anyNA(data) # prüft ob mind. ein Missing im Datensatz
[1] TRUE
Ja, data
enthält fehlende Werte.
table(is.na(data)) # absolute Anzahl fehlender Werte
FALSE TRUE
19337 1313
table(is.na(data))[2]/(table(is.na(data))[1] + table(is.na(data))[2])
# relative Anzahl fehlender Werte
TRUE
0.06358354
colSums(is.na(data)) # Übersicht über Anzahl Missings für alle Variablen
id male age inc time
0 24 25 28 0
pri_nee_gen_1 pri_nee_gen_2 pri_nee_gen_3 pri_nee_gen_4 pri_nee_soc_1
19 19 19 19 19
pri_nee_soc_2 pri_nee_soc_3 pri_nee_soc_4 pri_nee_soc_5 pri_nee_soc_6
19 19 19 19 19
pri_nee_soc_7 pri_nee_soc_8 pri_nee_soc_9 pri_nee_int_1 pri_nee_int_2
19 19 19 21 21
pri_nee_int_3 pri_nee_int_4 pri_nee_int_5 pri_nee_int_6 pri_nee_int_7
21 21 21 21 21
pri_nee_int_8 pri_nee_int_9 soc_1 soc_2 soc_3
21 21 15 15 15
soc_4 soc_5 soc_6 soc_7 soc_8
15 15 16 15 15
itg_1 itg_2 itg_3 itg_4 itg_5
21 21 21 21 21
itg_6 itg_7 itg_8 itg_9 itg_10
21 21 21 21 21
itg_11 anx_1 anx_2 anx_3 anx_4
21 18 18 18 18
anx_5 anx_6 anx_7 anx_8 ria_1
18 18 18 18 17
ria_2 ria_3 ria_4 ria_5 ria_6
17 17 17 17 17
ria_7 ria_8 tra_1 tra_2 tra_3
17 17 21 21 21
tra_4 tra_5 tra_6 tra_7 tra_8
21 21 21 21 21
Jede Variable mit Ausnahme von id
und time
enthält fehlende Werte.
7.) Was ist die höchste Anzahl an fehlenden Werten von einer Person und welche Person hat bzw. welche Personen haben die meisten fehlenden Werte?
Tipp
In der letzten Aufgabe haben wir uns variablenweise Missings angeschaut mitcolSums()
. Nun wollen wir uns zeilenweise Missings anschauen.
Lösung
max(rowSums(is.na(data))) # maximale Anzahl Missings (absoluter Wert)
[1] 68
max(rowSums(is.na(data))) / ncol(data)
# Max-Anzahl Missings einer Person / Anzahl aller Variablen (relativer Wert)
[1] 0.9714286
Fehlende Werte auf 68 Variablen ist die Höchstzahl an fehlenden Werten pro Person. Das entspricht einem Prozentsatz von ca. 97% fehlenden Werten.
# Personen mit diesen Zeilen haben die meisten Missings:
names(which(rowSums(is.na(data)) == max(rowSums(is.na(data)))))
[1] "63" "66" "78" "133" "134" "136" "139" "180" "199" "206" "262" "267"
[13] "268" "281" "283"
Wir müssen hier names()
nutzen, damit wir die Zeilennamen ausgegeben bekommen da wir sonst einen benannten Vektor ausgegeben bekommen.
Man sollte überlegen, wie mit den Daten dieser 15 Personen bei etwaigen Analysen umzugehen wäre. Wenn die Daten MCAR sind, könnten wir diese Personen aus den Analysen entfernen. Mehr Infos zu Fehlenden Werten im gleichnamigen Kapitel.
18.8.2 Übung 2: Extrahieren von Daten
Nun wollen wir einzelne Daten aus dem Datensatz extrahieren. Dabei interessieren uns entweder Variablen mit bestimmten Ausprägungen (von Personen) oder Personen mit bestimmten Ausprägungen (auf Variablen).
Achtung: Achte darauf, immer auch die
id
-Variable mit zu extrahieren, um Beobachtungseinheiten identifizieren zu können, auch wenn das nicht jedes mal erneut in der Aufgabenbeschreibung steht.
1.) Extrahiere alle demographischen Variablen aus data
.
Tipp 1
Im Codebuch (S. 26ff) steht, welche Variablen zu den demographischen Angaben zählen.
Achtung: Im Codebuch steht die Variable
sex
, welche im Datensatz nicht enthalten ist. Alternativ gibt es die Variablemale
.
Tipp 2
Es gibt drei demographische Variablen indata
(aber mehr im Codebuch).
Lösung
library(dplyr)
select(data, id, male, age, inc)
2.) Extrahiere alle Variablen, die grundlegende Bedürfnisse (General Needs) erfassen.
Tipp 1
Die Variablen haben denselben Wortstamm:gen
.
Tipp 2
Es gibt vier Items zu grundlegenden Bedürfnissen.Lösung
c(1, # id
data[,grep("gen", names(data)))] # general needs
3.) Extrahiere alle Variablen, die gesellschaftliche (Societal Needs) und interpersonelle (Interpersonal Needs) Bedürfnisse erfassen.
Tipp 1
Die Variablen haben zwar denselben Wortstamm –nee
– aber den haben die Variablen zu grundlegenden Bedürfnissen (General Needs) auch.
Tipp 2
Es gibt insgesamt 18 Items zu gesellschaftlichen und interpersonellen Bedürfnissen (jeweils 9).Lösung
# Vorauswahl von Bedürfnis-Variablen mittels des gemeinsamen Wortstammes:
<- data[,c(1, # id
all_needs grep("nee", names(data)))] # alle Bedürfnis-Variablen
# Auswahl der gewollten Variablen aus den Bedürfnis Variablen (neben "id") ..
# .. solche, die "c" oder "t" im Namen haben (trifft nur auf General needs nicht zu):
grep("id|c|t", names(all_needs))] all_needs[
4.) Extrahiere alle Personen, die weniger als $500 oder mehr als $5.000 monatlich zur Verfügung haben.
Tipp
Um beide Bedingungen (< $500
und > $5.000
) abzufragen, können wir den logischen Operator |
(“oder”) nutzen.
Lösung
Zuerst vergleichen wir die Angaben zu den Ausprägungen von inc
im Codebuch (S. 28) und der Kodierung in R. Laut Codebuch ist < $500
die erste Ausprägung; > $5.000
die letzte. Nun schauen wir, mit welchen Kodierungen diese korrespondieren.
table(data$inc)
1 2 3 4 5
148 74 24 15 6
DIe beiden werden mit 1
und 5
kodiert. Anschließend wenden wir dieses Wissen auf unsere Selektion an.
filter(data, inc == 1 | inc == 5)
5.) Extrahiere die “Extremkreuzer” (Personen, die nur die niedrigste oder höchste Ausprägung einer Frage ankreuzen) aus dem Geselligkeits-Fragebogen (Sociability).
Tipp 1
Wir können wieder den logischen Operator|
(“oder”) nutzen, um jeweils Personen mit der niedrigste oder höchsten Ausprägung eines Items auszuwählen.
Tipp 2
Es gibt insgesamt zwei Extremkreuzer.Lösung
Im Codebuch (S. 20) sehen wir, dass für alle Items des Geselligkeits-Fragebogen dieselbe Skale, welche von -3 bis 3 geht, genutzt wurde. Nun schauen wir uns am Beispiel eines Items an, wie diese in R kodiert werden.
# niedrigste und höchste Ausprägung in Erfahrung bringen:
table(data$soc_1)
1 2 3 4 5 6 7
23 71 54 55 52 21 4
# Fall-Selektion anwenden:
filter(data, soc_1 == 1 | soc_1 == 7,
== 1 | soc_2 == 7,
soc_2 == 1 | soc_3 == 7,
soc_3 == 1 | soc_4 == 7,
soc_4 == 1 | soc_5 == 7,
soc_5 == 1 | soc_6 == 7,
soc_6 == 1 | soc_7 == 7,
soc_7 == 1 | soc_8 == 7) soc_8
Auf den Seiten 6 bis 7 sehen wir die Items des Geselligkeits-Fragebogens (oben rechts ist der Pfeil).
6.) Extrahiere die Items des Fragebogens zu Integrität (Integrity) für alle Personen, die 18 Jahre alt sind.
Tipp 1
Die Reihenfolge der Auswahl, d.h. ob zuerst Variablen oder zuerst Fälle selektiert werden, ist eigentlich irrelevant, aber wenn wir erst die Fälle selektieren und dann die Variablen müssen wir die Variableage
nicht wieder aus dem finalen Datensatz entfernen.
Tipp 2
Der finale Datensatz besteht aus 12 Variablen (davon sind 11 die Integritäts-Items) von 54 Personen.Lösung
# Fälle selektieren:
<- filter(data, age == 18)
data_18 # Variablen selektietren:
<- data_18[,c(1, # id
all_itg_items grep("itg", names(data_18)))] # Integrität Items
7.) Extrahiere alle Personen, die im Fragebogen zu interpersonellen Bedürfnissen (Needs, Interpersonal) über dem Gesamtmittelwert (Mittelwert über alle Personen im Datensatz) liegen. Entferne Personen mit mindestens einem fehlenden Wert auf diesen Items aus der Analyse.
Achtung: Unser Vorgehen mit fehlenden Wert hier (casewise deletion) ist stark vereinfacht und sollte in echten Analysen nicht ohne Belege für MCAR durchgeführt werden.
Tipp 1
Wir müssen1) alle Items des Fragebogens extrahieren,
2) alle Personen mit mind. einem Missing entfernen,
3) die Items jeweils für jede Person aufsummieren (d.h. individuelle Scores bilden),
4) diese individuellen Scores über alle Personen aufsummieren, um den Gesamtmittelwert zu berechnen,
5) die individuellen Scores mit dem Gesamtmittelwert vergleichen, um nur überdurchschnittliche Personen in unserer finalen Auswahl zu haben.
Tipp 2
Im finalen Datensatz befinden sich 140 (von initial 296) Personen.Lösung
# 1) Items extrahieren:
<- data[,c(1, # id
all_int_items grep("int", names(data)))] # Int. Bedürfnisse Items
# 2) Personen mit mind. einem Missing entfernen:
<- na.omit(all_int_items) # 21 Personen entfernt
all_int_items # 3) individuelle Scores bilden:
library(dplyr)
<- mutate(all_int_items, score = rowSums(all_int_items))
all_int_items # 4) Mittelwert individueller Scores berechnen:
<- mean(all_int_items$score)
mean_score mean_score
[1] 182.6387
# 5) überdurchschnittliche Personen extrahieren:
<- filter(all_int_items, score > mean_score) # 135 Personen entfernt final_data
18.8.3 Übung 3: Sortieren von Daten
Im Folgenden wollen wir unseren Datensatz nach den aufsteigenden bzw. absteigenden Ausprägungen auf einer der enthaltenen Variablen sortieren.
1.) Sortiere data
(primär) nach den aufsteigenden Ausprägungen in age
und (sekundär) nach dem Geschlecht (Frauen zuerst).
Achtung: Da die Kodierung der Variablen
male
im Codebuch nicht geklärt wird, gehen wir hier standarmäßig davon aus, dass0
für nein und1
für ja steht.
Lösung
Da Frauen zuerst in der sekundären Sortierung vorkommen sollen, können wir beide Variablen aufsteigend sortieren.
order(data$age, data$male),] # Default: aufsteigende Sortierung data[
2.) Sortiere data
(primär) nach absteigendem Einkommen und (sekundär, tertiär, ect.) nach den aufsteigenden Ausprägungen auf den Traditionalismus-Items (Traditionalism) in ihrer natürlichen Reihenfolge (beginnend bei _1
, endend bei _8
).
Tipp 1
Mit der Funktionorder()
können wir nur jeweils auf- oder absteigend für alle angegebenen Variablen sortieren. Daher sollten wir für diese Aufgabe auf andere Funktionen, z.B. arrange()
aus dem Paket dplyr zurückgreifen.
Tipp 2
Um besser beurteilen zu können, ob die Sortierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz nur aus den relevanten Variablen (inklusiveid
) zu erstellen.
Lösung
library(dplyr)
# neuer Datensatz nur mit relevanten Variablen:
<- data[,c(1, # id
data_tra 4, # inc
grep("tra", names(data)))]
# Sortierung:
arrange(data_tra, -inc, tra_1, tra_2, tra_3, tra_4, tra_5, tra_6, tra_7, tra_8)
# mit einem "-" sortieren wir absteigend
Warning: There was 1 warning in `arrange()`.
ℹ In argument: `..1 = -inc`.
Caused by warning in `Ops.ordered()`:
! '-' is not meaningful for ordered factors
18.8.4 Übung 4: Änderung der Kodierung von Daten
Nun wollen wir die Kodierungen einiger Variablen ändern bzw. Variablen mit neuen Kodierungen erstellen.
1.) Erstelle eine neue Variable income
, die das monatliche Einkommen der befragten Personen kodiert, und dafür die Kategorien aus dem Codebuch (S.28) nutzt.
Tipp
Um besser beurteilen zu können, ob die Kodierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz bestehend ausinc
und id
zu erstellen.
Lösung
# neuen Datensatz erstellen:
library(dplyr)
<- select(data, id, inc)
data_inc # neue Variable income erstellen:
<- c("1='<$500'; 2='$500-$1000'; 3='$1000-$2000'; 4='$2000-$4000'; 5='>$5000'")
rec library(car)
$income <- recode(data_inc$inc, recodes=rec) data_inc
Die Variable rec
, welche die Überführung der bestehenden in die neue Kodierung enthält, wurde nur aus darstellerischen Gründen erstellt. Der Inhalt kann auch direkt dem Parameter recodes
übergeben werden.
2.) Erstelle eine neue Variable sex
, die analog zur gleichnamigen Variablen im Codebuch das biologische Geschlecht kodiert. Gehe für diese Aufgabe davon aus, dass alle Personen, die einen fehlender Wert auf male
haben, einer Kategorie other
(nicht-binäres Geschlecht) zugeordnet werden.
Tipp
Um besser beurteilen zu können, ob die Kodierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz bestehend ausmale
und id
zu erstellen.
Lösung
library(dplyr)
# neuen Datensatz erstellen:
<- select(data, id, male)
data_sex # neue Variable sex erstellen:
$sex <- case_when(data_sex$male == 0 ~ "female",
data_sex$male == 1 ~ "male",
data_sexis.na(data_sex$male) ~ "other")
3.) Rekodiere die negativ kodierten Items des Fragebogens zu Risikovermeidung (Risk Avoidance).
Tipp 1
Die negativ kodierten Items sind im Codebuch jeweils mit einem * markiert.Tipp 2
Es ist sinnvoll zur Überprüfung der Aufgabe, einen neuen Datensatz bestehend aus den negativ kodierten Items und ihrem rekodierten Pendant (und natürlichid
) zu erstellen.
Lösung
# neuen Datensatz erstellen:
library(dplyr)
<- select(data, id,
data_ria # negativ kodierte Items
ria_1, ria_3, ria_5) # neue rekodierte Variablen erstellen:
library(car)
$ria_1_rec <- recode(data_ria$ria_1, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")
data_ria$ria_3_rec <- recode(data_ria$ria_3, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")
data_ria$ria_5_rec <- recode(data_ria$ria_5, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1") data_ria
18.8.5 Übung 5: Erstellen von Summary-Variablen
Nachfolgend wollen wir neue Variablen erstellen, die Informationen aus mehreren Variablen des Datensatzes zusammenfassen.
1.) Erstelle für die verschiedenen Bereiche von Bedürfnissen – Allgemein (General), Gesellschaftlich (Societal) und Interpersonell (Interpersonal) – jeweils separate Summenwerte und einen Gesamtsummenwert (über alle drei Bereiche).
Tipp
Die Items aller drei Bedürfnis-Bereiche haben denselben Wortstamm:nee
.
Lösung
# neuer Datensatz mit relevanten Variablen:
<- data[,c(1, # id
data_need grep("nee", names(data)))] # Bedürfnis-Items
# Summenwerte der drei Bedürfnis-Bereiche und Gesamtsummenwert:
library(dplyr)
$score_gen <- rowSums(select(data_need, matches("nee_gen")))
data_need$score_soc <- rowSums(select(data_need, matches("nee_soc")))
data_need$score_int <- rowSums(select(data_need, matches("nee_int")))
data_need$score_all <- rowSums(select(data_need, matches("nee"))) data_need
2.) Erstelle die personenspezifischen Summenwerte der Items, die interpersonelle Bedürfnisse erfassen (Needs, Interpersonal), für alle Personen, die ein monatliches Einkommen von mehr als $1.000 haben. Bei allen Personen, die weniger zur Verfügung haben, soll ein "/"
in der Summenwert-Variablen stehen.
Tipp
Zur Bearbeitung der Aufgabe können wirmutate()
mit ifelse()
kombinieren, um den beiden Bedingungen (monatliches Einkommen von mehr bzw. weniger als $1.000) unterschiedliche Werte zuzuweisen.
Lösung
# neuer Datensatz mit relevanten Variablen:
<- data[,c(1, # id
data_int 4, # inc
grep("int", names(data)))] # Int. Bedürfnisse Items
Nun vergleichen wir die Angaben zu den Ausprägungen von inc
im Codebuch (S. 28) und der Kodierung in R. Es gibt insgesamt 5 Ausprägungen. Auf unsere Bedingung (mehr als $1.000) treffen drei Ausprägungen zu. Daher ist es codesparender, wenn wir die zwei nicht zutreffenden Ausprägungen (< $500
und $500 - $1000
) negieren (!=
). Laut Codebuch ist < $500
die erste Ausprägung; $500 - $1000
die zweite. Nun schauen wir, mit welchen Kodierungen diese korrespondieren.
table(data$inc)
1 2 3 4 5
148 74 24 15 6
Sie haben die Kodierungen 1
und 2
. Diese Information können wir nun anwenden.
# neue Variable mit Summenwert bzw. "/" erstellen
library(dplyr)
$score <- ifelse(data_int$inc != 1 & data_int$inc != 2,
data_introwSums(select(data_need, matches("int"))),
"/") # ifelse(Bedingung, trifft zu, trifft nicht zu)
Um eine möglichst exakte Replikation der Funktionen zu gewährleisten gibt es im folgenden relevante Angaben zum System (R-Version, Betriebssystem, geladene Pakete mit Angaben zur Version), mit welchem diese Seite erstellt wurde.
sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.1 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Etc/UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] car_3.1-3 carData_3.0-5 rmarkdown_2.29 knitr_1.49
[5] readr_2.1.5 kableExtra_1.4.0 dplyr_1.1.4
loaded via a namespace (and not attached):
[1] jsonlite_1.8.9 compiler_4.4.2 crayon_1.5.3 tidyselect_1.2.1
[5] xml2_1.3.6 stringr_1.5.1 systemfonts_1.1.0 scales_1.3.0
[9] yaml_2.3.10 fastmap_1.2.0 R6_2.5.1 generics_0.1.3
[13] Formula_1.2-5 htmlwidgets_1.6.4 tibble_3.2.1 munsell_0.5.1
[17] svglite_2.1.3 pillar_1.9.0 tzdb_0.4.0 rlang_1.1.4
[21] utf8_1.2.4 stringi_1.8.4 xfun_0.49 viridisLite_0.4.2
[25] cli_3.6.3 withr_3.0.2 magrittr_2.0.3 digest_0.6.37
[29] rstudioapi_0.17.1 hms_1.1.3 lifecycle_1.0.4 vctrs_0.6.5
[33] evaluate_1.0.1 glue_1.8.0 abind_1.4-8 fansi_1.0.6
[37] colorspace_2.1-1 tools_4.4.2 pkgconfig_2.0.3 htmltools_0.5.8.1
Für Informationen zur Interpretation dieses Outputs schaut auch den Abschnitt Replizierbarkeit von Analysen des Kapitels zu Paketen an.