Datenvorbereitung

Wie wir Daten vor der Analyse aufbereiten und manipulieren können
Letzte Änderung am 30. September 2020

Table of Contents


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)

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 diesen 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. Hier finden wir weitere Informationen zum Erhebungsinstrument, der Protestant Work Ethic Scale.

Hinweis: Im Abschnitt Plausibilitäts-Check wird die Kodierung in PWE_data noch korrigiert.

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:

Tipp: Für weitere Hilfe beim Herunterladen und Einlesen von (u.a.) csv-Dateien können wir uns die das Kapitel Daten Einlesen anschauen.

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 und RStudio.

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

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, was die Überprüfung erleichtert.


sapply(sapply(airquality, unique), sort)

$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

$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

$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 

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:

Wir erstellen uns dafür eine Kopie des Datensatzes und arbeiten mit dieser weiter.


# Kopie des Datensatzes erstellen
PWE_data_fac <- PWE_data

Zuerst erstellen wir einen 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_fac$gender_uf <- factor(PWE_data_fac$gender)

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


# faktorisieren (sortiert; natürliche Sortierung)
PWE_data_fac$education_of <- factor(PWE_data_fac$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_fac$education_of_s <- factor(PWE_data_fac$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_fac[,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.

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.

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_xy <- merge(x, y, all = TRUE) 
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)
vornamen_bind <- bind_rows(vornamen_13, vornamen_14, .id = "id")
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
Die Anzahl der Zeilen stimmt mit der Summe der Zeilen von vornamen_13 und vornamen_14 überein, d.h. es wurden alle Fälle übernommen.

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.


vornamen_merge_col <- merge(vornamen_13, vornamen_14, by = "vorname", all = TRUE)

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.

  1. vorname und geschlecht 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(
  vornamen_merge_col$geschlecht.x == vornamen_merge_col$geschlecht.y),])

[1] 1672
  1. geschlecht bei vorname 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(
  vornamen_merge_col$geschlecht.x != vornamen_merge_col$geschlecht.y),])

[1] 112
  1. vorname und geschlecht eines Falles sind nur in einem Dataframe enthalten (für die Daten des anderen sind NAs angegeben)

vornamen_merge_col[2,]

  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)

vornamen_join <- full_join(vornamen_13, vornamen_14, by="vorname")

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)


vornamen_join_row <- full_join(vornamen_13, vornamen_14)

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

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.

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.

Die Variablennamen 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
var_mit_o <- grep(pattern="o", names(airquality)) 

# Anwenden der Selektion auf den Dataframe
df_var_mit_o <- airquality[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 mit grep() 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. Ein Beispiel dazu finden wir im Abschnitt Fälle mit grep() extrahieren. 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.


df_wind_ozone <- data.frame(airquality$Wind, airquality$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.

Wir können den Variablen z.B. 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)

df_month_day <- select(airquality, 
                       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)

df_without_month_day <- select(airquality, 
                       -Month, 
                       -Day)

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.

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 Ausprägungen der Fälle haben …
… 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
[17]  28  31  34  49  51  53  54  55  85  88  90  96 103 104 110 118
[33] 122 135 140 141 142 144 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"
[14] "62" "61" "61" "67" "76" "67" "65" "76" "76" "76" "76" "86" "86"
[27] "86" "86" "86" "86" "76" "86" "96" "76" "67" "76" "68" "64" "69"
[40] "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
[17]  34  49 140 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"
[14] "61" "61" "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
[17] 118 122 135 141 152

grep("6$", airquality$Temp, value=TRUE) # Werte

 [1] "56" "66" "66" "66" "76" "76" "76" "76" "76" "86" "86" "86" "86"
[14] "86" "86" "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
[17] 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"
[14] "85" "85" "85" "75" "75" "75"

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.


df_temp5 <-  airquality[grep("^5|5$", airquality$Temp),]

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.


df_temp57 <- airquality[airquality$Temp == 57,]

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.


df_temp57_66 <- airquality[airquality$Temp == 57 | airquality$Temp == 66,]
Weitere logische Operatoren sind z.B. != (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.


df_month6_day15ff <- dplyr::filter(airquality, Month == 6, Day >= 15)

Zu Beginn haben wir erläutert, warum wir manchmal :: nutzen sollten.

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),].


