18  Datenvorbereitung

Vor jeder statistischen Auswertung ist es notwendig, die Daten entsprechend der angestrebten Analyse aufzubereiten. Beispielsweise kann das beinhalten, Daten zusammenzuführen, zu extrahieren oder zu sortieren, aber auch Variablen umzukodieren oder transformierte Variablen zu erstellen.

In diesem Kapitel wollen wir uns verschiedene Schritte der Datenvorbereitung anschauen. Wir nutzen dafür größtenteils Funktionen aus zwei verschiedenen Paketen: dem Standardpaket base und dem Zusatzpaket dplyr. Zweiteres laden wir mit library(dplyr).

Wir fangen mit grundsätzlichen Überprüfungen unserer der Daten an. Nachfolgend arbeiten wir uns in spezifischere Aufbereitungsbereiche vor, die wir in Abhängigkeit unserer geplanten Auswertung ggf. benötigen.

Beispieldatensätze für dieses Kapitel

Für das vorliegende Kapitel nutzen wir mehrere Datensätze, um die unterschiedlichen Verarbeitungsschritte zu demonstrieren.

Den Datensatz airquality werden wir am meisten nutzen. Dieser enthält Daten aus einer Untersuchung der Luftqualität in New York, die von Mai bis September des Jahres 1973 stattfand. Der Datensatz ist standardmäßig in R enthalten und wir bekommen ihn mit der Funktion data() in unser R-Environment:

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:

  • Ozone: mittlere Ozonkonzentration in ppb (parts per billion)
  • Solar.R: Sonneneinstrahlung in Langley (Einheit)
  • Wind: durchschnittliche Windgeschwindigkeit in Meilen pro Stunde
  • Temp: maximale Temperatur in Grad Fahrenheit
  • Month: Monatsangabe als Zahl (1-12)
  • Day: Tagesangabe als Zahl (1-31)

Mehr Informationen zum Datensatz finden wir hier.

Den Datensatz PWE_data, welcher Daten einer psychometrischen Erhebung enthält, werden wir auch häufiger nutzen. Um den Datensatz und das Codebuch herunterzuladen, klicken wir auf diesen Link. Dann gehen wir in den Ordner PWE_data und lesen data.csv ein:

library(readr) # zum Einlesen der csv-Datei
PWE_data <- read_table("Dateipfad/data.csv")
names(PWE_data)
  [1] "Q1A"          "Q1I"          "Q1E"          "Q2A"          "Q2I"         
  [6] "Q2E"          "Q3A"          "Q3I"          "Q3E"          "Q4A"         
 [11] "Q4I"          "Q4E"          "Q5A"          "Q5I"          "Q5E"         
 [16] "Q6A"          "Q6I"          "Q6E"          "Q7A"          "Q7I"         
 [21] "Q7E"          "Q8A"          "Q8I"          "Q8E"          "Q9A"         
 [26] "Q9I"          "Q9E"          "Q10A"         "Q10I"         "Q10E"        
 [31] "Q11A"         "Q11I"         "Q11E"         "Q12A"         "Q12I"        
 [36] "Q12E"         "Q13A"         "Q13I"         "Q13E"         "Q14A"        
 [41] "Q14I"         "Q14E"         "Q15A"         "Q15I"         "Q15E"        
 [46] "Q16A"         "Q16I"         "Q16E"         "Q17A"         "Q17I"        
 [51] "Q17E"         "Q18A"         "Q18I"         "Q18E"         "Q19A"        
 [56] "Q19I"         "Q19E"         "country"      "introelapse"  "testelapse"  
 [61] "surveyelapse" "TIPI1"        "TIPI2"        "TIPI3"        "TIPI4"       
 [66] "TIPI5"        "TIPI6"        "TIPI7"        "TIPI8"        "TIPI9"       
 [71] "TIPI10"       "VCL1"         "VCL2"         "VCL3"         "VCL4"        
 [76] "VCL5"         "VCL6"         "VCL7"         "VCL8"         "VCL9"        
 [81] "VCL10"        "VCL11"        "VCL12"        "VCL13"        "VCL14"       
 [86] "VCL15"        "VCL16"        "education"    "urban"        "gender"      
 [91] "engnat"       "age"          "screenw"      "screenh"      "hand"        
 [96] "religion"     "orientation"  "race"         "voted"        "married"     
[101] "familysize"   "major"       

Der Datensatz enthält 102 Variablen. Einige davon schauen wir uns im Laufe des Kapitels noch genauer an. Im Codebook, welches sich ebenfalls im Ordner PWE_data befindet, finden wir eine Erklärung zu den Variablen. Mehr Informationen zum Erhebungsinstrument, der Protestant Work Ethic Scale, finden wir in Mirels & Garrett (1971) (nur über HU-VPN zugänglich).

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

  • vorname: Vorname (kann doppelt vorkommen, wenn Name für beide Geschlechter gegeben wurde)
  • anzahl: Häufigkeit des Vornamens in diesem Jahr
  • geschlecht: (binäres) Geschlecht der Kinder mit diesem Vornamen

Achtung: 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.

18.1 Grundlegende erste Schritte

Zuerst widmen wir unsere Aufmerksamkeit der Überprüfung wichtiger übergreifender Punkte der Datenvorbereitung. Diese sind: ob unser Datensatz als Dataframe vorliegt, ob unsere Daten plausibel und fehlende Werte korrekt kodiert sind, ob nominal- und v.a. ordinalskalierte Variablen faktorisiert sind, und ob wir unser bestehendes Tabellenformat ggf. ändern müssen. Die Schritte sind bereits in einer sinnvollen Abfolge angeordnet (z.B. ist es vorteilhaft, erst unplausible und fehlende Werte ausfindig zu machen und umzukodieren, bevor man Variablen faktorisiert).

Optional können wir vorher unser Wissen zum Messen von Merkmalen und der korrekten Darstellung dieser in R auffrischen.

Wir schauen uns nachfolgend nur die Datensätze airquality und PWE_data an.

18.1.1 Recap: Kodierung von Daten

Generell wenn wir mit Daten arbeiten, ist es ratsam, sich zuallererst Gedanken darüber zu machen, welche Informationen wir diesen entnehmen können (Messniveau) und ob sie so gespeichert sind, dass R sie richtig erkennt (Datentypen und -strukturen).

Die nachfolgende Wiederholung ist eine verkürzte Variante des Abschnitts Daten aus dem Kapitel zu Einführung in R.

18.1.1.1 Messniveaus

Das Messniveau (oder auch Skalenniveau) ist eine wichtige Eigenschaft von Merkmalen (Variablen) von Untersuchungseinheiten. Es beschreibt, welche Informationen in unseren Messwerten abgebildet werden und damit auch welche mathematischen Transformationen mit den Messwerten sinnvoll sind (z.B. das Berechnen von Mittelwerten). Somit begrenzt das Messniveau auch die zulässigen Datenauswertungsverfahren unserer Variablen.

Die Kodierung von nominalskalierten Merkmalen ist insofern willkürlich, als dass lediglich auf Gleichheit versus Ungleichheit geachtet werden muss (z.B. 1, 4, 9 oder A, Y, M).

airquality: -
PWE_data: u.a.school, urban, gender

Die Kodierung von ordinalskalierten Merkmalen geschieht der Größe nach, d.h. dass die Rangfolge der Kodierungen einzelner Gruppen relevant ist (z.B. 1 < 4 < 9 oder A < M < Y). Man kann aber auch eine eigene Sortierung festlegen, die nicht der “natürlichen” Rangfolge entspricht (z.B. Y < A < M). Ein Realschulabschluss ist beispielsweise besser als ein Hauptschulabschluss. Wir können aber nicht festlegen, wie viel besser er ist.

airquality: -
PWE_data: education

Bei der Kodierung von intervallskalierten Merkmalen sind sowohl die Rangfolge als auch die Abstände zwischen den Ausprägungen relevant (z.B. 1, 4, 7; jeweils mit gleichem Abstand zueinander; oder 1.4, 1.5, 2.3; jeweils mit verschiedenen Abständen zueinander). Ein Beispiel dafür ist die Temperatur in Grad Celsius oder Grad Fahrenheit.

airquality: Temp (Temperatur in Grad Fahrenheit)
PWE_data: u.a. Antworten (1-5) auf die Items des PWE (Q1A, …, Q19A), Antworten (1-7) auf die Items des TIPI (TIPI1, …, TIPI10)

Bei der Kodierung von verhältnisskalierten Merkmalen ist zusätzlich noch ein Nullpunkt vorhanden. Dieser erlaubt es, dass Quotienten zwischen Werten gebildet werden können. Ein beliebtes Beispiel ist die Kelvin Skala. Bei dieser ist bei 0°K keine Bewegungsenergie mehr vorhanden und 20°K sind doppelt so viel wie 40°K.

airquality: Ozone, Solar.R, Wind
PWE_data: age

Zu guter Letzt gibt es noch absolutskalierte Merkmale, welche sowohl einen eindeutigen Nullpunkt als auch eine eindeutige Einheit der Skala (z.B. Anzahl der Kinder) vorweisen kann. Die Kodierung entspricht der natürlichen Einheit.

airquality: -
PWE_data: familysize

Nachfolgend finden wir eine Tabelle der möglichen Unterscheidungen der jeweiligen Messiniveaus.

(Un-) Gleichheit Rangordnung Abstände Verhältnisse natürliche Einheit
Nominal X
Ordinal X X
Intervall X X X
Verhältnis X X X X
Absolut X X X X X

18.1.1.2 Datentypen und Datenstrukturen

Achtung: Die Unterteilung nach “Datentyp” und “Datenstruktur” sind getreu des Manuals von R. Man stößt in anderen Quellen aber auch auf abweichende Unterteilungen bzw. Benennungen.

Der Datentyp gibt an, um was für Daten es sich handelt, d.h. welche Werte(bereiche) diese haben und welche Operationen wir auf sie anwenden können. Wir nutzen zumeist Zeichen, Wahrheitswerte und Zahlen. Diese werden in R als character, logical, integer und double gespeichert, wobei letztere beiden häufig als numeric zusammengefasst werden.

Die Datenstruktur bestimmt die Organisation und Speicherung von Daten(typen). In R gibt es z.B. Vektoren, Matrizen, Dataframes, Listen und Faktoren. Beispielsweise können Vektoren und Matrizen jeweils nur einen Datentypen enthalten, während Dataframes mehrere Datentypen enthalten können.

Datentyp und -struktur sind ausschlaggebend dafür, welche Funktionen wir anwenden können bzw. welchen Output wir bekommen.

  • Beispiel 1:
    Man kann nur mit numeric deskriptiv-statistische Kennwerte bilden.
  • Beispiel 2:
    Wenn mir nominal- bzw. ordinalskalierte Daten nicht adäquat kodieren, kann es zu ungewollten Konsequenzen kommen.
    Wenn wir z.B. eine Variable \(X\) haben, welche eine Gruppenzugehörigkeit mit 1, 2 und 3 kodiert, und diese als Prädiktor in ein Regressionsmodell aufnehmen, dann wird \(X\) als kontinuierliche Variable behandelt und wir würden keine separaten Schätzungen für die Mittelwertsdifferenzen der Gruppen bekommen.

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

_
Art der Skala:
Nominal- Ordinal- Intervall- Verhältnis- Absolut-
Datentyp: character X X2
Datentyp: numeric X1 X2 X X X
1 Faktorisieren (unordered factor) notwendig wenn keine Indikatorvariable(n) genutzt 2 Faktorisieren (ordered factor) notwendig

Mit der Funktion str() können wir uns eine kompakte Übersicht der enthaltenen Variablen (d.h. ihr Datentyp bzw. die Datenstruktur Faktor sowie jeweils die ersten 10 Werte) der Datenstruktur (hier: Dataframe) ausgeben lassen. So können wir schauen, ob die Daten auch entsprechend ihres Messniveaus kodiert sind.

str(airquality)
'data.frame':   153 obs. of  6 variables:
 $ Ozone  : int  41 36 12 18 NA 28 23 19 8 NA ...
 $ Solar.R: int  190 118 149 313 NA NA 299 99 19 194 ...
 $ Wind   : num  7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
 $ Temp   : int  67 72 74 62 56 66 65 59 61 69 ...
 $ Month  : int  5 5 5 5 5 5 5 5 5 5 ...
 $ Day    : int  1 2 3 4 5 6 7 8 9 10 ...

Für den Beispieldatensatz airquality liegen die verhältnisskalierten Variablen Ozone,Solar.R, Wind, Month und Day korrekterweise als integer bzw. numeric vor. Die intervallskalierte Variable Temp liegt auch wie erwartet als integer vor (weil die Temperatur in Fahrenheit und nicht in Kelvin gemessen wurde ist die Variable nicht verhältnisskaliert).

Für den Datensatz PWE_data schauen wir uns exemplarisch nur die letzten 15 Variablen an:

