Datenvorbereitung

Wie wir Daten vor der Analyse aufbereiten können
Letzte Änderung am 26. Februar 2021


Einleitung

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:

data(airquality)
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:

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
PWE_data <- read_table2("Dateipfad/data.csv")
names(PWE_data)
  [1] "Q1A"          "Q1I"          "Q1E"          "Q2A"         
  [5] "Q2I"          "Q2E"          "Q3A"          "Q3I"         
  [9] "Q3E"          "Q4A"          "Q4I"          "Q4E"         
 [13] "Q5A"          "Q5I"          "Q5E"          "Q6A"         
 [17] "Q6I"          "Q6E"          "Q7A"          "Q7I"         
 [21] "Q7E"          "Q8A"          "Q8I"          "Q8E"         
 [25] "Q9A"          "Q9I"          "Q9E"          "Q10A"        
 [29] "Q10I"         "Q10E"         "Q11A"         "Q11I"        
 [33] "Q11E"         "Q12A"         "Q12I"         "Q12E"        
 [37] "Q13A"         "Q13I"         "Q13E"         "Q14A"        
 [41] "Q14I"         "Q14E"         "Q15A"         "Q15I"        
 [45] "Q15E"         "Q16A"         "Q16I"         "Q16E"        
 [49] "Q17A"         "Q17I"         "Q17E"         "Q18A"        
 [53] "Q18I"         "Q18E"         "Q19A"         "Q19I"        
 [57] "Q19E"         "country"      "introelapse"  "testelapse"  
 [61] "surveyelapse" "TIPI1"        "TIPI2"        "TIPI3"       
 [65] "TIPI4"        "TIPI5"        "TIPI6"        "TIPI7"       
 [69] "TIPI8"        "TIPI9"        "TIPI10"       "VCL1"        
 [73] "VCL2"         "VCL3"         "VCL4"         "VCL5"        
 [77] "VCL6"         "VCL7"         "VCL8"         "VCL9"        
 [81] "VCL10"        "VCL11"        "VCL12"        "VCL13"       
 [85] "VCL14"        "VCL15"        "VCL16"        "education"   
 [89] "urban"        "gender"       "engnat"       "age"         
 [93] "screenw"      "screenh"      "hand"         "religion"    
 [97] "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).

exclamation
Im Abschnitt Plausibilitäts-Check werden die Kodierungen einiger Variablen noch korrigiert. Außerdem müssen die Werte der Variablen Q9A, Q13A und Q15A 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:

vornamen_13 <- read.csv("Dateipfad/vornamen-von-neugeborenen2013.csv")
vornamen_14 <- read.csv("Dateipfad/vornamen-von-neugeborenen2014.csv")
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:

exclamation Es kommt häufiger zu Problemen bei der Ausführung der Funktionen filter(), select() und summarise() 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.

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.

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.

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 entsprcht 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

Datentypen und Datenstrukturen

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

Nachfolgend finden wir eine Übersicht zu den Möglichkeiten der Kodierung von Merkmalen mit verschiedenen Skalenniveaus.

_
Art der Skala:
Nominal- Ordinal- Intervall- Verhältnis- Absolut-
Datentyp: character X X\(^2\)
numeric X\(^1\) X\(^2\) 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" ...
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).

Dataframe

Für viele Anwendungen in R (z.B. für das Erstellen von Grafiken mit gglot2) 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:

airquality <- as.data.frame(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.

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 NAs 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
[17]  22  23  24  27  28  29  30  31  32  34  35  36  37  39  40  41
[33]  44  45  46  47  48  49  50  52  59  61  63  64  65  66  71  73
[49]  76  77  78  79  80  82  84  85  89  91  96  97 108 110 115 118
[65] 122 135 168  NA

$Solar.R
  [1]   7   8  13  14  19  20  24  25  27  31  36  37  44  47  48  49
 [17]  51  59  64  65  66  71  77  78  81  82  83  91  92  95  98  99
 [33] 101 112 115 118 120 127 131 135 137 138 139 145 148 149 150 153
 [49] 157 167 175 183 186 187 188 189 190 191 192 193 194 197 201 203
 [65] 207 212 213 215 220 222 223 224 225 229 230 236 237 238 242 244
 [81] 248 250 252 253 254 255 256 258 259 260 264 266 267 269 272 273
 [97] 274 275 276 279 284 285 286 287 290 291 294 295 299 307 313 314
[113] 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
[14]  8.6  9.2  9.7 10.3 10.9 11.5 12.0 12.6 13.2 13.8 14.3 14.9 15.5
[27] 16.1 16.6 18.4 20.1 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
[23] 79 80 81 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] 23 24 25 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
[23] 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
[45] 57 58 59 60 61 62 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
[14]  570  586  600  601  640  720  768  780  800  810  834  864  948
[27]  960  962 1024 1080 1093 1111 1138 1152 1200 1242 1280 1300 1348
[40] 1360 1364 1366 1368 1376 1400 1422 1423 1440 1477 1493 1500 1504
[53] 1536 1541 1600 1676 1680 1707 1821 1824 1920 2048 2400 2560 2561
[66] 3072 3840