df_ascend_temp <- airquality[order(airquality$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.


df_ascend_temp_wind <- airquality[order(airquality$Temp, airquality$Wind),]

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


df_descend_temp_wind_1 <- airquality[order(-airquality$Temp, airquality$Wind),]

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.


df_ascend_temp_wind_2 <- arrange(airquality, -Temp, Wind)

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.

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 Merkmale in R auf verschiedene Arten ändern. Nachfolgend schauen wir uns zwei Funktionen an, die wir nutzen können.

Davor erstellen wir eine Kopie des Datensatzes PWE_data, mit der wir im Folgenden arbeiten werden.


PWE_data_code <- PWE_data


recode()

In der Funktion recode() aus dem Paket car müssen wir grundsätzlich zwei Argumente spezifizieren: var und recodes. Ersterem übergeben wir die umzukodierende Variable, zweiterem einen String, welcher in der Form Input=Output die alte in die neuen Kodierung umwandelt.

Wir müssen hierbei einige syntaktische Besonderheiten beachten:

Schauen wir uns an, wie man die Ausprägungen von eduaction (d.h. 1, 2, 3, 4) zu den Beschreibungen (d.h. Less than High School, High School, University Degree, Graduate Degree) ändert.


library(car)

PWE_data_code$education_new <- recode(var=PWE_data_code$education,
                                      recodes="1='Less than High School';
                                      2='High School';
                                      3='University Degree'; 
                                      4='Graduate Degree'")

str(PWE_data_code$education_new) # Überprüfung Datentyp

 chr [1:1350] "University Degree" "High School" "High School" ...

unique(PWE_data_code$education_new) # um alle Kategorien zu sehen

[1] "University Degree"     "High School"          
[3] "Graduate Degree"       "Less than High School"
[5] NA                     

education_new liegt als character vor und wird somit als nominalskaliert gehandhabt.

Das Argument as.factor legt fest, ob die rekodierte Variable als Faktor gespeichert werden soll. Per default wird das nur gemacht, wenn die Variable auch vorher schon als Faktor vorliegt. Leider können wir so aber keine geordneten Faktoren, d.h. keine ordinalskalierten Variablen, erstellen. Dafür müssten wir auf die Funktion factor(..., ordered = TRUE, levels) zurückgreifen (siehe Abschnitt Faktorisieren).

Das Vorgehen dabei, wenn die neuen Kodierungen auch Zahlen sein sollen ist sogar etwas sparsamer, weil wir uns die ' ' sparen können.

Als Beispiel invertieren wir die Kodierung von TIPI1.


PWE_data_code$TIPI1_invert <- car::recode(var=PWE_data_code$TIPI1,
                                       recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")

str(PWE_data_code$TIPI1_invert) # Überprüfung Datentyp

 num [1:1350] 6 6 7 1 6 6 2 7 6 3 ...

unique(PWE_data_code$TIPI1_invert) # um alle Kategorien zu sehen

[1]  6  7  1  2  3  5  4 NA

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


library(dplyr)

PWE_data_code$Q1E_kat <- case_when(
  PWE_data_code$Q1E < 45457.75 ~ 1, # kleiner damit exklusiv 
  PWE_data_code$Q1E < 90720.5 ~ 2, 
  PWE_data_code$Q1E < 135983.2 ~ 3, 
  PWE_data_code$Q1E < 181247 ~ 4) 

str(PWE_data_code$Q1E_kat) # Überprüfung Datentyp

 num [1:1350] 1 1 1 1 1 1 1 1 1 1 ...

unique(PWE_data_code$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.


PWE_data_code$Q1E_kat <- factor(PWE_data_code$Q1E_kat, ordered=TRUE)

str(PWE_data_code$Q1E_kat) # Überprüfung Datentyp

 Ord.factor w/ 4 levels "1"<"2"<"3"<"4": 1 1 1 1 1 1 1 1 1 1 ...

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_0\) Steigung \(b_j\) mögliche Anwendung
Dummy Mittelwert in der Referenzgruppe Differenz des Mittelwerts der j-ten Gruppe zur Referenzgruppe Vergleich von Experimental- und Kontrollgruppe
Effekt Mittelwert der Gruppenmittelwerte (Gesamtmittelwert) Differenz des Mittelwertes der j-ten Gruppe zum Gesamtmittelwert Vergleich von Gruppen in varianzanalytischen Designs
Kontrast 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).

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 ref()) 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_ct<- lm(introelapse ~ C(gender_uf, contr.treatment(3, 1)), PWE_data_fac) 
summary(lm_ct)

Call:
lm(formula = introelapse ~ C(gender_uf, contr.treatment(3, 1)), 
    data = PWE_data_fac)

Residuals:
   Min     1Q Median     3Q    Max 
 -2248  -2235   -781   -764 961254 

Coefficients:
                                     Estimate Std. Error t value
(Intercept)                              2249       1038   2.166
C(gender_uf, contr.treatment(3, 1))2    -1466       1496  -0.980
C(gender_uf, contr.treatment(3, 1))3    -1847       4339  -0.426
                                     Pr(>|t|)  
(Intercept)                            0.0305 *
C(gender_uf, contr.treatment(3, 1))2   0.3274  
C(gender_uf, contr.treatment(3, 1))3   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_uf <- lm(introelapse ~ gender_uf, PWE_data_fac)
summary(lm_uf)

Call:
lm(formula = introelapse ~ gender_uf, data = PWE_data_fac)

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 (Yes, d.h. Englisch-Muttersprachler). 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 ref() 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
PWE_data_fac$gender_uf_ref <- relevel(PWE_data_fac$gender_uf, ref = 2)

ls.str(PWE_data_fac[,c(103, 106)]) # zum Überprüfen

gender_uf :  Factor w/ 3 levels "1","2","3": 1 2 2 2 2 2 1 3 2 1 ...
gender_uf_ref :  Factor w/ 3 levels "2","1","3": 2 1 1 1 1 1 2 3 1 2 ...

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 Intercept 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_cs <- lm(introelapse ~ C(gender_uf, contr.sum(3)), PWE_data_fac)
summary(lm_cs)

Call:
lm(formula = introelapse ~ C(gender_uf, contr.sum(3)), data = PWE_data_fac)

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
PWE_data_fac$gender_uf_ref <- relevel(PWE_data_fac$gender_uf, ref = 2)

ls.str(PWE_data_fac[,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_fac <- PWE_data_fac[order(PWE_data_fac$gender_uf),] 
# 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_fac$gender_uf, useNA = 'ifany')

   1    2    3 <NA> 
 675  627   41    7 

# Kontrast erstellen
PWE_data_fac$gender_kontrast <- c(rep(1/675, 675), # Male (1)
                                  rep(-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.

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_kontr <- lm(introelapse ~ gender_kontrast, PWE_data_fac)
summary(lm_kontr)

Call:
lm(formula = introelapse ~ gender_kontrast, data = PWE_data_fac)

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
In unserem Regressionsmodell mit dem Kontrast Männer vs. Frauen entspricht der Interzept \(b_0\) dem Mittelwert der Gruppenmittelwerte der betrachteten Gruppen (d.h. Männer und Frauen) und das partiellen Steigungsgewichte \(b_1\) dem Unterschied in den Mittelwerten der betrachteten Gruppen.

6. Summary-Variablen

Wenn wir nicht mit den Rohdaten arbeiten wollen, sondern Informationen von mehreren, aggregierten Variablen (z.B. Skalensummenwerte) oder anderweitig transformierten Variablen (z.B. standardisierte Werte) auswerten wollen, müssen wir Summary-Variablen erstellen.

Wir schauen uns im Rahmen dieses Unterkapitels an, wie man Summen- und Mittelwerte von mehreren Variablen erstellt. Dieses Vorgehen ist beispielsweise für die Erstellung von Skalen in der Testkonstruktion von großer Relevanz. Dafür schauen wir uns drei Funktionen aus dem Paket dplyr an.

Wir nutzen zu diesem Zweck den Datensatz PWE_data, welcher aus einer psychometrischen Erhebung stammt. Zu Beginn des Kapitels haben wir den Datensatz heruntergeladen.

mutate(): Zusammenfassung aller Fälle

Wenn wir eine neue Variable als Funktion aus anderen, im Dataframe enthaltenen Variablen erstellen wollen, und dafür alle Fälle einbeziehen wollen, können wir mutate() nutzen. Die Funktion erstellt eine neue Variable und fügt diese dem Dataframe hinzu.

Wir schauen uns Summenwerte über 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. Wir wollen uns den Summenwert von allen 19 Items anschauen.

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.

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)
PWE_data <- mutate(PWE_data, 
                       sum_all = rowSums(select(PWE_data, matches("^Q.*A$")),
                                         na.rm=TRUE))

Wenn wir hingegen nicht den Summen- sondern den Mittelwert über die Variablen bilden wollen, nutzen wir rowMeans(). Der Rest bleibt analog zum Vorgehen oben.


PWE_data <- mutate(PWE_data, 
                        mean_all = rowMeans(select(PWE_data, matches("^Q.*A$")),
                                            na.rm=TRUE))

Für unser Beispiel wurden mit mutate() alle 102 Variablen im Dataframe behalten, aber der Übersichtlichkeit halber nur die neu erstellten ausgegeben (und von diesen auch nur die ersten 10 von 1350 Fällen).

Wenn wir nur die neu erstellten Summary-Variablen im Dataframe behalten wollen (und nicht die vorher enthaltenen Variablen), können wir die Funktion transmute() anstelle von mutate() nutzen.

mutate() und ifelse(): Zusammenfassung 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 zusätzlich zu mutate() 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 unseren Zweck 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.


PWE_data <- mutate(PWE_data, 
                       sum_gr = ifelse(PWE_data$gender == 2 & PWE_data$religion == 3, # test
                                        rowSums(select(PWE_data, matches("^Q.*A$")),  # yes
                                                na.rm=TRUE),
                                        NA))                                          # no
Es gibt nur 9 weibliche Buddhistinnen im Datensatz und wir schauen uns hier exemplarisch die Fälle 20-30 an, unter denen sich 2 Fälle befinden.

7. Weitere wichtige Hinweise

Cheat Sheet dplyr

Wenn ihr Gefallen an den tidyverse-Funktionen select(), filter(), mutate(), transmute() und summarise() gefunden habt, könnt ihr euch ein Cheat Sheet zur Data Transformation mit dplyr in deutsch oder englisch herunterladen.

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

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.


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.0.2 (2020-06-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.1 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=de_DE.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=de_DE.UTF-8        LC_COLLATE=de_DE.UTF-8    
 [5] LC_MONETARY=de_DE.UTF-8    LC_MESSAGES=de_DE.UTF-8   
 [7] LC_PAPER=de_DE.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=de_DE.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] car_3.0-10       carData_3.0-4    dplyr_1.0.2     
[4] readr_1.3.1      kableExtra_1.2.1 knitr_1.30      
[7] rmarkdown_2.3   

loaded via a namespace (and not attached):
 [1] zip_2.1.1         Rcpp_1.0.5        cellranger_1.1.0 
 [4] highr_0.8         compiler_4.0.2    pillar_1.4.6     
 [7] forcats_0.5.0     tools_4.0.2       digest_0.6.25    
[10] jsonlite_1.7.1    evaluate_0.14     lifecycle_0.2.0  
[13] tibble_3.0.3      viridisLite_0.3.0 pkgconfig_2.0.3  
[16] rlang_0.4.7       openxlsx_4.2.2    rstudioapi_0.11  
[19] curl_4.3          distill_0.8       yaml_2.2.1       
[22] haven_2.3.1       xfun_0.18         rio_0.5.16       
[25] httr_1.4.2        stringr_1.4.0     xml2_1.3.2       
[28] generics_0.0.2    vctrs_0.3.4       hms_0.5.3        
[31] webshot_0.5.2     tidyselect_1.1.0  data.table_1.13.0
[34] glue_1.4.2        R6_2.4.1          readxl_1.3.1     
[37] foreign_0.8-80    purrr_0.3.4       selectr_0.4-2    
[40] magrittr_1.5      scales_1.1.1      htmltools_0.5.0  
[43] ellipsis_0.3.1    abind_1.4-5       rvest_0.3.6      
[46] colorspace_1.4-1  stringi_1.5.3     munsell_0.5.0    
[49] crayon_1.3.4     

Für Informationen zur Interpretation dieses Outputs schaut auch den Abschnitt Replizierbarkeit von Analysen des Kapitels zu Paketen an.

jump-to-top