ls.str(PWE_data[,88:102])
age :  num [1:1350] 24 66 17 23 19 40 50 21 16 27 ...
education :  num [1:1350] 3 2 2 2 2 4 4 2 1 2 ...
engnat :  num [1:1350] 1 1 2 1 1 1 1 1 1 1 ...
familysize :  num [1:1350] 2 5 4 3 2 3 3 1 3 2 ...
gender :  num [1:1350] 1 2 2 2 2 2 1 3 2 1 ...
hand :  num [1:1350] 1 2 1 2 1 1 1 3 3 1 ...
major :  chr [1:1350] "Computer" NA NA "dietetics" NA "Philosophy" "engineering" ...
married :  num [1:1350] 1 3 1 1 1 3 2 1 1 2 ...
orientation :  num [1:1350] 1 1 1 1 1 2 1 5 2 1 ...
race :  num [1:1350] 11 16 16 16 16 16 16 17 16 16 ...
religion :  num [1:1350] 6 6 2 2 1 7 6 4 1 2 ...
screenh :  num [1:1350] 1080 640 1024 1080 615 ...
screenw :  num [1:1350] 1920 360 1280 1920 1093 ...
urban :  num [1:1350] 2 3 2 2 1 3 1 2 3 2 ...
voted :  num [1:1350] 2 2 2 2 2 2 1 2 2 1 ...

Wir nutzen hier für PWE_data die Funktion ls.str(), weil der Output für ein Tibble Dataframe so weniger ausführlich ist. Allerdings werden die Variablen mit dieser Funktion alphabetisch sortiert (im Gegensatz zu str(), welche die Variablen der Spaltennummerierung nach darstellt).

Wie wir sehen, liegen fast alle Variablen als numeric vor, obwohl viele nominalskaliert sind. So würden sie von R nicht entsprechend ihres Messniveaus behandelt werden. Wir müssen diese also entweder in character umwandeln oder faktorisieren (später mehr dazu).

18.1.2 Dataframe

Für viele Anwendungen in R (z.B. für das [Erstellen von Grafiken mit ggplot2][Grafiken ggplot]) ist es notwendig, dass der Datensatz als Dataframe vorliegt. Folgendermaßen können wir prüfen, ob ein Datensatz ein Dataframe ist:

is.data.frame(airquality)
[1] TRUE
is.data.frame(PWE_data)
[1] TRUE
Was genau ist ein Dataframe?

Ein Dataframe ist eine Datenstruktur, die es uns erlaubt, Daten tabellarisch zu speichern. Der Dataframe ist eine Liste aus Vektoren mit gleicher Länge. Listen können (im Gegensatz zu Matrizen) Variablen mit unterschiedlichen Datentypen speichern.

Mehr Informationen gibt es im vorhergehenden Abschnitt zu Datentypen und Datenstrukturen.

Falls unser Datensatz kein Dataframe ist, können wir ihn folgendermaßen umwandeln:

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.

18.1.3 Plausibilitäts-Check

Im nächsten Schritt lohnt es sich zu überprüfen, ob in den vorangegangen Schritten der Erhebung und Kodierung unserer Daten, Fehler passiert sind. Das ist wichtig damit wir solche Fehler nicht in unsere Analyse übertragen, wo sie sehr viel unwahrscheinlicher auffallen werden. Dafür überprüfen wir, ob die Messniveaus und Ausprägungen unserer interessierenden Variablen auch unseren Erwartungen entsprechen.

Entweder man hat die Daten selbst erhoben und somit Wissen darüber, welche Werte möglich sind, oder man schaut sich die Dokumentation zu den Daten an.

Für den Datensatz airquality finden wir die Dokumentation hier.

Konkretisieren wir einmal einen hypothetischen Fall an der Variable Day. Wir wissen, dass diese mit Zahlen von 1-31 kodiert sein kann. Es wäre also unplausibel, wenn andere Werte (z.B. 0 oder 32) vorliegen würden.

Das Auftauchen von unplausiblen Werten ist z.B. wahrscheinlicher, wenn Daten manuell digitalisiert (d.h. eingetippt) wurden (z.B. Paper-and-Pencil Tests). Gerade in diesen Fällen sollte man sicher gehen, und die Daten auf unplausible Werte hin überprüfen.

Die Funktion unique() gibt uns einen Überblick über alle enthaltenen Ausprägungen eines Vektors. Wenn wir diese mit sapply() kombinieren, können wir das für jede Variable im Datensatz anwenden. Wenn wir den Output wiederum erneut an sapply() übergeben, und sort() anwenden, werden die Ausprägungen aufsteigend sortiert (weil der Default decreasing = FALSE ist), was die Überprüfung erleichtert. In sort() können wir außerdem na.last=TRUE nutzen, um uns das Vorhandensein von 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  22  23  24
[20]  27  28  29  30  31  32  34  35  36  37  39  40  41  44  45  46  47  48  49
[39]  50  52  59  61  63  64  65  66  71  73  76  77  78  79  80  82  84  85  89
[58]  91  96  97 108 110 115 118 122 135 168  NA

$Solar.R
  [1]   7   8  13  14  19  20  24  25  27  31  36  37  44  47  48  49  51  59
 [19]  64  65  66  71  77  78  81  82  83  91  92  95  98  99 101 112 115 118
 [37] 120 127 131 135 137 138 139 145 148 149 150 153 157 167 175 183 186 187
 [55] 188 189 190 191 192 193 194 197 201 203 207 212 213 215 220 222 223 224
 [73] 225 229 230 236 237 238 242 244 248 250 252 253 254 255 256 258 259 260
 [91] 264 266 267 269 272 273 274 275 276 279 284 285 286 287 290 291 294 295
[109] 299 307 313 314 320 322 323 332 334  NA

$Wind
 [1]  1.7  2.3  2.8  3.4  4.0  4.1  4.6  5.1  5.7  6.3  6.9  7.4  8.0  8.6  9.2
[16]  9.7 10.3 10.9 11.5 12.0 12.6 13.2 13.8 14.3 14.9 15.5 16.1 16.6 18.4 20.1
[31] 20.7

$Temp
 [1] 56 57 58 59 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
[26] 82 83 84 85 86 87 88 89 90 91 92 93 94 96 97

$Month
[1] 5 6 7 8 9

$Day
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
[26] 26 27 28 29 30 31

Einen kompakten Überblick über die Verteilung der Variablen können wir mit der Funktion summary() bekommen. Hierbei interessieren uns vor allem die Extremwerte (Min. und Max.), d.h. der Range der Variablen, und die Missings (NA).

summary(airquality)
     Ozone           Solar.R           Wind             Temp      
 Min.   :  1.00   Min.   :  7.0   Min.   : 1.700   Min.   :56.00  
 1st Qu.: 18.00   1st Qu.:115.8   1st Qu.: 7.400   1st Qu.:72.00  
 Median : 31.50   Median :205.0   Median : 9.700   Median :79.00  
 Mean   : 42.13   Mean   :185.9   Mean   : 9.958   Mean   :77.88  
 3rd Qu.: 63.25   3rd Qu.:258.8   3rd Qu.:11.500   3rd Qu.:85.00  
 Max.   :168.00   Max.   :334.0   Max.   :20.700   Max.   :97.00  
 NA's   :37       NA's   :7                                       
     Month            Day      
 Min.   :5.000   Min.   : 1.0  
 1st Qu.:6.000   1st Qu.: 8.0  
 Median :7.000   Median :16.0  
 Mean   :6.993   Mean   :15.8  
 3rd Qu.:8.000   3rd Qu.:23.0  
 Max.   :9.000   Max.   :31.0  
                               

Insgesamt sehen die Daten plausibel aus. Für eine spezifischere Einschätzung der Wetter-Variablen (Ozone, Solar.R, Wind und Temp) könnte man sich zusätzlich Vergleichsdaten von anderen Erhebungen in einem ähnlichen Zeitraum und Gebiet anschauen.

Für den Datensatz PWE_data schauen wir uns hier wieder nur einige Variablen an. Informationen zu den Variablen finden wir in der Codebook, welches sich im Ordner PWE_data befindet.

sapply(sapply(PWE_data[,88:101], unique), sort)
$education
[1] 0 1 2 3 4

$urban
[1] 0 1 2 3

$gender
[1] 0 1 2 3

$engnat
[1] 0 1 2

$age
 [1] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
[26] 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
[51] 63 64 65 66 67 69 70 71 72 74 75 76 77 81 90

$screenw
 [1]    0  320  347  360  375  393  396  412  414  424  455  486  505  570  586
[16]  600  601  640  720  768  780  800  810  834  864  948  960  962 1024 1080
[31] 1093 1111 1138 1152 1200 1242 1280 1300 1348 1360 1364 1366 1368 1376 1400
[46] 1422 1423 1440 1477 1493 1500 1504 1536 1541 1600 1676 1680 1707 1821 1824
[61] 1920 2048 2400 2560 2561 3072 3840

$screenh
 [1]    0  320  347  360  414  480  486  505  568  569  570  576  592  597  601
[16]  614  615  618  640  648  658  667  672  692  698  702  720  731  732  736
[31]  740  741  744  747  748  752  753  758  759  760  762  768  774  780  786
[46]  800  803  808  809  812  819  820  831  846  848  853  854  863  864  866
[61]  869  879  888  892  896  900  902  912  933  943  945  960  962 1000 1003
[76] 1022 1024 1050 1080 1112 1152 1194 1200 1280 1350 1366 1440 1536 1728 1920
[91] 2160

$hand
[1] 0 1 2 3

$religion
 [1]  0  1  2  3  4  5  6  7  8  9 10 11 12

$orientation
[1] 0 1 2 3 4 5

$race
[1] 10 11 12 13 14 15 16 17

$voted
[1] 0 1 2

$married
[1] 0 1 2 3

$familysize
 [1]  0  1  2  3  4  5  6  7  8  9 10 12 13 14 33

Es fällt auf, dass alle dargestellten Variablen (bis auf age), die Ausprägung 0 enthalten, obwohl diese im Codebook für diese Variablen nicht definiert ist. Auch für die anderen Variablen im Datensatz, mit Ausnahme von VCL1, …, VCL16, gilt, dass 0 nicht als mögliche Ausprägung gegeben wird, obwohl sie vorhanden ist. Wir können daher annehmen, dass 0 wahrscheinlich eine alternative Kodierung für NA ist. Besser wäre es natürlich, wenn wir die Wissenschaftler*innen, welche die Daten erhoben haben, kontaktieren und nachfragen würden.

Wie wir sehen, geht die Überprüfung von plausiblen und fehlenden Werten häufig ineinander über. Werte, die außerhalb des Ranges der betrachteten Variable liegen, können eine Kodierung für fehlende Werte darstellen.

Wenn wir unplausible Werte in unseren Daten finden, können wir zu 5. Kodierung ändern springen, und diese umkodieren. Wenn wir fehlkodierte Missings finden, können wir auch case_when() oder alternative Möglichkeiten aus dem Abschnitt [Sind die Missings einheitlich kodiert][Sind die Missings einheitlich kodiert?] des Kapitels Fehlende Werte nutzen (z.B. die im Folgenden illustrierte Umkodierung des gesamten Datensatzes).

Weil wir in PWE_data sehr viele Variablen haben und es zu umständlich wäre, alle einzeln umzukodieren, kodieren wir erst im gesamten Datensatz 0 zu NA um, und ändern danach wieder die Kodierung für die Variablen VCL1 bisVCL16.

# Umkodierung für gesamten Datensatz
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.

18.1.4 Fehlende Werte

Generell werden fehlende Werte (Missings) in R mit NA dargestellt. In anderen Programmen mag das anders sein (z.B. werden Missings in Unipark mit 99 oder -99 kodiert). Wie im vorhergehenden Abschnitt demonstriert, überschneidet sich die Überprüfung von plausiblen und fehlenden Werten häufig.

Neben der im letzten Abschnitt vorgestellten Varianten, Missings mit summary() zu finden, gibt es noch weitere Optionen.

Beispielsweise können wir mit der Kombination von colSums() und is.na() spaltenweise Missings zählen.

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

Achtung: Wenn wir Variablen mit Missings für unsere Analysen nutzen wollen, sollten wir überprüfen, ob die Missings zufällig sind und in Abhängigkeit davon unseren Umgang anpassen, um systematischen Verzerrungen der Analysen entgegenzuwirken.

Einen ausführlichen Überblick zu Missings finden wir im Kapitel Fehlende Werte.

18.1.5 Faktorisieren

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

  • nominalskaliert: gender
    • “What is your gender?”: 1 = Male, 2 = Female, 3 = Other
  • ordinalskaliert: education
    • “How much education have you completed?”: 1 = Less than high school, 2 = High school, 3 = University degree, 4 = Graduate degree.

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.

18.1.6 Wide- und Long-Format

In Abhängigkeit unserer Daten und der Analyse, die wir durchführen wollen, ist es ggf. erforderlich, dass unsere Daten in ein anderes Tabellenformat überführt werden müssen. Es gibt das Wide- und das Long-Format.

Die Unterscheidung von Wide- und Long-Format ist von Bedeutung, wenn unsere Daten eine genestete Struktur aufweisen, das heisst jeweils mehrere Messungen von derselben Untersuchungseinheit vorliegen (z.B. bei Längsschnitterhebungen, mehrere Ratern oder Schülern in Klassen).

Im Wide-Format liegen Messungen einer Untersuchungseinheit in einer Zeile vor. Jeder Messzeitpunkt bzw. jede Messung ist eine eigene Variable.

Beispiel 1 Wide-Format: Messzeitpunkte

Untersuchungseinheit t1 t2 t3
1 4 3 1
2 5 2 3

Beispiel 2 Wide-Format: Rater

Untersuchungseinheit self friend parent
1 2 1 3
2 3 4 2

Im Long-Format liegen Messungen einer Untersuchungseinheit in mehreren Zeilen vor. Alle Messzeitpunkte bzw. Messungen von unterschiedlichen Ratern liegen in einer Variable vor und die Messzeitpunkte bzw. Rater werden in einer separaten Variable kodiert.