$screenh
 [1]    0  320  347  360  414  480  486  505  568  569  570  576  592
[14]  597  601  614  615  618  640  648  658  667  672  692  698  702
[27]  720  731  732  736  740  741  744  747  748  752  753  758  759
[40]  760  762  768  774  780  786  800  803  808  809  812  819  820
[53]  831  846  848  853  854  863  864  866  869  879  888  892  896
[66]  900  902  912  933  943  945  960  962 1000 1003 1022 1024 1050
[79] 1080 1112 1152 1194 1200 1280 1350 1366 1440 1536 1728 1920 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? 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
PWE_data[PWE_data == 0] <- NA

# "Rückkodierung" für Variablen, die regulär 0 enthalten
# library(dplyr)
PWE_data$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)

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.

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 
           1            1            1            1            1 
         Q2E          Q3A          Q3I          Q3E          Q4A 
           1            1            1            1            1 
         Q4I          Q4E          Q5A          Q5I          Q5E 
           1            1            1            1            1 
         Q6A          Q6I          Q6E          Q7A          Q7I 
           1            1            1            1            1 
         Q7E          Q8A          Q8I          Q8E          Q9A 
           1            1            1            1            1 
         Q9I          Q9E         Q10A         Q10I         Q10E 
           1            1            1            1            1 
        Q11A         Q11I         Q11E         Q12A         Q12I 
           1            1            1            1            1 
        Q12E         Q13A         Q13I         Q13E         Q14A 
           1            1            1            1            1 
        Q14I         Q14E         Q15A         Q15I         Q15E 
           1            1            1            1            1 
        Q16A         Q16I         Q16E         Q17A         Q17I 
           1            1            1            1            1 
        Q17E         Q18A         Q18I         Q18E         Q19A 
           1            1            1            1            1 
        Q19I         Q19E      country  introelapse   testelapse 
           1            1            0            0            0 
surveyelapse        TIPI1        TIPI2        TIPI3        TIPI4 
           0            8           10           13            9 
       TIPI5        TIPI6        TIPI7        TIPI8        TIPI9 
           9            8            9            8            8 
      TIPI10         VCL1         VCL2         VCL3         VCL4 
          13            0            0            0            0 
        VCL5         VCL6         VCL7         VCL8         VCL9 
           0            0            0            0            0 
       VCL10        VCL11        VCL12        VCL13        VCL14 
           0            0            0            0            0 
       VCL15        VCL16    education        urban       gender 
           0            0           17           19            7 
      engnat          age      screenw      screenh         hand 
           2            0            2            2            5 
    religion  orientation         race        voted      married 
          15           25            0           11            6 
  familysize        major 
          28          448 

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

Faktorisieren

Wir schauen uns das Faktorisieren exemplarisch an zwei Variablen aus dem Datensatz PWE_data an:

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)
PWE_data$gender_uf <- factor(PWE_data$gender)

Nun erstellen wir einen (neuen) sortierten (d.h. ordinalskalierten) Faktor. Dafür ergänzen wir das Argument ordered=TRUE.

# faktorisieren (sortiert; natürliche Sortierung)
PWE_data$education_of <- factor(PWE_data$education, ordered=TRUE)

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)
PWE_data$education_of_s <- factor(PWE_data$education, ordered=TRUE,
                                levels=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.

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

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:

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.

exclamation 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).

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.

vornamen_merge_row <- merge(vornamen_13, vornamen_14, all = TRUE)

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.