Beispiel 1 Long-Format: Messzeitpunkte

Untersuchungseinheit Zeitpunkt Messung
1 1 4
1 2 3
1 3 1
2 1 5
2 2 2
2 3 3

Beispiel 2 Long-Format: Rater

Untersuchungseinheit Rater Messung
1 self 2
1 friend 1
1 parent 3
2 self 3
2 friend 4
2 parent 2

Im Beispieldatensatz PWE_data gibt es keine wiederholte Messungen. Psychometrische und demographische Daten wurden einmalig erhoben. Hierfür gibt es keine Notwendigkeit der Formatierung vom Long- ins Wide-Format oder vice-versa.

Im Beispieldatensatz airquality gibt es wiederholte Messungen der Untersuchungseinheiten (Ozone, Solar.R, Wind und Temp) zu unterschiedlichen Zeiten, die in Month und Day kodiert werden. Jede dieser Untersuchungseinheiten liegt in mehreren Zeilen vor. Es handelt sich folglich um einen Datensatz im Long-Format. Im Wide-Format hätten wir z.B. die Variablen Ozone_5_1 (Monat 5, Tag 1), Ozone_5_2 (Monat 5, Tag 2), …, Solar.R_5_1 (Monat 5, Tag 1), etc. Je nachdem, wie wir die Daten auswerten wollen, ist es notwendig bzw. nicht notwendig, die Daten umzuformatieren.

Im Kapitel zum (Wide- und Long-Format) erfahren wir, wie wir beide Formate ineinander überführen können. Hierzu werden jeweils zwei Möglichkeiten vorgestellt: reshape() aus dem Standardpaket stats und spread() bzw. gather() aus dem Paket tidyr.

18.2 Datensätze zusammenführen

Synonyme: Mergen, Fusionieren, Integrieren

Nicht immer haben wir das Glück, dass die für uns relevanten Daten in einem gemeinsamen Dataframe vorliegen. Daher schauen wir uns nachfolgend an, wie man Dataframes zusammenführen kann. Es gibt dabei zwei Szenarien, die man unterscheiden kann:

  • die selben Variablen von verschiedenen Fällen
    • z.B. von einer erneuten Aufnahme von Personen in eine Studie
    • hier werden die Zeilen “ergänzt”
  • verschiedene Variablen von den selben Fällen
    • z.B. wenn einzelne Abschnitte einer Studie (Tests, Fragebögen) in unterschiedlichen Datensätzen gespeichert wurden
    • hier werden die Spalten “ergänzt”

Wir schauen uns wieder Funktionen aus zwei verschiedenen Paketen an: merge() aus dem Basispaket base und bind_rows() bzw. die _join()-Funktionen aus dem Zusatzpaket dplyr.

Wir nutzen dafür die Datensätze vornamen_13 und vornamen_14, in denen die Vornamen der Neugeborenen in München, jeweils für die Jahre 2013 und 2014 enthalten sind. Diese Datensätze eignen sich für beide Szenarien, weil sowohl dieselben Variablen (vorname, anzahl und geschlecht) als auch dieselben Fälle (d.h. Vornamen) in beiden Datensätzen vorkommen (z.B. Maximilian). Die Untersuchungseinheiten sind hier also nicht einzelne Personen, sondern Vornamen. Was bei der Untersuchung einzelner Personen die ID-Variable ist, ist hier die Variable vorname.

Achtung: Es kann sein, dass wir einen Dataframe nach dem Zusammenführen noch in ein anderes Format überführen müssen, um unsere Auswertung durchführen zu können (siehe Wide- und Long-Format).

18.2.1 Selbe Variablen, unterschiedliche Fälle

Die beiden nachfolgend vorgestellten Funktionen unterscheiden sich bezüglich einiger Funktionalitäten.

Ein wichtiger Unterschied ist, dass merge() beim Zusammenführen der Dataframes gleiche Fälle (d.h. Fälle mit gleichen Ausprägungen in den Variablen) nur einmalig übernimmt (d.h. Dopplungen löscht) während bind_rows() alle Fälle übernimmt, auch wenn sich diese doppeln.

In Abhängigkeit der geplanten Nutzung der Daten sollten wir individuell entscheiden, welche Funktion wir nutzen wollen.

merge()

Mit merge() können wir zwei Dataframes, deren Namen wir der Funktion übergeben, vertikal zusammenführen.

Wir müssen dabei unbedingt all = TRUE spezifizieren, weil der Default (all = FALSE) nur Zeilen behält, die in beiden Dataframes mit genau der gleichen Ausprägung auf den Variablen vorhanden sind (und von diesen jeweils nur eine Version).

Mit all.x = TRUE bzw. all.y = TRUE würden wir, neben den Fällen mit den gleichen Ausprägungen in beiden Datensätzen, auch alle nur im ersten bzw. zweiten Datensatz enthaltenen Fälle behalten.

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.

Achtung: Wir sollten mit merge() demnach auch nur Dataframes zusammenführen, die genau dasselbe Set an Variablen haben (d.h. ein Dataframe sollte nicht noch eine zusätzliche Variable besitzen) oder wir sollten eine ID-Variable haben, deren IDs nur einmal vorkommen (sowohl innerhalb eines Dataframes als auch zwischen den Dataframes). Sonst kann es zum ungewollten Nicht-Übernehmen von Fällen kommen.

Schauen wir uns das Problem an einem Beispiel an:

Nehmen wir an, dass ein Dataframe x eine zusätzliche Variable a hat. Für die Fälle des anderen Dataframe y würden auf a im zusammengeführten Objekt nur fehlende Werte (NA) stehen. So würde die Funktion solche Fälle (im Vergleich der beiden Dataframes), die bis auf die Variable a die gleichen Ausprägungen haben, nicht übernehmen.

x und y haben jeweils drei Fälle. x hat drei Variablen; y hat zwei.

x
  c b a
1 5 5 5
2 6 6 6
3 7 7 7
y
  c b
1 5 7
2 7 5
3 6 6
merge_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.

18.2.2 Unterschiedliche Variablen, selbe Fälle

Im Gegensatz zu merge() und bind_rows() unterscheiden sich merge(..., by) und die _join()-Funktionen nicht in ihrer Funktionalität, sondern nur in der Reihenfolge der Fälle im zusammengeführten Dataframe.

Allerdings kann man die _join()-Funktionen auch alternativ zu bind_rows() nutzen. Dann unterscheidet sich das Ergebnis im Vergleich zu merge() auch lediglich in der Reihenfolge der Fälle. Mehr Infos dazu finden wir im Abschnitt [_join()].

merge(..., by)

Die Funktion merge() können wir auch nutzen, um Spalten aneinander zu heften.

Mit dem Argument by geben wir an, welche ID-Variable die (selben) Fälle kodiert.

Wenn es unterschiedliche Benennungen der gleichen ID-Variablen in den beiden Datensätzen gibt, müssen wir by.x und by.y nutzen. Die Benennung von by.x wird dann übernommen.

x und y spielen auf die Reihenfolge an, in welcher wir die Datensätze an die Funktion merge() übergeben. x ist der zuerst übergebene Datensatz; y der als zweites übergebene.

Wir wollen auch hier wieder die Daten aus beiden Dataframes übernehmen, und geben das mit all = TRUE an.

Wir könnten aber hier ebenso all.x = TRUE bzw. all.y = TRUE oder all = FALSE nutzen.

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")
Warning in full_join(vornamen_13, vornamen_14, by = "vorname"): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 3 of `x` matches multiple rows in `y`.
ℹ Row 1292 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.

Nun überprüfen wir wieder die Dimensionen des neu erstellten Dataframes.

dim(vornamen_join)
[1] 6371    5

Wir sehen, dass der mit full_join(..., by = "vorname") zusammengeführte Datensatz genau die gleichen Dimensionen hat wie der mit merge(..., by = "vorname", all = TRUE) zusammengeführte. Die beiden Funktionen unterscheiden sich nur in der Sortierung der Fälle (welcher Dataframe zuerst eingegeben wurde vs. natürliche Sortierung der Fälle).

full_join() als Alternative zu bind_rows()

Mit full_join(x, y) bekommen wir (bis auf die Sortierung der Fälle) das gleiche Ergebnis wie bei merge(x, y, all = TRUE)

vornamen_join_row <- full_join(vornamen_13, vornamen_14)
Joining with `by = join_by(vorname, anzahl, geschlecht)`

Zur Demonstration der Übereinstimmung schauen wir uns die Dimensionen und den Aufbau des Dataframes (am Beispiel der ersten 6 Zeilen) an.

dim(vornamen_join_row)
[1] 7496    3
vorname anzahl geschlecht
Maximilian 166 m
Felix 124 m
Anna 109 w
David 109 m
Sophia 108 w
Emilia 103 w
dim(vornamen_merge_row)
[1] 7496    3
vorname anzahl geschlecht
+ 7 m
+ 9 w
Aadhavan 2 m
Aadhya 1 w
Aahana 1 w
Aahel 1 m

18.3 Daten extrahieren

Synonyme: Splitten, Subsetten, Filtern, Selektieren, Extrahieren

Manchmal möchten wir nur bestimmte Variablen bzw. bestimmte Fälle aus einem Datensatz betrachten. Generell bietet es sich an, dafür reguläre Ausdrücke (regular expressions z.B. die Metacharactere .*, |, ^ und $) und logische Operatoren (logical operators z.B. >, < und ==) zu nutzen.

Wie wir Variablen (Spalten) und Fälle (Zeilen) selektieren und in einem neuen Dataframe speichern können, schauen wir uns nun an.

18.3.1 Variablen

Wenn wir nur einige Variablen aus einem bzw. aus mehreren Datensätzen benötigen, können wir diese mit verschiedenen Möglichkeiten entnehmen. Im Folgenden schauen wir uns dafür Möglichkeiten aus dem Standardpacket base und dem Zusatzpaket dplyr an.

Unten befindet sich eine Übersicht, der wir entnehmen können, welche Methode wir wählen sollten in Abhängigkeit davon, ob die Variablen, die wir extrahieren wollen, ähnlich oder unterschiedlich sind.

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

18.3.1.1 Datums-Variablen splitten

Für den Fall, dass wir eine Datums-Variable in unserem Datensatz haben, welche in einem für uns unangemessenen Format vorliegt, können wir diese mit dem Paket lubridate umformatieren. Auf dieser Seite wird der Umgang mit den im Paket enthaltenen Funktionen ymd() und mdy() erklärt.

18.3.2 Fälle

Wir schauen uns nachfolgend einige Möglichkeiten der Extraktion von Fällen mit spezifischen Ausprägungen (die man z.B. für eine Subgruppenanalyse benötigt) an. Auch hier schauen wir uns wieder sowohl Funktionen aus dem Standardpaket base als auch aus dem Zusatzpaket dplyr an.

Unten befindet sich eine Übersicht, der wir entnehmen können, welche Methode wir wählen sollten in Abhängigkeit davon, ob die Fälle, die wir extrahieren wollen, ähnlich oder unterschiedlich sind.

Die 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  28  31  34
[20]  49  51  53  54  55  85  88  90  96 103 104 110 118 122 135 140 141 142 144
[39] 147 148 152 153
grep("6", airquality$Temp, value=TRUE) # Werte
 [1] "67" "62" "56" "66" "65" "61" "69" "69" "66" "68" "64" "66" "68" "62" "61"
[16] "61" "67" "76" "67" "65" "76" "76" "76" "76" "86" "86" "86" "86" "86" "86"
[31] "76" "86" "96" "76" "67" "76" "68" "64" "69" "63" "76" "68"

Wenn eine Ausprägung eine bestimmte Zahlen- oder Zeichenketten zu Beginn enthalten soll, setzen wir ein ^ vor diese.

Wenn wir beispielsweise alle Tage suchen, an denen die Temperatur (Temp) im Bereich 60-69°F, dann können wir das folgendermaßen tun:

Diese Suche könnten wir auch mit den logischen Operatoren durchführen.

grep("^6", airquality$Temp) # Indizes
 [1]   1   4   6   7   9  10  12  13  14  16  17  19  20  23  24  28  34  49 140
[20] 142 144 147 148 153
grep("^6", airquality$Temp, value=TRUE) # Werte
 [1] "67" "62" "66" "65" "61" "69" "69" "66" "68" "64" "66" "68" "62" "61" "61"
[16] "67" "67" "65" "67" "68" "64" "69" "63" "68"

Wenn eine Ausprägung eine bestimmte Zahlen- oder Zeichenketten am Ende enthalten soll, setzen wir ein $ ans Ende.

Wenn wir beispielsweise alle Tage suchen, an denen die Temperaturangabe (Temp) mit einer 6 endet, dann können wir das folgendermaßen tun:

grep("6$", airquality$Temp) # Indizes
 [1]   5   6  13  17  31  51  53  54  55  85  88  90  96 103 104 110 118 122 135
[20] 141 152
grep("6$", airquality$Temp, value=TRUE) # Werte
 [1] "56" "66" "66" "66" "76" "76" "76" "76" "76" "86" "86" "86" "86" "86" "86"
[16] "76" "86" "96" "76" "76" "76"

Wie beim Extrahieren von Variablen können wir auch hier mit grep() verschiedene Bestandteile einer Ausprägung anhand des logischen Operators | suchen.

Als Beispiel suchen wir Temperaturangaben, die zu Beginn eine 5 enthalten oder mit einer 5 enden.

grep("^5|5$", airquality$Temp) # Indizes
 [1]   5   7   8  15  18  21  25  26  27  36  49  56  63  81  86  97 115 132 151
grep("^5|5$", airquality$Temp, value=TRUE) # Werte
 [1] "56" "65" "59" "58" "57" "59" "57" "58" "57" "85" "65" "75" "85" "85" "85"
[16] "85" "75" "75" "75"

Achtung: Die Zahlen bzw. Zeichenketten dürfen nicht durch Freizeichen getrennt werden, z.B. würden mit "6| ^7" nur Temperaturangaben gefiltert werden, die eine 6 enthalten.

Die Suche mit "^x|x$" ergibt gemeinsam die globale Suche nach "x".

Wenn wir hingegen mehrere Bedingungen verknüpfen wollen, z.B. "^x" und "x\$", dann nutzen wir .*, z.B. "^x.x\$" (für ein Beispiel siehe [mutate(): Zusammenfassung aller Fälle]).

Ähnlich zum Abschnitt zu Variablen mit grep() extrahieren wenden wir die Selektion mit der Form Datensatz[grep(),] an, und speichern diese in einem neuen Objekt.

Wir können hierfür nur den Indizes-Vektor (value=FALSE; Default) nutzen.

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.

18.4 Daten sortieren

Für manche Vorhaben, wie z.B. grafische Darstellungen oder dem Quantifizieren von Heteroskedastizität, benötigt man sortierte Daten.

Wir schauen uns nachfolgend zwei Möglichkeiten an, einen Dataframe nach den Ausprägungen seiner Variablen zu sortieren.

order()

Nachfolgend sehen wir, wie man mit order() aufsteigend sortiert.

Weil mit order() nur Zeilenindizes ausgegeben werden, müssen wir diese noch auf den Dataframe anwenden. Das machen wir mit der Form Datensatz[order(Variable),].

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

Beispielsweise sehen wir hier, dass die Fälle 27, 25 und 18 (auf Seite 1 in den Zeilen 2, 3 und 4) anders sortiert sind als oben.

Wenn wir nach den absteigenden Werte der Variablen sortieren wollen, hängen wir jeweils ein - vor diese. Alternativ können wir auch das Argument decreasing=TRUE setzen (dann werden aber, im Gegensatz zu unserem Beispiel, alle Variablen absteigend sortiert).

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)

18.5 Kodierung ändern

Wenn die Daten nicht in einer angemessenen Kodierung vorliegen, muss man diese nachträglich anpassen bzw. neu erstellen. Die Kodierung einer Variablen ist von ihrem Messniveau abhängig.

Für dieses Kapitel beschränken wir uns auf die Nutzung des Datensatzes PWE_data, welchen wir zu Beginn heruntergeladen haben.

18.5.1 Umkodieren

Wenn wir zum Beispiel Messwerte in Meter zu Messwerten in Zentimeter ändern oder negativ gepolte Items umpolen wollen, spricht man von Umkodieren.

Wir können die Kodierung von Merkmalen in R auf verschiedene Arten ändern. Nachfolgend schauen wir uns zwei Funktionen an, die wir nutzen können.

recode()

In der Funktion recode() (auch Recode() möglich) aus dem Paket car müssen wir grundsätzlich zwei Argumente spezifizieren: var und recodes. Ersterem übergeben wir die umzukodierende Variable, zweiterem die alte und die neue Kodierung der Variablen.

Wir müssen hierbei einige syntaktische Besonderheiten von recodes beachten:
recodes = "alt_1 = neu_k; alt_2 = neu_k-1; ...; alt_k = neu_1"
(Ausprägungen der Kodierung von 1 bis \(k\))

  • die Input=Output-Parameter müssen gemeinsam als Zeichenkette (d.h. in " ") vorliegen
  • die verschiedenen Input=Output-Parameter müssen mit Semikolon (;) getrennt werden

Wir invertieren im Folgenden die Werte der Variablen Q9A, Q13A und Q15A (numeric). Die Information, dass die Variablen negativ kodiert sind, finden wir in Mirels & Garrett (1971) (nur über HU-VPN zugänglich). Informationen zur Messskala finden wir auch im Codebuch.

So sehen die Daten (der ersten 10 Personen) bisher aus:

Jetzt invertieren wir die Skalen:

library(car)
PWE_data$Q9A <- recode(var=PWE_data$Q9A,
                       recodes="1=5; 2=4; 3=3; 4=2; 5=1")

PWE_data$Q13A <- recode(var=PWE_data$Q13A,
                       recodes="1=5; 2=4; 3=3; 4=2; 5=1")

PWE_data$Q15A <- recode(var=PWE_data$Q15A,
                       recodes="1=5; 2=4; 3=3; 4=2; 5=1")

Abschließend überprüfen wir (visuell), ob die Umkodierung geklappt hat:


Wenn die Kodierung aus Zeichenketten (character) besteht, müssen wir diese jeweils noch mit ' ' umschließen.

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

Mit dem Parameter as.factor legen wir fest, ob die (neue) rekodierte Variable als Faktor gespeichert werden soll.

Achtung: Leider können wir so aber nur ungeordnete (nominalskaliert) und keine geordneten (ordinalskaliert) Faktoren erstellen. Dafür müssten wir auf die Funktion factor(..., ordered = TRUE, levels) zurückgreifen (siehe Abschnitt Faktorisieren).

# aus Gründen der Darstellung speichern wir die Kodierungen zuerst in einem String:
recode <- c("1='Less than High School';2='High School';3='University Degree';4='Graduate Degree'")
PWE_data$education_new <- recode(var=PWE_data$education,
                                 as.factor=TRUE,
                                 recodes=recode)

Abschließend vergleichen wir die Daten (der ersten 10 Personen) von education und education_new:

case_when()

Mit der Funktion case_when() aus dem Paket dplyr können wir für verschiedene Fälle (d.h. Bedingungen) der ursprünglichen Variable angeben, wie diese umkodiert werden soll. Auf die linke Seite schreiben wir eine logische Bedingung (z.B. größer als mit >); auf die rechte Seite die neue Kodierung. Verbunden werden beide mit einer Tilde (~).

Im Gegensatz zu recode() können wir durch die Verwendung von logischen Operatoren (z.B. >) ganzen Zahlenintervallen dieselbe Kodierung zuweisen.

Als Beispiel bilden wir Kategorien für das intervallskalierte Merkmal Q1E (mit Frage Q1 verbrachte Zeit in Millisekunden). Den Range des Merkmals erfahren wir mit range(PWE_data$Q1E, na.rm=TRUE) (195 - 181.246). Wir teilen Q1E in vier gleich breite Kategorien ein: \([195, 45457.75), [45457.75, 90720.5), [90720.5, 135983.2), [135983.2, 181247)\).

Hinweis: [ heißt inklusive, ) heißt exklusive. Bei der oberen Grenze wird aufgerundet.

# library(dplyr)

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

str(PWE_data$Q1E_kat) # Überprüfung Datentyp
 num [1:1350] 1 1 1 1 1 1 1 1 1 1 ...
unique(PWE_data$Q1E_kat) # um alle Kategorien zu sehen
[1]  1  2  4  3 NA

Nun haben wir eine neue Variable Q1E_kat erstellt, welche zusammengefasste Informationen aus Q1E enthält. Die neu erstellte Variable liegt als numeric vor, d.h., dass wir diese noch faktorisieren und ordnen müssen, damit sie als ordinalskaliert gehandhabt wird.

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

str(PWE_data$Q1E_kat) # Überprüfung Datentyp
 Ord.factor w/ 4 levels "1"<"2"<"3"<"4": 1 1 1 1 1 1 1 1 1 1 ...

18.5.2 Indikatorvariablen: Kodierung nominaler Merkmale

Nominale Merkmale kann man in Form von Dummy-, Effekt- und Kontrastkodierungen repräsentieren. Eine solche Repräsentation ist vor allem im Rahmen des Allgemeinem Linearen Modells (ALM) von Interesse.

Untenstehende Tabelle gibt einen groben Überblick über die Interpretation der Parameter des ALM in Abhängigkeit der Kodierung.

Intercept b<sub>0</sub> Steigung b<sub>j</sub> mögliche Anwendung
<b>Dummy</b> Mittelwert in der Referenzgruppe Differenz des Mittelwerts der j-ten Gruppe zur Referenzgruppe Vergleich von Experimental- und Kontrollgruppe
<b>Effekt</b> Mittelwert der Gruppenmittelwerte (Gesamtmittelwert) Differenz des Mittelwertes der j-ten Gruppe zum Gesamtmittelwert Vergleich von Gruppen in varianzanalytischen Designs
<b>Kontrast</b> Mittelwert über die Mittelwerte der Kontrastgruppen lässt sich als Funktion der Kontrastkoeffizienten darstellen, die den jeweiligen Kontrast kodieren |Gezielte Einzelvergleiche von (Kombinationen) von Gruppen
Die Interpretation der Mittelwerte und Differenzen hängt zusätzlich davon ab, ob ein balanciertes oder unbalanciertes Design vorliegt (d.h. ob die Gruppengrößen gleich oder ungleich sind).

Für mehr Informationen zu Indikatorvariablen können wir z.B. folgende Quelle nutzen:

Bortz, J., & Schuster, C. (2010). Allgemeines lineares Modell. In J. Bortz, & C. Schuster (Eds.), Statistik für Sozialwissenschaftler (S.363-384). Heidelberg: Springer
(für HU-Studierende über ub.hu-berlin.de zugänglich)

Im Folgenden schauen wir uns an, wie man konkret bei der Erstellung der verschiedenen Arten der Indikatorvariablen vorgehen kann.

Dafür nutzen wir die im Abschnitt Faktorisieren erstellte Variable gender_uf aus PWE_data_fac.

Zusätzlich rechnen wir jeweils eine einfache lineare Regression mit den verschiedenen Kodierungen, um die Unterschiede zwischen den Koderierungsarten zu veranschaulichen.

Wir regredieren dafür die Zeit in Sekunden, die die Probanden auf der Instruktionsseite verbracht haben (introelapse), auf ihr Geschlecht (gender_uf).

Achtung: Die nachfolgend vorgestellten Funktionen lassen sich auch auf ordinalskalierte Merkmale (d.h. sortierte Faktoren) anwenden. Bei diesen unterscheidet sich aber die Interpretation der geschätzten Koeffizienten der Regression. Wir bekommen Schätzungen für lineare (L), quadratische (Q) und kubische (C) Trends. Daher behandeln wir im folgenden Abschnitt nur die Kodierung von nominalskalierten Merkmalen.

Dummy-Kodierung

Viele Funktionen in R (z.B. lm(), lme() und lmer()) kodieren nominalskalierte Variablen intern automatisch nach der Dummy-Kodierung um, wenn diese vorher als Faktoren deklariert wurden. Dabei wird die erste Kategorie als Referenzkategorie genutzt. Wenn wir eine andere Referenzkategorie haben wollen, können wir dafür die im Folgenden vorgestellten Funktionen (C(..., contr.treatment(n, base)) oder relevel()) nutzen.

Man benötigt für eine Dummykodierung mit \(k\)-Kategorien \(k-1\) Indikatorvariablen. Die jeweils interessierende Gruppe wird in der jeweiligen Indikatorvariablen mit 1 kodiert; die anderen mit 0. Als Referenzkategorie (von der die Abweichung berechnet wird) gilt jene, welche in allen Indikatorvariablen mit 0 kodiert wird.

Die Indikatorvariablen erstellt uns die Funktion contr.treatment() automatisch, wenn wir die Anzahl der Faktorstufen (n) und die Referenz (base) angeben.

Um die faktorisierte Variable gender_uf, welche 3 Ausprägungen hat, zu kodieren, benötigen wir 2 Dummy-Variablen. Wir wählen die erste Kategorie (1 = Male) als Referenzkategorie.

contr.treatment(n=3, base=1)
  2 3
1 0 0
2 1 0
3 0 1

C(Faktor, contr.treatment(n, base)) setzt die Konstraste für den kategorialen Prädiktor innerhalb von lm():

lm_ct<- lm(introelapse ~ C(gender_uf, contr.treatment(3, 1)), PWE_data) 
summary(lm_ct)

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

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

Coefficients:
                                     Estimate Std. Error t value Pr(>|t|)  
(Intercept)                              2249       1038   2.166   0.0305 *
C(gender_uf, contr.treatment(3, 1))2    -1466       1496  -0.980   0.3274  
C(gender_uf, contr.treatment(3, 1))3    -1847       4339  -0.426   0.6703  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 26970 on 1340 degrees of freedom
  (7 observations deleted due to missingness)
Multiple R-squared:  0.0007689, Adjusted R-squared:  -0.0007224 
F-statistic: 0.5156 on 2 and 1340 DF,  p-value: 0.5973

Nun vergleichen wir das Regressionsmodell mit den Dummy-kodierten Indikatorariablen (C(gender_uf, contr.treatment(n=4, base=1)) mit dem Regressionsmodell mit dem unsortierten Faktor (gender_uf), bei dem ebenfalls die erste Kategorie als Referenzkategorie genutzt wird.

lm_uf <- lm(introelapse ~ gender_uf, PWE_data)
summary(lm_uf)

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

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

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept)     2249       1038   2.166   0.0305 *
gender_uf2     -1466       1496  -0.980   0.3274  
gender_uf3     -1847       4339  -0.426   0.6703  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 26970 on 1340 degrees of freedom
  (7 observations deleted due to missingness)
Multiple R-squared:  0.0007689, Adjusted R-squared:  -0.0007224 
F-statistic: 0.5156 on 2 and 1340 DF,  p-value: 0.5973

Da die Referenzkategorie identisch ist, sehen wir, dass wir bei beiden die gleichen Ergebnisse erhalten.

Bei der Dummykodierung entspricht unser Interzept \(b_0\) dem ungewichteten Mittelwert in der Referenzkategorie (1 = Male). Die partiellen Steigungsgewichte \(b_j\) (C(...)2 und C(...)3 bzw. gender_uf2 und gender_uf3) entsprechen den ungewichteten Mittelwertsunterschieden zwischen der jeweiligen Gruppe (2 = Female bzw. 3 = Other) und der Referenzgruppe (1 = Male).

Alternativ zu C(..., contr.treatment()) können wir mit der Funktion relevel() die Referenzkategorie eines unsortierten Faktors ändern. Standardmäßig ist immer die erste Gruppe nach der natürlichen Reihenfolge (bei Zahlen aufsteigend und bei Buchstaben alphabetisch) die Referenzkategorie.

Dem Argument ref übergeben wir die derzeitige Position der gewünschten Referenzkategorie.

# Beispiel: Female als Referenzkategorie
PWE_data$gender_uf_ref <- relevel(PWE_data$gender_uf, ref = 2)

ls.str(PWE_data[,c(103, 106)]) # zum Überprüfen
education_new :  Factor w/ 4 levels "Graduate Degree",..: 4 2 2 2 2 1 1 2 3 2 ...
gender_uf :  Factor w/ 3 levels "1","2","3": 1 2 2 2 2 2 1 3 2 1 ...
Effektkodierung

Für diese Kodierung benötigen wir ebenfalls \(k-1\) Indikatorvariablen. Die jeweils zutreffende Gruppe wird in der jeweiligen Indikatorvariablen mit 1 kodiert; die nicht zutreffende mit 0 (ebenso wie bei der Dummy-Kodierung). Die “Referenzkategorie” (in unserem Beispiel No) wird in allen Indikatorvariablen mit -1 kodiert.

Es gibt eigentlich keine echte Referenzkategorie (wie bei der Dummy-Kodierung). Vielmehr entspricht der Interzept dem Mittelwert über alle Gruppen hinweg.

Analog zu contr.treatment(n, ...) bei der Dummy-Kodierung können wir contr.sum(n) nutzen, um eine effektkodierte Matrix eines Faktors zu erstellen.

contr.sum(n=3)
  [,1] [,2]
1    1    0
2    0    1
3   -1   -1

Wir müssen lediglich in n spezifizieren, wie viele Faktorstufen es gibt. Auch hier übergeben wir den Output der Funktion an C(), wenn wir z.B. eine lineare Regression mit lm() berechnen wollen.

lm_cs <- lm(introelapse ~ C(gender_uf, contr.sum(3)), PWE_data)
summary(lm_cs)

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

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

Coefficients:
                            Estimate Std. Error t value Pr(>|t|)
(Intercept)                   1144.4     1490.2   0.768    0.443
C(gender_uf, contr.sum(3))1   1104.5     1606.2   0.688    0.492
C(gender_uf, contr.sum(3))2   -361.5     1614.8  -0.224    0.823

Residual standard error: 26970 on 1340 degrees of freedom
  (7 observations deleted due to missingness)
Multiple R-squared:  0.0007689, Adjusted R-squared:  -0.0007224 
F-statistic: 0.5156 on 2 and 1340 DF,  p-value: 0.5973

Bei der Effektkodierung entspricht unser Interzept \(b_0\) dem Mittelwert der ungewichteten Gruppenmittelwerte. Die partiellen Steigungsgewichte \(b_j\) (C(...)1 und C(...)2) entsprechen der Differenz des Mittelwerts der jeweiligen Gruppe (1 = Male bzw. 2 = Female) zum Mittelwert der ungewichteten Gruppenmittelwerte.

Leider können wir mit contr.sum() nur die erste Faktorstufe als “Referenzkategorie” nutzen. Mit ref() können wir jedoch wieder die Sortierung der Faktorstufen ändern und den umsortierten Faktor dann wieder an C(..., contr.sum(n)) übergeben.

# Beispiel: Female als Referenzkategorie
PWE_data$gender_uf_ref <- relevel(PWE_data$gender_uf, ref = 2)

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

Bei der Kontrastkodierung können wir uns eigens gewählte Kontraste zwischen verschiedenen Gruppen anschauen. Die Gewichte \(c_i\) eines Kontrastes müssen der Bedingung genügen, dass die Summe der Gewichte über die Anzahl der zu kodierenden Kategorien \(i\) null ist, d.h. \(\sum\limits_{i} c_i = 0\).

Die jeweilige Kodierung mit 0 in einer Indikatorvariablen sorgt dafür, dass eine Gruppe bzw. ein Fall nicht mit in einen Kontrast eingeht.

Bei multiplen Kontrasten (d.h. mindestens zwei kontrastkodierten Variablen) können wir orthogonale (d.h. unkorrelierte) und nicht orthogonalen (d.h. korrelierte) Kontraste unterscheiden.

Zwei Kontraste \(j\) und \(j'\) sind orthogonal wenn zusätzlich zur oberen Bedingung gilt: \(\sum\limits_{i} \, c_{ij} \cdot c_{ij'} = 0\)

Im Folgenden werden wir nur einen Kontrast erstellen. Für mehr Informationen zur Orthogonalität von multiplen Kontrasten können wir z.B. bei Bortz & Schuster (2010) nachschauen.

Wir kontrastieren Männer und Frauen hinsichtlicher der verbrachten Zeit auf der Instruktionsseite (introelapse).

Dafür sortieren wir den Datensatz PWE_data_fac zuerst mit order() nach gender_uf. Die Variable hat eine Zahlen-Kodierung: Männer sind gender_uf = 1, Frauen gender_uf = 2 und Andere gender_uf = 3.

PWE_data <- PWE_data[order(PWE_data$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$gender_uf, useNA = 'ifany')

   1    2    3 <NA> 
 675  627   41    7 
# Kontrast erstellen
PWE_data$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.

Achtung: Wenn wir ungleich große Gruppen haben, wie in unserem Beispiel, dann liegen die einzelnen Gewichtungen als Brüche vor z.B. \(c_{Männer} = \frac{1}{675}\) und \(c_{Frauen} = -\frac{1}{627}\). Wenn wir die Elemente unserer kontrastkodierten Variablen gender_kontrast aufsummieren, erhalten wir -5.5944832^{-17}. Damit genügen wir de facto der Bedingung \(\sum\limits_{i} c_i = 0\) nicht, aber die Zahl ist so klein (d.h. so nah an 0), dass wir sie vernachlässigen können.

Jetzt nehmen wir die kontrastkodierte Variable als Prädiktor in ein neues Regressionsmodell auf.

lm_kontr <- lm(introelapse ~ gender_kontrast, PWE_data)
summary(lm_kontr)

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

Residuals:
   Min     1Q Median     3Q    Max 
 -2464  -2451   -998   -985 961038 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)  
(Intercept)       1758.6      775.3   2.268   0.0235 *
gender_kontrast 476517.1   513617.1   0.928   0.3537  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 28490 on 1348 degrees of freedom
Multiple R-squared:  0.0006381, Adjusted R-squared:  -0.0001032 
F-statistic: 0.8608 on 1 and 1348 DF,  p-value: 0.3537
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.

18.6 Summary-Variablen

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

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

Achtung: Die Werte der Variablen Q9A, Q13A und Q15A, die wir im Folgenden nutzen werden, wurden im Abschnitt Umkodieren rekodiert, weil sie negativ gepolt sind.

rowSums(), rowMeans() und select(): Summen- und Mittelwerte

Wir schauen uns im Folgenden an, wie man Summen- und Mittelwerte von mehreren Variablen erstellt. Dieses Vorgehen ist beispielsweise für die Erstellung von Skalenwerten in der Testkonstruktion von großer Relevanz.

Wir schauen uns den Summen- sowie den Mittelwert über alle Items der Protestant Work Ethic Scale an. Die Fragen wurden auf einer intervallskalierten Skala - von 1 (stimme nicht zu) bis 5 (stimme zu) - beantwortet und in den Variablen, die mit einem Q beginnen und einem A enden gespeichert.

Um einen Summenwert, d.h. eine Summe einer Person über mehrere Variablen, zu bilden, können wir auf die Funktion rowSums() zurück greifen. Wenn wir fehlende Werte in den Variablen haben, setzen wir das Argument na.rm=TRUE.

Es ist zusätzlich sinnvoll, mit einer Kombination aus select(), matches() und den regulären Ausdrücken, die relevanten Variablen auszuwählen. Dafür laden wir das Paket dplyr.

matches() ist eine select helper Funktion, welcher man reguläre Ausdrücke übergeben kann. Es gibt noch weitere Hilfsfunktionen, die man auch als Alternative zu regulären Ausdrücken nutzen kann z.B. starts_with(x) anstatt ^x. Wenn wir aber mehrere Bedingungen haben, sollten wir matches() und den regulären Ausdruck .* (zur konjunktiven Verknüpfung) nutzen.

# library(dplyr)
PWE_data$sum <- rowSums(select(PWE_data, matches("^Q.*A$")), na.rm=TRUE)

Nun schauen wir uns die neu erstellte Variable (für die die ersten 10 Personen) einmal an:

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

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

Abschließend schauen wir uns wieder die neu erstellte Variable (für die die ersten 10 Personen) an:

ifelse(): Auswahl bestimmter Fälle

Jetzt schauen wir uns an, wie wir eine Variable nur für eine bestimmte Gruppe erstellen können.

Dafür nutzen wir die Funktion ifelse(test, yes, no). Mit dieser testen wir, ob eine oder mehrere Bedingungen (test) zutreffen und geben an, was mit den Fällen passieren soll, auf die die Bedingung(en) zutreffen (yes) und jene, auf die diese nicht zutreffen (no).

Für unser Beispiel sollen alle Fälle, auf die die Bedingung(en) nicht zutreffen, ein NA auf der neu erstellten Variablen erhalten.

Wenn wir mehrere Bedingungen nutzen wollen, können wir auf die logischen Operatoren zurückgreifen. Mit & geben wir an, dass beide Bedingungen zutreffen sollen; mit | dass eine Bedingung zutreffen soll. Mit runden Klammern können wir Bedingungen noch differenzierter angeben z.B. (A | B) & C heißt, dass entweder A oder B eine (noch festzulegende) Ausprägung erfüllen müssen und zusätzlich noch C. Mehr Informationen zu logischen Operatoren finden wir im gleichnamigen Abschnitt.

Wir nutzen das gleiche Beispiel wie im Abschnitt vorher, nur dass wir jetzt nur den Summenwert für weibliche (gender == 2) Buddhistinnen (religion == 3) bilden wollen.

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

Schauen wir uns die neu erstellte Variable sowie die Gruppierungsvariablen (der Personen 687 bis 691 an, unter denen sich 2 von insgesamt 9 weibliche Buddhistinnen befinden) einmal an:

mutate(): Summary-Variablen als Funktion von anderen Variablen erstellen

Wenn wir neue Variablen erstellen wollen, die Funktionen von bestehenden Variablen sind, können wir die Funktion mutate() aus dem Paket dplyr nutzen. Besonders nützlich hierbei ist, dass wir mit dem Parameter .keep festlegen können, welche Variablen wir im (neu erstellten) Datensatz behalten wollen.

Nachdem wir schon den Mittelwert der Skale berechnet haben (mean), wollen wir noch eine neue Variable erstellen, die den z-standardisierten Mittelwert der Personen widergibt.

# für z-Standardisierung notwendige Kennwerte berechnen:
mean_all <- mean(PWE_data$mean, na.rm=TRUE) # Mittelwert über alle Personenmittelwerte
sd_all <- sd(PWE_data$mean, na.rm=TRUE) # Standardabweichung der Mittelwerte
# library(dplyr)
PWE_data_mean <- mutate(PWE_data, 
                        sw_mean = (mean - mean_all) / sd_all,
                        .keep = "used") # alle benutzten und neu erstellen Variablen

Abschließend können wir uns den neu erstellten Datensatz PWE_data_mean (für die ersten 10 Personen) einmal anschauen:

18.7 Weitere wichtige Hinweise

18.7.1 Cheat Sheet dplyr

Wer Gefallen an den tidyverse-Funktionen select(), filter(), mutate(), und summarise() gefunden hat, kann ein Cheat Sheet zur Data Transformation mit dplyr in deutsch oder englisch herunterladen.

18.7.2 Stichprobengröße

Es ist generell sehr wichtig, auch bei der Datenvorbereitung, ein Auge auf die Stichprobengröße zu haben. Teilweise werden bei der Datenvorbereitung einige Untersuchungseinheiten aus der Analyse exkludiert und damit sinkt die Stichprobengröße \(N\).

Wenn wir Auswertungen machen, in denen wir Ergebnisobjekte bekommen (z.B. bei der Regression mit lm()), können wir die Information zu \(N\) daraus ablesen. Dazu klicken wir auf das Ergebnisobjekt im Environment (z.B. lm_kontr). Unter model sehen wir die Dimensionalität des genutzten Teil des Datensatzes und können anhand der Anzahl der Zeilen \(N\) ablesen (z.B. bei lm_kontr: [116 x 3] d.h. 116 Fälle).

18.7.3 Replizierbarkeit

Wir sollten unsere R-Skripte generell großzügig kommentieren (mit #), damit wir (und ggf. auch Dritte) schnell nachvollziehen können, was wir da eigentlich gemacht haben.

Es lohnt sich auch, wenn man einen Datensatz (teil-)aufbereitet hat, diesen zu speichern, d.h. als neue Datei außerhalb von R zu exportieren (z.B. wenn man einen Datensatz vom Wide- ins Long-Format gebracht hat).

Außerdem ist es sinnvoll, alle Schritte der Datenvorbereitung sowie Datenauswertung im gleichen Programm durchzuführen, um möglichen Kompatibilitätsproblemen zwischen verschiedenen Programmen vorzubeugen.

18.8 Übung

Im Folgenden wollen wir einige Aufgaben bearbeiten, die in den Bereich der Datenvorbereitung fallen. Dazu gehören u.a. das Extrahieren und Sortieren von Daten, die Änderung der Kodierung von Daten sowie das Erstellen von Summary-Variablen. Zuallererst sollten wir uns jedoch immer mit dem genutzten Datensatz vertraut machen.

Dazu nutzen wir einen Datensatz, der im Rahmen eines Projektes zur Untersuchung des Zusammenhangs des Bedürfnisses nach Privatsphäre und verschiedenen Persönlichkeitseigenschaften erhoben wurde. Mehr Informationen zum Projekt und zur Publikation finden wir hier.

Den Datensatz sowie das dazugehörige Codebuch finden wir im Open Science Framework. Mehr Informationen zu OSF, der Replikationskrise und der Open Science Bewegung finden wir hier.

Den Datensatz können wir, nachdem wir ihn heruntergeladen haben, folgendermaßen in R einlesen:

data <- read.csv("Dateipfad/data.csv") # hier den eigenen Dateipfad einfügen

So sollte der Datensatz aussehen:


18.8.1 Übung 1: Erste Schritte

Zuallererst wollen wir uns mit dem Datensatz vertraut machen. Dazu benötigen wir das Codebuch, welches uns Informationen über die erhobenen Variablen sowie deren Messung gibt. Am besten überfliegen wir das Codebuch und den Datensatz einmal, um uns damit vertraut zu machen, bevor wir die nachfolgenden Aufgaben bearbeiten.

Achtung: Es sind nicht alle Variablen, die im Codebuch auftauchen, auch im Datensatz.

1.) Es gibt zwei Variablen im Datensatz, die nicht im Codebuch zu finden sind. Welche sind das?

Lösung

Die Variable sex taucht nicht im Datensatz auf. Nur die Variable male, welche die Ausprägungen 0 und 1 besitzt. Schätzungsweise soll mit beiden dieselbe Information koderit werden: das biologische Geschlecht der befragten Personen.

Die Variable time taucht nicht im Codebuch auf. Möglicherweise ist das die individuelle Bearbeitungszeit für den Fragebogen in Sekunden. Vor der Nutzung der der Variablen time müssten wir deren Bedeutung klären.


2.) Wie viele Variablen und Beobachtungen enthält der Datensatz?

Tipp Standardmäßig sind Variablen die Spalten und Beobachtungen die Zeilen eines Datensatzes.
Lösung

Wir finden die Information im R Studio Feld Environment

… oder indem wir folgende Funktionen nutzen:

ncol(data) # Variablen = Anzahl der Spalten
[1] 70
nrow(data) # Beobachtungen (Personen; N) = Anzahl der Zeilen
[1] 296


3.) Liegen alle Variablen in einem ihrem Messniveau angemessenen Datentyp vor?

Tipp

Im Codebuch finden wir Informationen zu den Variablen. Über die Funktionen str() bekommen wir Informationen zum Datentyp.

Lösung
str(data)
'data.frame':   296 obs. of  70 variables:
 $ id           : int  1 2 3 4 5 6 7 8 9 10 ...
 $ male         : int  0 1 0 0 0 0 1 1 0 0 ...
 $ age          : int  19 19 20 19 22 20 20 18 19 19 ...
 $ inc          : int  2 3 1 2 3 2 1 1 1 3 ...
 $ time         : int  2456 1414 828 1043 1806 1133 1625 2319 7129 1343 ...
 $ pri_nee_gen_1: int  6 7 5 6 4 5 6 5 4 4 ...
 $ pri_nee_gen_2: int  6 2 5 3 5 4 6 6 3 4 ...
 $ pri_nee_gen_3: int  6 6 4 6 7 6 5 7 4 6 ...
 $ pri_nee_gen_4: int  7 7 5 6 7 6 7 7 6 6 ...
 $ pri_nee_soc_1: int  2 4 5 2 5 4 5 4 1 6 ...
 $ pri_nee_soc_2: int  4 5 4 2 5 4 6 4 2 6 ...
 $ pri_nee_soc_3: int  3 6 4 3 5 3 4 6 2 6 ...
 $ pri_nee_soc_4: int  1 6 4 2 4 2 5 4 2 4 ...
 $ pri_nee_soc_5: int  1 4 4 5 4 2 6 6 3 6 ...
 $ pri_nee_soc_6: int  1 3 5 5 2 4 4 4 2 2 ...
 $ pri_nee_soc_7: int  1 1 3 3 1 2 2 6 1 2 ...
 $ pri_nee_soc_8: int  1 7 4 2 2 2 2 6 3 2 ...
 $ pri_nee_soc_9: int  1 3 4 2 6 5 6 7 1 6 ...
 $ pri_nee_int_1: int  1 1 4 4 2 4 2 5 2 5 ...
 $ pri_nee_int_2: int  1 6 5 5 1 2 5 5 1 3 ...
 $ pri_nee_int_3: int  7 7 5 5 6 2 4 3 3 6 ...
 $ pri_nee_int_4: int  3 6 2 7 5 5 4 4 4 6 ...
 $ pri_nee_int_5: int  5 6 4 7 2 5 2 3 6 6 ...
 $ pri_nee_int_6: int  3 6 3 5 2 3 3 2 3 3 ...
 $ pri_nee_int_7: int  7 7 5 6 2 5 6 6 6 4 ...
 $ pri_nee_int_8: int  1 7 4 5 5 3 5 5 6 3 ...
 $ pri_nee_int_9: int  4 7 3 3 5 5 5 5 4 3 ...
 $ soc_1        : int  5 5 3 2 2 3 2 5 3 2 ...
 $ soc_2        : int  4 4 6 6 6 6 5 2 6 6 ...
 $ soc_3        : int  4 4 2 3 2 2 5 6 4 6 ...
 $ soc_4        : int  4 6 4 7 6 6 3 5 3 4 ...
 $ soc_5        : int  7 5 2 6 4 2 2 4 2 3 ...
 $ soc_6        : int  4 5 6 5 2 6 2 4 3 4 ...
 $ soc_7        : int  3 2 2 2 2 2 2 5 3 2 ...
 $ soc_8        : int  3 4 6 6 5 4 2 2 7 5 ...
 $ itg_1        : int  1 1 2 3 2 3 1 4 3 1 ...
 $ itg_2        : int  2 1 4 4 4 6 2 4 1 1 ...
 $ itg_3        : int  5 7 4 5 4 5 4 4 7 1 ...
 $ itg_4        : int  2 7 6 2 6 6 7 4 3 7 ...
 $ itg_5        : int  1 1 2 1 2 2 1 4 1 1 ...
 $ itg_6        : int  4 1 2 3 2 2 4 4 2 1 ...
 $ itg_7        : int  1 6 2 2 3 2 1 4 6 1 ...
 $ itg_8        : int  5 2 4 5 4 4 6 4 3 1 ...
 $ itg_9        : int  5 1 1 2 6 5 2 4 7 2 ...
 $ itg_10       : int  5 2 2 7 6 5 7 4 7 6 ...
 $ itg_11       : int  4 4 4 5 5 5 6 4 4 1 ...
 $ anx_1        : int  1 1 3 5 5 2 6 5 3 6 ...
 $ anx_2        : int  5 4 3 1 5 6 3 3 5 5 ...
 $ anx_3        : int  1 1 5 3 3 2 6 3 2 6 ...
 $ anx_4        : int  4 3 3 3 3 3 5 1 7 2 ...
 $ anx_5        : int  6 6 2 5 5 3 6 3 1 2 ...
 $ anx_6        : int  6 7 3 3 5 6 2 5 6 3 ...
 $ anx_7        : int  3 6 5 6 4 2 2 1 2 5 ...
 $ anx_8        : int  6 4 4 3 5 7 4 6 7 2 ...
 $ ria_1        : int  1 7 5 5 6 3 4 3 7 6 ...
 $ ria_2        : int  5 7 5 4 4 6 6 5 6 6 ...
 $ ria_3        : int  1 6 5 6 6 3 3 5 5 3 ...
 $ ria_4        : int  6 3 3 4 5 5 3 4 6 6 ...
 $ ria_5        : int  4 2 5 6 4 2 6 6 5 5 ...
 $ ria_6        : int  5 6 3 3 5 6 2 3 4 5 ...
 $ ria_7        : int  5 7 5 4 5 5 1 7 5 6 ...
 $ ria_8        : int  6 7 5 5 5 6 6 5 4 6 ...
 $ tra_1        : int  5 7 3 3 2 5 3 4 4 5 ...
 $ tra_2        : int  6 2 6 6 6 4 6 4 7 5 ...
 $ tra_3        : int  5 7 4 3 5 5 5 4 5 5 ...
 $ tra_4        : int  7 6 5 7 4 5 6 4 5 2 ...
 $ tra_5        : int  5 7 4 3 5 5 2 4 4 6 ...
 $ tra_6        : int  7 1 4 6 6 5 6 4 6 2 ...
 $ tra_7        : int  4 7 4 3 5 4 2 4 2 6 ...
 $ tra_8        : int  3 7 4 3 4 5 5 4 5 6 ...

Alle Variablen liegen als integer vor. Für die Fragebogenitems (pri_nee_, soc_, itg_, anx_, ria_ und tra_), die intervallskaliert sein sollen, ist das korrekt. Die soziodemographischen Variablen male und inc hingegen sind nominal- bzw. ordinalskaliert. Das bedeutet, dass sie noch faktorisiert werden müssen, um in R als solche erkannt zu werden.

# nominalskaliert (unsortierter Faktor):
data$male <- factor(data$male)
str(data$male)
 Factor w/ 2 levels "0","1": 1 2 1 1 1 1 2 2 1 1 ...

Achtung: Nicht verwirren lassen: Der Faktor male hat die Kodierungen 0 und 1 (wie schon die integer-Variable vorher), aber die interne Kodierung des Faktors ist 1 und 2.

# ordinalskaliert (sortierter Faktor):
data$inc <- factor(data$inc, ordered=TRUE)
str(data$inc)
 Ord.factor w/ 5 levels "1"<"2"<"3"<"4"<..: 2 3 1 2 3 2 1 1 1 3 ...
levels(data$inc)
[1] "1" "2" "3" "4" "5"

Achtung: Aus dem Codebuch ist leider nicht ersichtlich, welche Kategorie (z.B. < $500) für welche Kodierung steht (z.B. 1). Wir gehen hier davon aus, dass beide aufsteigend gepaart wurden, z.B. < $500 = 1, aber die interne Kodierung eines Faktors beginnt bei 1, d.h. in unserem Fall gibt es 1 und 2.


4.) Wie heißt die Variable, die kodiert, inwieweit die befragte Person gerne viele Menschen um sich herum hat? Welche Antwortoption auf der Messskala (hiermit ist nicht die Kodierung der Daten gemeint) haben die meisten befragten Personen angekreuzt?

Tipp 1 Den Namen der Variablen sowie deren Messskala finden wir im Codebuch. Die Häufigkeiten der jeweiligen Ausprägungen der Variablen bringen wir in R in Erfahrung.
Tipp 2 Mit der Funktion table() können wir uns die Häufigkeiten der Ausprägungen einer Variablen ausgeben lassen. Die Funktion ist auch bereits sehr hilfreich, um einen schnellen Überblick über die möglichen Ausprägungen zu bekommen.
Lösung

Die Variable heißt soc_2 und hat eine Messskala, welche von -3 bis 3 (inklusive 0) geht (Codebuch S.20).

table(data$soc_2)

 1  2  3  4  5  6  7 
 2 14 36 69 72 61 27 

Die Kodierung 5 kommt am häufigsten vor. Die meisten befragten Personen haben damit eine 1 auf der Messskala angegeben.


5.) Gibt es Werte von Variablen im Datensatz, die unplausibel erscheinen? Wenn ja, entferne die entsprechenden Personen aus dem Datensatz.

Tipp 1

Hiervoll ist es sinnvoll, sich eine Übersicht der Ausprägungen aller Variablen anzuschauen und diese ggf. mit den Angaben im Codebuch zu vergleichen.

Tipp 2

Es gibt einen Wert einer Variablen der heraussticht.

Lösung
sapply(sapply(data, unique), sort, na.last=TRUE) # sortierte Ausprägungen der Variablen
$id
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
 [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
 [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
 [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
 [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
 [91]  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
[109] 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
[127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
[145] 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
[163] 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
[181] 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
[199] 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
[217] 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
[235] 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
[253] 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
[271] 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
[289] 289 290 291 292 293 294 295 296

$male
[1] 0    1    <NA>
Levels: 0 1

$age
 [1]  9 18 19 20 21 22 23 28 29 56 NA

$inc
[1] 1    2    3    4    5    <NA>
Levels: 1 < 2 < 3 < 4 < 5

$time
  [1]      3      5      7      8      9     10     12     13     21     31
 [11]     86    129    165    194    240    259    313    351    369    371
 [21]    384    439    443    455    456    458    484    539    562    585
 [31]    587    593    601    602    606    624    638    639    663    677
 [41]    694    706    725    741    760    778    814    815    828    829
 [51]    840    847    851    852    875    877    890    892    899    910
 [61]    934    986    991    992    999   1035   1039   1040   1043   1052
 [71]   1056   1061   1071   1073   1075   1077   1088   1090   1094   1097
 [81]   1104   1118   1119   1133   1141   1143   1144   1148   1152   1155
 [91]   1157   1158   1166   1167   1176   1177   1187   1197   1200   1209
[101]   1211   1219   1220   1222   1227   1236   1243   1248   1250   1255
[111]   1258   1259   1265   1290   1292   1303   1313   1314   1316   1318
[121]   1319   1332   1343   1345   1349   1363   1367   1372   1376   1387
[131]   1388   1397   1402   1405   1411   1414   1419   1425   1438   1448
[141]   1453   1460   1465   1481   1492   1496   1497   1499   1514   1524
[151]   1525   1560   1581   1595   1596   1602   1606   1625   1628   1633
[161]   1651   1655   1656   1660   1669   1671   1672   1683   1695   1702
[171]   1706   1713   1714   1716   1732   1734   1735   1745   1753   1756
[181]   1772   1778   1780   1803   1806   1815   1820   1831   1845   1847
[191]   1869   1879   1889   1905   1930   1932   1945   1949   1985   1994
[201]   2022   2077   2125   2135   2139   2142   2144   2183   2185   2197
[211]   2248   2255   2319   2342   2348   2360   2373   2393   2420   2447
[221]   2456   2493   2495   2515   2518   2523   2537   2597   2606   2649
[231]   2654   2673   2759   2769   2927   3204   3224   3273   3386   3466
[241]   3739   3803   3808   3858   3913   4084   4436   4702   4748   5031
[251]   5108   5229   5469   5927   5944   6064   6282   6598   6685   6849
[261]   7129   7246   8016   8817   9274  12533  13928  17371  17973  22834
[271]  50047  54973  96391 101191 110358 192301 197593 342508 590589 688498

$pri_nee_gen_1
[1]  1  2  3  4  5  6  7 NA

$pri_nee_gen_2
[1]  1  2  3  4  5  6  7 NA

$pri_nee_gen_3
[1]  2  3  4  5  6  7 NA

$pri_nee_gen_4
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_1
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_2
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_3
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_4
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_5
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_6
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_7
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_8
[1]  1  2  3  4  5  6  7 NA

$pri_nee_soc_9
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_1
[1]  1  2  3  4  5  6 NA

$pri_nee_int_2
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_3
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_4
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_5
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_6
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_7
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_8
[1]  1  2  3  4  5  6  7 NA

$pri_nee_int_9
[1]  1  2  3  4  5  6  7 NA

$soc_1
[1]  1  2  3  4  5  6  7 NA

$soc_2
[1]  1  2  3  4  5  6  7 NA

$soc_3
[1]  1  2  3  4  5  6  7 NA

$soc_4
[1]  1  2  3  4  5  6  7 NA

$soc_5
[1]  1  2  3  4  5  6  7 NA

$soc_6
[1]  1  2  3  4  5  6  7 NA

$soc_7
[1]  1  2  3  4  5  6  7 NA

$soc_8
[1]  1  2  3  4  5  6  7 NA

$itg_1
[1]  1  2  3  4  5  6 NA

$itg_2
[1]  1  2  3  4  5  6  7 NA

$itg_3
[1]  1  2  3  4  5  6  7 NA

$itg_4
[1]  1  2  3  4  5  6  7 NA

$itg_5
[1]  1  2  3  4  5  6  7 NA

$itg_6
[1]  1  2  3  4  5  6  7 NA

$itg_7
[1]  1  2  3  4  5  6  7 NA

$itg_8
[1]  1  2  3  4  5  6  7 NA

$itg_9
[1]  1  2  3  4  5  6  7 NA

$itg_10
[1]  1  2  3  4  5  6  7 NA

$itg_11
[1]  1  2  3  4  5  6  7 NA

$anx_1
[1]  1  2  3  4  5  6  7 NA

$anx_2
[1]  1  2  3  4  5  6  7 NA

$anx_3
[1]  1  2  3  4  5  6  7 NA

$anx_4
[1]  1  2  3  4  5  6  7 NA

$anx_5
[1]  1  2  3  4  5  6  7 NA

$anx_6
[1]  1  2  3  4  5  6  7 NA

$anx_7
[1]  1  2  3  4  5  6  7 NA

$anx_8
[1]  1  2  3  4  5  6  7 NA

$ria_1
[1]  1  2  3  4  5  6  7 NA

$ria_2
[1]  1  2  3  4  5  6  7 NA

$ria_3
[1]  1  2  3  4  5  6  7 NA

$ria_4
[1]  1  2  3  4  5  6  7 NA

$ria_5
[1]  1  2  3  4  5  6  7 NA

$ria_6
[1]  1  2  3  4  5  6  7 NA

$ria_7
[1]  1  2  3  4  5  6  7 NA

$ria_8
[1]  1  2  3  4  5  6  7 NA

$tra_1
[1]  1  2  3  4  5  6  7 NA

$tra_2
[1]  1  2  3  4  5  6  7 NA

$tra_3
[1]  1  2  3  4  5  6  7 NA

$tra_4
[1]  1  2  3  4  5  6  7 NA

$tra_5
[1]  1  2  3  4  5  6  7 NA

$tra_6
[1]  1  2  3  4  5  6  7 NA

$tra_7
[1]  1  2  3  4  5  6  7 NA

$tra_8
[1]  1  2  3  4  5  6  7 NA
which(data$age == 9)
[1] 232

Die Person mit der Zeile 232 hat (vermutlich versehentlich) als Alter 9 Jahre angegeben. Wir entfernen diese Person aus dem Datensatz.

data <- data[-which(data$age == 9),]


6.) Enthält der Datensatz fehlende Werte (“Missings”; NA) und wenn ja, wie viele insgesamt und auf welchen Variablen?

Tipp Das Thema “Fehlende Werte” wird im Kapitel zur Datenvorbereitung nur kurz angerissen, weil es ein eigenständiges Kapitel dazu gibt.
Lösung
anyNA(data) # prüft ob mind. ein Missing im Datensatz
[1] TRUE

Ja, data enthält fehlende Werte.

table(is.na(data)) # absolute Anzahl fehlender Werte

FALSE  TRUE 
19337  1313 
table(is.na(data))[2]/(table(is.na(data))[1] + table(is.na(data))[2]) 
# relative Anzahl fehlender Werte
      TRUE 
0.06358354 
colSums(is.na(data)) # Übersicht über Anzahl Missings für alle Variablen
           id          male           age           inc          time 
            0            24            25            28             0 
pri_nee_gen_1 pri_nee_gen_2 pri_nee_gen_3 pri_nee_gen_4 pri_nee_soc_1 
           19            19            19            19            19 
pri_nee_soc_2 pri_nee_soc_3 pri_nee_soc_4 pri_nee_soc_5 pri_nee_soc_6 
           19            19            19            19            19 
pri_nee_soc_7 pri_nee_soc_8 pri_nee_soc_9 pri_nee_int_1 pri_nee_int_2 
           19            19            19            21            21 
pri_nee_int_3 pri_nee_int_4 pri_nee_int_5 pri_nee_int_6 pri_nee_int_7 
           21            21            21            21            21 
pri_nee_int_8 pri_nee_int_9         soc_1         soc_2         soc_3 
           21            21            15            15            15 
        soc_4         soc_5         soc_6         soc_7         soc_8 
           15            15            16            15            15 
        itg_1         itg_2         itg_3         itg_4         itg_5 
           21            21            21            21            21 
        itg_6         itg_7         itg_8         itg_9        itg_10 
           21            21            21            21            21 
       itg_11         anx_1         anx_2         anx_3         anx_4 
           21            18            18            18            18 
        anx_5         anx_6         anx_7         anx_8         ria_1 
           18            18            18            18            17 
        ria_2         ria_3         ria_4         ria_5         ria_6 
           17            17            17            17            17 
        ria_7         ria_8         tra_1         tra_2         tra_3 
           17            17            21            21            21 
        tra_4         tra_5         tra_6         tra_7         tra_8 
           21            21            21            21            21 

Jede Variable mit Ausnahme von id und time enthält fehlende Werte.


7.) Was ist die höchste Anzahl an fehlenden Werten von einer Person und welche Person hat bzw. welche Personen haben die meisten fehlenden Werte?

Tipp In der letzten Aufgabe haben wir uns variablenweise Missings angeschaut mit colSums(). Nun wollen wir uns zeilenweise Missings anschauen.
Lösung
max(rowSums(is.na(data))) # maximale Anzahl Missings (absoluter Wert)
[1] 68
max(rowSums(is.na(data))) / ncol(data)
# Max-Anzahl Missings einer Person / Anzahl aller Variablen (relativer Wert)
[1] 0.9714286

Fehlende Werte auf 68 Variablen ist die Höchstzahl an fehlenden Werten pro Person. Das entspricht einem Prozentsatz von ca. 97% fehlenden Werten.

# Personen mit diesen Zeilen haben die meisten Missings:
names(which(rowSums(is.na(data)) == max(rowSums(is.na(data)))))
 [1] "63"  "66"  "78"  "133" "134" "136" "139" "180" "199" "206" "262" "267"
[13] "268" "281" "283"

Wir müssen hier names() nutzen, damit wir die Zeilennamen ausgegeben bekommen da wir sonst einen benannten Vektor ausgegeben bekommen.

Man sollte überlegen, wie mit den Daten dieser 15 Personen bei etwaigen Analysen umzugehen wäre. Wenn die Daten MCAR sind, könnten wir diese Personen aus den Analysen entfernen. Mehr Infos zu Fehlenden Werten im gleichnamigen Kapitel.


18.8.2 Übung 2: Extrahieren von Daten

Nun wollen wir einzelne Daten aus dem Datensatz extrahieren. Dabei interessieren uns entweder Variablen mit bestimmten Ausprägungen (von Personen) oder Personen mit bestimmten Ausprägungen (auf Variablen).

Achtung: Achte darauf, immer auch die id-Variable mit zu extrahieren, um Beobachtungseinheiten identifizieren zu können, auch wenn das nicht jedes mal erneut in der Aufgabenbeschreibung steht.

1.) Extrahiere alle demographischen Variablen aus data.

Tipp 1

Im Codebuch (S. 26ff) steht, welche Variablen zu den demographischen Angaben zählen.

Achtung: Im Codebuch steht die Variable sex, welche im Datensatz nicht enthalten ist. Alternativ gibt es die Variable male.

Tipp 2 Es gibt drei demographische Variablen in data (aber mehr im Codebuch).
Lösung
library(dplyr)
select(data, id, male, age, inc)


2.) Extrahiere alle Variablen, die grundlegende Bedürfnisse (General Needs) erfassen.

Tipp 1 Die Variablen haben denselben Wortstamm: gen.
Tipp 2 Es gibt vier Items zu grundlegenden Bedürfnissen.
Lösung
data[,c(1, # id
        grep("gen", names(data)))] # general needs


3.) Extrahiere alle Variablen, die gesellschaftliche (Societal Needs) und interpersonelle (Interpersonal Needs) Bedürfnisse erfassen.

Tipp 1 Die Variablen haben zwar denselben Wortstamm – nee – aber den haben die Variablen zu grundlegenden Bedürfnissen (General Needs) auch.
Tipp 2 Es gibt insgesamt 18 Items zu gesellschaftlichen und interpersonellen Bedürfnissen (jeweils 9).
Lösung
# Vorauswahl von Bedürfnis-Variablen mittels des gemeinsamen Wortstammes:
all_needs <- data[,c(1, # id
                     grep("nee", names(data)))] # alle Bedürfnis-Variablen
# Auswahl der gewollten Variablen aus den Bedürfnis Variablen (neben "id") ..
# .. solche, die "c" oder "t" im Namen haben (trifft nur auf General needs nicht zu):
all_needs[grep("id|c|t", names(all_needs))]


4.) Extrahiere alle Personen, die weniger als $500 oder mehr als $5.000 monatlich zur Verfügung haben.

Tipp Um beide Bedingungen (< $500 und > $5.000) abzufragen, können wir den logischen Operator | (“oder”) nutzen.
Lösung

Zuerst vergleichen wir die Angaben zu den Ausprägungen von inc im Codebuch (S. 28) und der Kodierung in R. Laut Codebuch ist < $500 die erste Ausprägung; > $5.000 die letzte. Nun schauen wir, mit welchen Kodierungen diese korrespondieren.

table(data$inc)

  1   2   3   4   5 
148  74  24  15   6 

DIe beiden werden mit 1 und 5 kodiert. Anschließend wenden wir dieses Wissen auf unsere Selektion an.

filter(data, inc == 1 | inc == 5)


5.) Extrahiere die “Extremkreuzer” (Personen, die nur die niedrigste oder höchste Ausprägung einer Frage ankreuzen) aus dem Geselligkeits-Fragebogen (Sociability).

Tipp 1 Wir können wieder den logischen Operator | (“oder”) nutzen, um jeweils Personen mit der niedrigste oder höchsten Ausprägung eines Items auszuwählen.
Tipp 2 Es gibt insgesamt zwei Extremkreuzer.
Lösung

Im Codebuch (S. 20) sehen wir, dass für alle Items des Geselligkeits-Fragebogen dieselbe Skale, welche von -3 bis 3 geht, genutzt wurde. Nun schauen wir uns am Beispiel eines Items an, wie diese in R kodiert werden.

# niedrigste und höchste Ausprägung in Erfahrung bringen:
table(data$soc_1)

 1  2  3  4  5  6  7 
23 71 54 55 52 21  4 
# Fall-Selektion anwenden:
filter(data, soc_1 == 1 | soc_1 == 7,
             soc_2 == 1 | soc_2 == 7,
             soc_3 == 1 | soc_3 == 7,
             soc_4 == 1 | soc_4 == 7,
             soc_5 == 1 | soc_5 == 7,
             soc_6 == 1 | soc_6 == 7,
             soc_7 == 1 | soc_7 == 7,
             soc_8 == 1 | soc_8 == 7)

Auf den Seiten 6 bis 7 sehen wir die Items des Geselligkeits-Fragebogens (oben rechts ist der Pfeil).


6.) Extrahiere die Items des Fragebogens zu Integrität (Integrity) für alle Personen, die 18 Jahre alt sind.

Tipp 1 Die Reihenfolge der Auswahl, d.h. ob zuerst Variablen oder zuerst Fälle selektiert werden, ist eigentlich irrelevant, aber wenn wir erst die Fälle selektieren und dann die Variablen müssen wir die Variable age nicht wieder aus dem finalen Datensatz entfernen.
Tipp 2 Der finale Datensatz besteht aus 12 Variablen (davon sind 11 die Integritäts-Items) von 54 Personen.
Lösung
# Fälle selektieren:
data_18 <- filter(data, age == 18)
# Variablen selektietren:
all_itg_items <- data_18[,c(1, # id
                         grep("itg", names(data_18)))] # Integrität Items


7.) Extrahiere alle Personen, die im Fragebogen zu interpersonellen Bedürfnissen (Needs, Interpersonal) über dem Gesamtmittelwert (Mittelwert über alle Personen im Datensatz) liegen. Entferne Personen mit mindestens einem fehlenden Wert auf diesen Items aus der Analyse.

Achtung: Unser Vorgehen mit fehlenden Wert hier (casewise deletion) ist stark vereinfacht und sollte in echten Analysen nicht ohne Belege für MCAR durchgeführt werden.

Tipp 1 Wir müssen
1) alle Items des Fragebogens extrahieren,
2) alle Personen mit mind. einem Missing entfernen,
3) die Items jeweils für jede Person aufsummieren (d.h. individuelle Scores bilden),
4) diese individuellen Scores über alle Personen aufsummieren, um den Gesamtmittelwert zu berechnen,
5) die individuellen Scores mit dem Gesamtmittelwert vergleichen, um nur überdurchschnittliche Personen in unserer finalen Auswahl zu haben.
Tipp 2 Im finalen Datensatz befinden sich 140 (von initial 296) Personen.
Lösung
# 1) Items extrahieren:
all_int_items <- data[,c(1, # id
                         grep("int", names(data)))] # Int. Bedürfnisse Items
# 2) Personen mit mind. einem Missing entfernen:
all_int_items <- na.omit(all_int_items) # 21 Personen entfernt
# 3) individuelle Scores bilden:
library(dplyr)
all_int_items <- mutate(all_int_items, score = rowSums(all_int_items))
# 4) Mittelwert individueller Scores berechnen:
mean_score <- mean(all_int_items$score)
mean_score
[1] 182.6387
# 5) überdurchschnittliche Personen extrahieren:
final_data <- filter(all_int_items, score > mean_score) # 135 Personen entfernt


18.8.3 Übung 3: Sortieren von Daten

Im Folgenden wollen wir unseren Datensatz nach den aufsteigenden bzw. absteigenden Ausprägungen auf einer der enthaltenen Variablen sortieren.

1.) Sortiere data (primär) nach den aufsteigenden Ausprägungen in age und (sekundär) nach dem Geschlecht (Frauen zuerst).

Achtung: Da die Kodierung der Variablen male im Codebuch nicht geklärt wird, gehen wir hier standarmäßig davon aus, dass 0 für nein und 1 für ja steht.

Lösung

Da Frauen zuerst in der sekundären Sortierung vorkommen sollen, können wir beide Variablen aufsteigend sortieren.

data[order(data$age, data$male),] # Default: aufsteigende Sortierung


2.) Sortiere data (primär) nach absteigendem Einkommen und (sekundär, tertiär, ect.) nach den aufsteigenden Ausprägungen auf den Traditionalismus-Items (Traditionalism) in ihrer natürlichen Reihenfolge (beginnend bei _1, endend bei _8).

Tipp 1 Mit der Funktion order() können wir nur jeweils auf- oder absteigend für alle angegebenen Variablen sortieren. Daher sollten wir für diese Aufgabe auf andere Funktionen, z.B. arrange() aus dem Paket dplyr zurückgreifen.
Tipp 2 Um besser beurteilen zu können, ob die Sortierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz nur aus den relevanten Variablen (inklusive id) zu erstellen.
Lösung
library(dplyr)
# neuer Datensatz nur mit relevanten Variablen:
data_tra <- data[,c(1, # id
                    4, # inc
                    grep("tra", names(data)))]
# Sortierung:
arrange(data_tra, -inc, tra_1, tra_2, tra_3, tra_4, tra_5, tra_6, tra_7, tra_8)
# mit einem "-" sortieren wir absteigend
Warning: There was 1 warning in `arrange()`.
ℹ In argument: `..1 = -inc`.
Caused by warning in `Ops.ordered()`:
! '-' is not meaningful for ordered factors


18.8.4 Übung 4: Änderung der Kodierung von Daten

Nun wollen wir die Kodierungen einiger Variablen ändern bzw. Variablen mit neuen Kodierungen erstellen.

1.) Erstelle eine neue Variable income, die das monatliche Einkommen der befragten Personen kodiert, und dafür die Kategorien aus dem Codebuch (S.28) nutzt.

Tipp Um besser beurteilen zu können, ob die Kodierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz bestehend aus inc und id zu erstellen.
Lösung
# neuen Datensatz erstellen:
library(dplyr)
data_inc <- select(data, id, inc)
# neue Variable income erstellen:
rec <- c("1='<$500'; 2='$500-$1000'; 3='$1000-$2000'; 4='$2000-$4000'; 5='>$5000'")
library(car)
data_inc$income <- recode(data_inc$inc, recodes=rec)

Die Variable rec, welche die Überführung der bestehenden in die neue Kodierung enthält, wurde nur aus darstellerischen Gründen erstellt. Der Inhalt kann auch direkt dem Parameter recodes übergeben werden.


2.) Erstelle eine neue Variable sex, die analog zur gleichnamigen Variablen im Codebuch das biologische Geschlecht kodiert. Gehe für diese Aufgabe davon aus, dass alle Personen, die einen fehlender Wert auf male haben, einer Kategorie other (nicht-binäres Geschlecht) zugeordnet werden.

Tipp Um besser beurteilen zu können, ob die Kodierung erfolgreich war, ist es sinnvoll, zuerst einen neuen Datensatz bestehend aus male und id zu erstellen.
Lösung
library(dplyr)
# neuen Datensatz erstellen:
data_sex <- select(data, id, male)
# neue Variable sex erstellen:
data_sex$sex <- case_when(data_sex$male == 0 ~ "female",
                          data_sex$male == 1 ~ "male",
                          is.na(data_sex$male) ~ "other")


3.) Rekodiere die negativ kodierten Items des Fragebogens zu Risikovermeidung (Risk Avoidance).

Tipp 1 Die negativ kodierten Items sind im Codebuch jeweils mit einem * markiert.
Tipp 2 Es ist sinnvoll zur Überprüfung der Aufgabe, einen neuen Datensatz bestehend aus den negativ kodierten Items und ihrem rekodierten Pendant (und natürlich id) zu erstellen.
Lösung
# neuen Datensatz erstellen:
library(dplyr)
data_ria <- select(data, id, 
                   ria_1, ria_3, ria_5) # negativ kodierte Items
# neue rekodierte Variablen erstellen:
library(car)

data_ria$ria_1_rec <- recode(data_ria$ria_1, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")
data_ria$ria_3_rec <- recode(data_ria$ria_3, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")
data_ria$ria_5_rec <- recode(data_ria$ria_5, recodes="1=7; 2=6; 3=5; 4=4; 5=3; 6=2; 7=1")


18.8.5 Übung 5: Erstellen von Summary-Variablen

Nachfolgend wollen wir neue Variablen erstellen, die Informationen aus mehreren Variablen des Datensatzes zusammenfassen.

1.) Erstelle für die verschiedenen Bereiche von Bedürfnissen – Allgemein (General), Gesellschaftlich (Societal) und Interpersonell (Interpersonal) – jeweils separate Summenwerte und einen Gesamtsummenwert (über alle drei Bereiche).

Tipp Die Items aller drei Bedürfnis-Bereiche haben denselben Wortstamm: nee.
Lösung
# neuer Datensatz mit relevanten Variablen:
data_need <- data[,c(1, # id
                     grep("nee", names(data)))] # Bedürfnis-Items

# Summenwerte der drei Bedürfnis-Bereiche und Gesamtsummenwert:
library(dplyr)
data_need$score_gen <- rowSums(select(data_need, matches("nee_gen")))
data_need$score_soc <- rowSums(select(data_need, matches("nee_soc")))
data_need$score_int <- rowSums(select(data_need, matches("nee_int")))
data_need$score_all <- rowSums(select(data_need, matches("nee")))


2.) Erstelle die personenspezifischen Summenwerte der Items, die interpersonelle Bedürfnisse erfassen (Needs, Interpersonal), für alle Personen, die ein monatliches Einkommen von mehr als $1.000 haben. Bei allen Personen, die weniger zur Verfügung haben, soll ein "/" in der Summenwert-Variablen stehen.

Tipp Zur Bearbeitung der Aufgabe können wir mutate() mit ifelse() kombinieren, um den beiden Bedingungen (monatliches Einkommen von mehr bzw. weniger als $1.000) unterschiedliche Werte zuzuweisen.
Lösung
# neuer Datensatz mit relevanten Variablen:
data_int <- data[,c(1, # id
                    4, # inc
                     grep("int", names(data)))] # Int. Bedürfnisse Items

Nun vergleichen wir die Angaben zu den Ausprägungen von inc im Codebuch (S. 28) und der Kodierung in R. Es gibt insgesamt 5 Ausprägungen. Auf unsere Bedingung (mehr als $1.000) treffen drei Ausprägungen zu. Daher ist es codesparender, wenn wir die zwei nicht zutreffenden Ausprägungen (< $500 und $500 - $1000) negieren (!=). Laut Codebuch ist < $500 die erste Ausprägung; $500 - $1000 die zweite. Nun schauen wir, mit welchen Kodierungen diese korrespondieren.

table(data$inc)

  1   2   3   4   5 
148  74  24  15   6 

Sie haben die Kodierungen 1 und 2. Diese Information können wir nun anwenden.

# neue Variable mit Summenwert bzw. "/" erstellen
library(dplyr)
data_int$score <- ifelse(data_int$inc != 1 & data_int$inc != 2,
                         rowSums(select(data_need, matches("int"))),
                         "/") # ifelse(Bedingung, trifft zu, trifft nicht zu)



Um eine möglichst exakte Replikation der Funktionen zu gewährleisten gibt es im folgenden relevante Angaben zum System (R-Version, Betriebssystem, geladene Pakete mit Angaben zur Version), mit welchem diese Seite erstellt wurde.

sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

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

time zone: Etc/UTC
tzcode source: system (glibc)

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

other attached packages:
[1] car_3.1-3        carData_3.0-5    rmarkdown_2.28   knitr_1.48      
[5] readr_2.1.5      kableExtra_1.4.0 dplyr_1.1.4     

loaded via a namespace (and not attached):
 [1] jsonlite_1.8.9    compiler_4.4.1    highr_0.11        crayon_1.5.3     
 [5] tidyselect_1.2.1  xml2_1.3.6        stringr_1.5.1     systemfonts_1.1.0
 [9] scales_1.3.0      yaml_2.3.10       fastmap_1.2.0     R6_2.5.1         
[13] generics_0.1.3    Formula_1.2-5     htmlwidgets_1.6.4 tibble_3.2.1     
[17] munsell_0.5.1     svglite_2.1.3     pillar_1.9.0      tzdb_0.4.0       
[21] rlang_1.1.4       utf8_1.2.4        stringi_1.8.4     xfun_0.48        
[25] viridisLite_0.4.2 cli_3.6.3         withr_3.0.1       magrittr_2.0.3   
[29] digest_0.6.37     rstudioapi_0.16.0 hms_1.1.3         lifecycle_1.0.4  
[33] vctrs_0.6.5       evaluate_1.0.1    glue_1.8.0        abind_1.4-8      
[37] fansi_1.0.6       colorspace_2.1-1  tools_4.4.1       pkgconfig_2.0.3  
[41] htmltools_0.5.8.1

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