Motivation

In diesem Tutorial wird es einen Crashkurs und möglichst einfache Anleitung dazu geben, wie man Daten mithilfe von R aufbereiten kann.

Der Vorteil von codebasierter Datenaufbereitung ist die schriftliche Fixierung jeder Veränderung der Daten, was für die Nachvollziehbarkeit des gesamten Prozesses sehr wertvoll ist. So kann das Skript zur Aufbereitung nicht nur selbst später nachvollzogen werden, sondern auch besser geteilt werden. Außerdem können bei der manuellen Aufbereitung von Daten einfacher schwerwiegende Fehler entstehen, die später mitunter zu Nicht-Nachvollziehbarkeit der Daten führen. Wird z.B. versehentlich ein Spaltenname verschoben, kann das die Werte einer gesamten Variablen verändern.

R bietet sich aufgrund seiner leichten Handhabbarkeit und Vielseitigkeit der Möglichkeiten der Aufbereitung besonders gut zur Datenauswertung an.

Vorausgesetzt für dieses Tutorial: R & RStudio bereits installiert, Grundkenntnisse über Installation von Paketen und der grundlegenden Funktionsweise von R.

Übersicht Inhalt

  1. Vorbereitung von R und Tipps für saubere Dokumentation

  2. Hinweise zur “sauberen” Datenerhebung

  3. Importieren verschiedener Datenformate

  4. Überblick über die Daten gewinnen

  5. Zuschneiden und Erweitern von Datenframes

  6. Umbenennung von Variablen

  7. Umgang mit fehlenden Werten

  8. Bearbeiten von Wörtern und Buchstaben

  9. Umkodieren und Reskalieren von Items aus Skalen

  10. Daten speichern


benötigte(s) Paket(e)

tidyverse: Eine Sammlung von Paketen, welche für die Datenaufbereitung nützlich sind. In der Regel sollten mit der Installation und Hinzufügung zur Library auch alle anderen für das Tutorial benötigten Pakete laden. Wenn dabei Fehler auftreten sollten, bietet sich eine händische Installation dieser zwei Pakete an: dplyr & stringr

Zum Speichern der Daten als Excel Datei benötigen wir außerdem das openxlsx Paket.


1 Vorbereitung von R und Tipps für saubere Dokumentation

1.1 Ein R-Projekt erstellen

Es bietet sich an, für die Aufbereitung eurer Daten und auch bei späteren Analysen oder Erstellung von Deskriptivstatistiken mit R ein neues Projekt zu erstellen. Ein RStudio-Projekt ist zunächst einfach ein weiterer Ordner auf eurer Festplatte. Er sorgt dafür, dass das Arbeitsverzeichnis automatisch gesetzt wird, sodass dieses nicht mehr neu definiert werden muss im Skript. Das erleichtert auch das Teilen von Projekten, da Dateipfade nun automatisch relativ zum Projektordner angegeben werden.

Um ein neues Projekt in R zu öffnen, könnt ihr entweder unter File -> New Project… klicken oder das kleine Würfelsymbol mit grünem Pluszeichen wählen.


1.2 Eine übersichtliche Ordnerstruktur wählen

Um Rohdaten, bereinigte Daten und Skripte bestmöglich auseinanderhalten zu können, bietet sich die folgende Ordnerstruktur gut an:

# Mein_Experiment
  ## scripts 
  ## rawdata 
  ## outputs 
  ## readme.txt 

Im scripts Ordner werden die zum Projekt gehörigen R-Skripte gespeichert (Datenaufbereitung, Auswertung etc.), die Rohdaten werden im rawdata Ordner gespeichert und die aufbereiteten Daten im outputs Ordner.

Es bietet sich außerdem an, eine readme.txt in die oberste Ebene zu packen, die das Projekt kurz beschreibt.

Natürlich könnt ihr auch eine andere Art der Ordnerstruktur wähle. Wichtig ist nur, dass Rohdaten und aufbereitete Daten nicht im gleichen Unterordner landen, da es sonst schnell zu Unübersichtlichkeit führen kann.


1.3 Benennung von Skripten und Daten

Im Laufe der Arbeit an eurem Projekt kann es vorkommen, dass immer wieder neue Versionen eines Skriptes oder auch neue aufbereitete Datensätze entstehen. Um später nicht den Überblick über die verschiedenen Versionen der Skripte und Daten zu verlieren, bietet es sich an, die Dateien mit jeweiligem Datum im Format YYYY_MM_DD zu versehen. Eine Benennung mit Worten wie final kann irreführend werden, sobald neue Versionen eines ehemals mit final bezeichneten Datensatzes erstellt werden. Hier kommt es schnell zu verwirrenden und langen Namen wie datenaufbereitung_final2 oder datenaufbereitung_finalnew oder *datenaufbereitung__finalforrealnow*.

Im Folgenden ein Beispiel für eine übersichtliche Art der Benennung:

# scripts 
  ## 2025_01_03_datenaufbereitung.R 
  ## 2025_01_05_datenaufbereitung.R 

1.4 Tipps für einen guten Workflow im Skript

Es bietet sich auch an, benötigte Pakete für das gesamte Skript immer am Anfang zu laden. So wird auch beim Teilen des Skriptes mit anderen ersichtlich, welche Pakete benötigt sind und welche ggf. noch geladen werden müssen.

Außerdem sollten längere Zeilen von Kommentaren oder längere Code-Blöcke der Übersichtlichkeit halber sinnvoll gebrochen werden. Hierfür bieten sich auch sinnvoll gewählte Leerzeichen und Einrückungen (insbesondere bei längerem Code) an. Welchen Stil ihr hier wählt, ist euch überlassen. Wichtig ist jedoch, den Stil einheitlich zu halten.

1.5 Der Pipe-Operator

Ein weiteres Konzept, welches ich zu Beginn einführen möchte, ist der Pipe Operator. Dies ist ein nützliches Werkzeug in R, welches hilft eine Abfolge mehrerer Operationen übersichtlicher zu machen und somit Fehleranfälligkeit verringert.

Die Idee hinter Pipes ist die Verkettung verschiedener imperativer Aktionen. Wenn wir in der späteren Aufbereitung also verschiedene Funktionen ineinander schachteln, ist der Pipe Operator sehr nützlich. Um Pipes zu erstellen, benutzen wir in diesem Tutorial tidyverse() Paket.

Der grundlegende Befehl einer Pipe ist das %>%. Eine Pipe verknüpft logisch Funktionen in aufeinanderfolgender Reihenfolge miteinander. Nehmen wir beispielsweise an, wir wollen einen Vektor mit den Zahlen 7,8 und 9 um die Zahlen 9, 10 und 11 erweitern, aus diesem gemeinsamen Vektor eine Summe bilden und dann die Quadratwurzel daraus berechnen. Um das zu machen kann man entweder über mehrere einzelne Funktionen mit überschreiben des Vektors oder in einer verschachtelten Funktion vorgehen.

vektor <- c(7,7,8) #Definieren des Vektors 
vektor_erweitert <- append(vektor, c(9, 10, 11)) #Erweitern um 9,10,11
summe <- sum(vektor_erweitert) #Summe aus Gesamtvektor
sqrt(summe) #Quadratwurzel aus Summe 
## [1] 7.211103
# oder wir verschachteln die Funktionen ineinander 

sqrt(sum(append(c(7,8,9), c(9, 10, 11))))
## [1] 7.348469
# Hinweis: aufgrund unterschiedlicher Rundung kommen hier lericht unterschiedliche Ergebnisse raus 

Wie man erkennen kann, ist der Code in der ersten Möglichkeit relativ umständlich, da man entweder alte Objekte überschreibt oder viele neue Objekte erstellen muss.

Im zweiten Fall führt das Verschachteln der Funktion dazu, dass man den Code nur noch schwer nachvollziehen kann. Versuchen wir also den gleichen Code in einer Pipe zu schreiben. Hierfür führe ich noch ein weiteres Prinzip der Pipe ein: den Platzhalter. Mit einem . oder .x kann man in einer Pipe angeben, an welche Stelle das zuvor links definierte Objekt reingepackt werden soll. Die Idee hinter einer Pipe ist dann wie folgt: “Nehme bitte das, was Links ist, und packe es in die Funktion rechts”.

c(7, 8, 9) %>% 
  {c(., 9, 10, 11)} %>% # Punkt als Platzhalter, Vektor oben (links) erweitert 
    sum() %>% #Summe aus erweitertem Vektor 
      sqrt() #Quadratwurzel daraus
## [1] 7.348469

Die grundlegende Funktionsweise einer Pipe sollte klar sein. Diese sollte in den Aufbereitungsschritten dieses Tutorials noch deutlicher werden. Vertiefende Information zur Verwendung von Pipes findet ihr in diesem Tutorial.


2 Hinweise zur “sauberen” Datenerhebung

Bereits bei der Erhebung der Daten ist es wichtig über Schritte der präventiven Datenaufbereitung nachzudenken. Das beinhaltet vor allem ein datensensibles Befragungsdesign zur Verringerung nachträglichen Arbeitsaufwandes.

Besonders häufig treten Probleme bei nicht erschöpfenden Antwortoptionen auf. So kann bei der Frage nach der Menge der gelesenen Bücher pro Jahr in einer Freitextoption alles zwischen 1-3, 1 bis 3 oder sogar eins bis drei herauskommen. Es ist also sinnvoll hier Antwortoptionen vorzugeben, die den relevanten untersuchten Bereich vorgeben.


3 Importieren verschiedener Datenformate

Der Vorteil der Nutzung von R zur Datenaufbereitung liegt darin, dass R sowohl reguläre als auch exotische Datentypen recht einfach einlesen kann. Der für dieses Tutorial verwendete Demo-Datensatz ist in einem csv-Format, da dieses auch das von Pavlovia genutzte Format ist, welches ihr beim Download eurer Daten erhalten werdet.

Falls ihr Daten in einem anderen Format vorliegen haben solltet (z.B. im Excel .xslsx Format) könnt ihr hier weitere Daten-Import Möglichkeiten einsehen

HINWEIS: Denkt daran, eure Rohdaten in einem separaten Ordner zu speichern und diese im Laufe der Aufbereitung nicht zu überschreiben. So habt ihr im Falle eines Fehlers in der Aufbereitung immer eine Rückverserichung!

Mit Pavlovia erhobene Daten liegen im long() Format vor. Das beudetet, dass die Daten einer Untersuchungseinheit innerhalb mehrerer Zeilen vertreten sein können. Dies tritt u.a. auch bei messwiederholten Daten auf.

Neben dem long() Format, gibt es auch ein wide() Format von csv-Daten. Sie lassen sich beide aber relativ einfach ineinander überführen.

Hier ein Tutorial zur Überführung der csv-Daten ineinander

Um Daten im csv Format zu importieren, gibt es nun zwei unterschiedliche Wege in R:

3.1 Option A: Nutzung der read.csv() Funktion

Mit der read.csv() Funktion lassen sich csv Daten sehr einfach in eurer Environment bei R laden. Unser Demodatensatz wurde lokal auf einem Rechner erstellt und weist daher einige Besonderheiten auf. In deutschen csv-Dateien ist das Trennzeichen von Spalten meist ein Semikolon (;). Die Standardeinstellung der Funktion read.csv() ist jedoch ein Leerzeichen als Trennzeichen. Daher müssen wir in unserem Fall die Ausprägung sep = ";" spezifieren. Da wir außerdem eine Variable mit Dezimalzahlen haben, spezifieren wir auch `dec = “,”`` , damit diese richtig erkannt werden.

Die Funktion read.csv() lässt sich noch weiter spezifieren. Um weitere Spezifikation zu erhalten, könnt ihr mit help() arbeiten. In manchen Fällen sind hier weitere Spezifierungen nötig, damit euer csv File richtig eingelesen wird.

# Daten importieren 
demodaten <- read.csv(file = "rawdata/demodaten.csv", sep = ";", dec = ",")
demodaten #Daten anschauen 
##    VP_id Geschlecht Alter Gruppe   RT                      Wort
## 1      1          m    18      1 1.34      Apfel, Banane, Kiwi 
## 2      2          w    19      2 1.78             Kiwi, Banane 
## 3      3          m    22      1 2.03         Weintraube, Kiwi 
## 4      4          w    25      2 2.51             Banane, Mango
## 5      5          w    27      1 1.56        Mango, Weintraube 
## 6      6          w    18      2 1.69             Banane, Mango
## 7      7          m    22      1 2.00  Weintraube, Kiwi, Mango 
## 8      8          m    19      2 1.11      Apfel, Banane, Kiwi 
## 9      9          w    23      1 1.63        Weintraube, Apfel 
## 10    10          m    24      2 1.52      Apfel, Banane, Kiwi 
## 11    11          w    27      1 2.16                     Kiwi 
## 12    12          m    19      2 2.13               Weintraube 
## 13    13          w    19      1 1.89                        99
## 14    14          m    28      2 1.75                      <NA>
## 15    15          w    27      1 1.00                    Äpfel 
## 16   16a          w    19      2 1.45                          
## 17   16b          w    19      1   NA          Kiwi, Weintraube
## 18    17          m    20      2 2.34                     Apfel
## 19    18          m    22      1 2.08            Banane, Mango 
## 20    19          m    22      2 1.67 Mango, Weintraube, Apfel 
## 21    20          w    23      1 1.85        Weintraube, Mängo

3.2 Option B: direktes Importieren über RStudio

Eine weitere Möglichkeit ist das Importieren des Datensatzes direkt über RStudio. Da wir in unserem Projekt bereits im richtigen Arbeitsverzeichnis arbeiten, solltet ihr dort auch die csv-Datei finden.

Auswählen der Datei mit einem Mausklick und Wählen von Import

Von dort öffnet sich nun ein weiteres Fenster, das euch eine Vorschau der zu importierenden Daten gibt. Hier sieht man wieder eine Eigenheit unserer csv Daten. In der Standardeinstellung wird ein Komma zur Trennung der Spalten erkannt, was in unserem Datensatz zum Import einer Tabelle mit nur einer Spalte führen würde.

So würden alle Variablen in einer Tabelle landen

Wir setzen also unten in der Einstellung auch hier das Semikolon als Trennzeichen.

Auswahl von Semikolon als Trennzeichen. Wie man sehen kann, sieht der Vorschau der Tabelle, die importiert werden wird, deutlich sinnvoller aus

4 Überblick über die Daten gewinnen

Schauen wir uns nun die importierten Daten an.

demodaten 
##    VP_id Geschlecht Alter Gruppe   RT                      Wort
## 1      1          m    18      1 1.34      Apfel, Banane, Kiwi 
## 2      2          w    19      2 1.78             Kiwi, Banane 
## 3      3          m    22      1 2.03         Weintraube, Kiwi 
## 4      4          w    25      2 2.51             Banane, Mango
## 5      5          w    27      1 1.56        Mango, Weintraube 
## 6      6          w    18      2 1.69             Banane, Mango
## 7      7          m    22      1 2.00  Weintraube, Kiwi, Mango 
## 8      8          m    19      2 1.11      Apfel, Banane, Kiwi 
## 9      9          w    23      1 1.63        Weintraube, Apfel 
## 10    10          m    24      2 1.52      Apfel, Banane, Kiwi 
## 11    11          w    27      1 2.16                     Kiwi 
## 12    12          m    19      2 2.13               Weintraube 
## 13    13          w    19      1 1.89                        99
## 14    14          m    28      2 1.75                      <NA>
## 15    15          w    27      1 1.00                    Äpfel 
## 16   16a          w    19      2 1.45                          
## 17   16b          w    19      1   NA          Kiwi, Weintraube
## 18    17          m    20      2 2.34                     Apfel
## 19    18          m    22      1 2.08            Banane, Mango 
## 20    19          m    22      2 1.67 Mango, Weintraube, Apfel 
## 21    20          w    23      1 1.85        Weintraube, Mängo
# das könnte auch mit der View() Funktion gemacht werden 
# bei dieser öffnen sich die Daten als eigene GUI 

Außerdem schauen wir uns die Art der Variablen mit der str() Funktion an.

str(demodaten)
## 'data.frame':    21 obs. of  6 variables:
##  $ VP_id     : chr  "1" "2" "3" "4" ...
##  $ Geschlecht: chr  "m" "w" "m" "w" ...
##  $ Alter     : int  18 19 22 25 27 18 22 19 23 24 ...
##  $ Gruppe    : int  1 2 1 2 1 2 1 2 1 2 ...
##  $ RT        : num  1.34 1.78 2.03 2.51 1.56 1.69 2 1.11 1.63 1.52 ...
##  $ Wort      : chr  "Apfel, Banane, Kiwi " "Kiwi, Banane " "Weintraube, Kiwi " "Banane, Mango" ...

Wir erkennen also, dass die Variable VP-id, Geschlecht und Wort als Character erkannt werden. Das entspricht in R einer Zeichenkette (also Wörtern). Alter und Gruppe entsprechen der Kategorie integer, also Zahlen und RT ist eine numerische Variable (numeric steht in R für die Bezeichung einer Variablen mit Dezimalzahlen).

4.1 Variablentypen anpassen

Manchmal kann es passieren, dass eine Variable fälschlicherweise erkannt wird oder sie für weitere Analysen umgewandelt werden muss. Gehen wir zum Beispiel davon aus, dass die Gruppen in unseren Daten einer Experimental und einer Kontrollgruppe ensprechen. Wenn wir das nachträglich ändern wollen, können wir die Variable demodaten$Gruppe mithilfe der factor() Funktion umwandeln. Für Gruppenzuordnungen ist es sinnvoll, die entsprechende Variable in einen Faktor umzuwandeln, sodass die entsprechenden Labels einer Gruppenzugehörigkeit zugeordnet werden. Für diese Funktion brauchen wir folgende Spezifierungen:

  • x: die Variable, die geändert werden soll (im Format daten$variable)
  • levels: die vorhandenen Levels
  • labels: die Labels, die den Levels gegeben werden sollen
demodaten$Gruppe <- factor(demodaten$Gruppe, levels = c(1,2), labels = c("exp", "controll"))
str(demodaten)
## 'data.frame':    21 obs. of  6 variables:
##  $ VP_id     : chr  "1" "2" "3" "4" ...
##  $ Geschlecht: chr  "m" "w" "m" "w" ...
##  $ Alter     : int  18 19 22 25 27 18 22 19 23 24 ...
##  $ Gruppe    : Factor w/ 2 levels "exp","controll": 1 2 1 2 1 2 1 2 1 2 ...
##  $ RT        : num  1.34 1.78 2.03 2.51 1.56 1.69 2 1.11 1.63 1.52 ...
##  $ Wort      : chr  "Apfel, Banane, Kiwi " "Kiwi, Banane " "Weintraube, Kiwi " "Banane, Mango" ...

Wir sehen nun, dass unsere Variable Gruppein einen Faktor mit den Levels exp und controll umgewandelt wurde.


Auch die Variable Geschlecht wandeln wir in einen Faktor um, um diese besser für spätere Analysen nutzen zu können.

demodaten$Geschlecht <- factor(demodaten$Geschlecht)
str(demodaten$Geschlecht) #m und w wurden jetzt einer Zahl zugeordnet. 
##  Factor w/ 2 levels "m","w": 1 2 1 2 2 2 1 1 2 1 ...

Weitere Möglichkeiten der Umwandlung von Variablen findet ihr hier

4.2 Weitere Arten des Überblickes über die Daten

Mit der summary() Funktion lassen sich erste Erkenntnisse über die Daten gewinnen.

summary(demodaten)
##     VP_id           Geschlecht     Alter         Gruppe         RT       
##  Length:21          m:10       Min.   :18   exp     :11   Min.   :1.000  
##  Class :character   w:11       1st Qu.:19   controll:10   1st Qu.:1.550  
##  Mode  :character              Median :22                 Median :1.765  
##                                Mean   :22                 Mean   :1.774  
##                                3rd Qu.:24                 3rd Qu.:2.042  
##                                Max.   :28                 Max.   :2.510  
##                                                           NA's   :1      
##      Wort          
##  Length:21         
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

Hier sehen wir grundlegende deskriptivstatistische Werte über numerische Variablen (Minimum & Maximum, Mittelwert und 1. sowie 3. Quantil). Bei dieser Art des Überblicks kann man bereits gut nach fälschlich erkannten fehlenden Werten schauen. Es kann oft passieren, dass statt NA in manchen Arten von Datenfiles 99 / -99 als fehlender Wert kodiert ist. So erhält man nach dem Import eine Mischung aus beiden Arten der Kodierung für fehlende Werte. In unseren Daten sehen wir, dass in der RT Variable bereits ein fehlender Wert korrekterweise erkannt wurde. Wenn wir uns die Gesamtübersicht der Daten anschauen, sehen wir, dass auch in der Wort-Variable fehlende Werte vorhanden sind. Diese werden noch nicht erkannt, da sie fälschicherweise als normaler Character eingelesen werden.

Den Umgang mit solchen fehlenden Werten werden wir uns in Punkt 7 anschauen. Zunächst schauen wir uns aber an, wie man Daten erweitern oder zuschneiden kann.


5 Zuschneiden und Erweitern von Datenframes

Bevor wir mit den Daten aus verschiedenen Erhebungen arbeiten können, werfen wir noch mal einen Blick auf unseren bereits vorhandenen Datenframe. Hier sehen wir eine Besonderheit bei der Person 16. Sie scheint zwei Eintragungen in der Tabelle zu haben, wobei in einer nur der Wert in der Reaktionszeit und in der anderen nur die Worte eingetragen wurden. So ein Fall kann auftreten, wenn beispielsweise das Experiment abgestürzt ist und die Untersuchung mit neu gestartetem Experiment wieder aufgenommen wurde.

demodaten 
##    VP_id Geschlecht Alter   Gruppe   RT                      Wort
## 1      1          m    18      exp 1.34      Apfel, Banane, Kiwi 
## 2      2          w    19 controll 1.78             Kiwi, Banane 
## 3      3          m    22      exp 2.03         Weintraube, Kiwi 
## 4      4          w    25 controll 2.51             Banane, Mango
## 5      5          w    27      exp 1.56        Mango, Weintraube 
## 6      6          w    18 controll 1.69             Banane, Mango
## 7      7          m    22      exp 2.00  Weintraube, Kiwi, Mango 
## 8      8          m    19 controll 1.11      Apfel, Banane, Kiwi 
## 9      9          w    23      exp 1.63        Weintraube, Apfel 
## 10    10          m    24 controll 1.52      Apfel, Banane, Kiwi 
## 11    11          w    27      exp 2.16                     Kiwi 
## 12    12          m    19 controll 2.13               Weintraube 
## 13    13          w    19      exp 1.89                        99
## 14    14          m    28 controll 1.75                      <NA>
## 15    15          w    27      exp 1.00                    Äpfel 
## 16   16a          w    19 controll 1.45                          
## 17   16b          w    19      exp   NA          Kiwi, Weintraube
## 18    17          m    20 controll 2.34                     Apfel
## 19    18          m    22      exp 2.08            Banane, Mango 
## 20    19          m    22 controll 1.67 Mango, Weintraube, Apfel 
## 21    20          w    23      exp 1.85        Weintraube, Mängo
demodaten[16:17, ]
##    VP_id Geschlecht Alter   Gruppe   RT             Wort
## 16   16a          w    19 controll 1.45                 
## 17   16b          w    19      exp   NA Kiwi, Weintraube

In der ersten Tabelle gibt es die VP mit der ID 16 zweimal. Sie scheint die Untersuchung abgebrochen zu haben und dann in einer neuen Runde wieder aufgenommen. Sie hat in einer Zeile einen Wert bei der RT und in der anderen bei Wort. Wir müssen diese also zusammenfügen, ohne dass Informationen verloren gehen.

5.1 Zeilen / Spalten abschneiden

In unserem Fall ist das Zusammenfügen sehr einfach und soll nur das Grundprinzip des Auswählens von Zeilen/Spalten eines Datenframes zum Entfernen veranschaulichen. Unsere Person 16 hat einen fehlenden Wert in der Spalte demodaten$Wortfür die Zeile 16a. Dieser befindet sich in Zeile 16a. Der Rest der Werte (Alter, ID) ist identisch. In unserem Falle reicht es also aus, den Wert für Wort aus der Zeile 17 in die obere Zeile einzusetzen und dann die untere zu löschen.

!Beim Löschen von Zeilen sollte man generell vorsichtig arbeiten und den Rohdatenframe nicht überschreiben, sodass Information nicht fälschlicherweise verloren geht!

demodaten$Wort[16] <- demodaten$Wort[17] #Wert aus Wort Spalte von Zeile 17 in Zeile 16 einsetzen 

# über die eckige Klammer greifen wir auf die Zeilen und Spalten des Datenframes zu 
# die Logik hier ist immer Daten[Zeilen, Spalten]
# um alle Zeilen oder Spalten auszuwählen muss lediglichh ein Leerzeichen gesetzt werden 

demodaten_neu <- demodaten[-c(17), ] #neuer Datenframe alle Spalten erhalten aber ohne Zeile 17  
demodaten_neu #Ausgabe zur Kontrolle 
##    VP_id Geschlecht Alter   Gruppe   RT                      Wort
## 1      1          m    18      exp 1.34      Apfel, Banane, Kiwi 
## 2      2          w    19 controll 1.78             Kiwi, Banane 
## 3      3          m    22      exp 2.03         Weintraube, Kiwi 
## 4      4          w    25 controll 2.51             Banane, Mango
## 5      5          w    27      exp 1.56        Mango, Weintraube 
## 6      6          w    18 controll 1.69             Banane, Mango
## 7      7          m    22      exp 2.00  Weintraube, Kiwi, Mango 
## 8      8          m    19 controll 1.11      Apfel, Banane, Kiwi 
## 9      9          w    23      exp 1.63        Weintraube, Apfel 
## 10    10          m    24 controll 1.52      Apfel, Banane, Kiwi 
## 11    11          w    27      exp 2.16                     Kiwi 
## 12    12          m    19 controll 2.13               Weintraube 
## 13    13          w    19      exp 1.89                        99
## 14    14          m    28 controll 1.75                      <NA>
## 15    15          w    27      exp 1.00                    Äpfel 
## 16   16a          w    19 controll 1.45          Kiwi, Weintraube
## 18    17          m    20 controll 2.34                     Apfel
## 19    18          m    22      exp 2.08            Banane, Mango 
## 20    19          m    22 controll 1.67 Mango, Weintraube, Apfel 
## 21    20          w    23      exp 1.85        Weintraube, Mängo

Das gleiche Prinzip ließe sich auch anwenden, um Spalten aus einem Datensatz zu entfernen. Würden wir beispielsweise einen Datensatz erstellen wollen, der nur die Personendaten und die RT enthält, würde man analog zu oben, wie folgt vorgehen.

demodaten_ohne_Wort <- demodaten_neu[ , -c(6)] #neuer Datenframe ohne Spalte 6 (Wortspalte)

Um mehrere Spalten oder Zeilen zu entfernen muss man innerhalb des Vektors c() eine Range definieren. Wollten wir also bis auf die Personendaten alle Daten entfernen würde man so vorgehen:

demodaten_ohneWort_ohneRT <- demodaten_neu[ , -c(5:6)]

Exkurs: Buchstaben aus VP_id entfernen

Ein weiteres Problem mit der Person 16 in unserem Datensatz bestand darin, dass sie mit 16a und 16b kodiert wurde. Das ist nachträglich etwas unschön, da die VPs so nicht mehr einheitlich kodiert sind. Wir bereinigen also unsere Spalte VP_id um jegliche Buchstaben. Dafür verwenden wir eine weitere neue nützliche Funktion. Die mutate() Funktion aus dem dplyr Paket. Sie eignet sich besonders gut für Pipes und in längeren Befehlen. Mit der Funktion wird in dem datensatz, in dem gearbeitet wird eine neue Zeile definiert oder eine bestehende überschrieben. So muss diese nicht immer neu definiert werden über demodaten$VP_id.

demodaten_neu <- demodaten_neu %>% #Pipe definieren ("Nehme demodaten und nutze es für mutate()")
  mutate(VP_id = parse_number(VP_id)) #nun wird Spalte VP_id genommen und überschrieben 
#parse_number() ist eine Funktion, die Zahlen aus der Spalte ausliest und nur diese weitergibt. So werden alle Buchstaben gelöscht. 

demodaten_neu[16, ]   #zur Überprüfung Zeilen ausgeben lassen 
##    VP_id Geschlecht Alter   Gruppe   RT             Wort
## 16    16          w    19 controll 1.45 Kiwi, Weintraube

Exkurs Ende

5.2 Datenframes zusammenführen

Oft liegen die Daten von Versuchspersonen in verschiedenen Datenframes vor, wenn in Experimenten Teile in anderen Seiten erhoben wurden. So kann es sein, dass ein typischer Fragebogen zur Stimmung in SoSci erhoben wurde und ein Reaktionszeitexperiment in Pavlovia. Die Aufgabe in der Datenaufbereitung besteht dann zunächst im Zusammenführen der Daten zu einem Datenframe.

Wir importieren dazu einen weiteren Datensatz aus einer weiteren Erhebung.

demodaten_2 <- read.csv("rawdata/demodaten2.csv", sep = ";", dec = ",") #Importieren der Daten 
demodaten_2 # Daten anschaue
##    VP_id   RT2
## 1      1  1.56
## 2      2  2.78
## 3      3  2.23
## 4      4  1.86
## 5      5  1.64
## 6      6  1.43
## 7      7  1.92
## 8      8  1.23
## 9      9  1.90
## 10    10  2.53
## 11    11  2.03
## 12    12 99.00
## 13    13  1.88
## 14    14  1.56
## 15    15  1.92
## 16    16  1.74
## 17    17  1.58
## 18    18  2.01
## 19    19  2.25
## 20    20  1.79
# Wir sehen, dass es sich um die gleichen 16 Versuchspersonen handelt. 

Für diese Art der Datentransformation eignet sich besonders gut das dplyr Paket. In diesem gibt es dafür 4 wichtige Funktionen. Dabei werden durch einen Join Daten rechts vom Originaldatensatz hinzugefügt. Die verschiedenen Ausprägungen der Funktionen definieren, welche Beobachtungseinheiten erhalten bleiben (also welche Tabelle die Beobachtungseinheiten bestimmt). Das ist relevant, weil zusammenzuführende Datensätze mitunter nicht dieselbe Länge haben können. Um Tabellen dann dennoch zusammenzuführen, kann man verschiedene Arten des “Abschneidens” wählen.

hier einmal das Zusammenführen von Tabellen unterschiedlicher Länge veranschaulicht, durch left_koin bleibt Person mit ID F erhalten, da sie in linker aber nicht rechter Tabelle auftaucht. Person E, die nur in rechter Tabelle ist, wird gelöscht.

  1. left_join(): Beobachtungseinheiten aus Originaltabelle (links) bleiben erhalten

  2. right_join(): Beobachtungseinheiten aus Zieltabelle (rechts) bleiben erhalten

  3. inner_join(): alle nicht übereinstimmenden Beobachtungseinheiten werden ausgeschlossen

  4. outer_join(): behält alle Beobachtungseinheiten.

In unserem Falle sind die Versuchspersonen identisch. Wir können die left-join() Funktion nutzen.

# library(dplyr)
# die Funktion left_join() funktioniert so: 
# left_join(Originaldaten, neue Daten, by = "Spalte anhand der vorgangen werden soll (i.d.R. ID)") 

demodaten_zusammen <- left_join(demodaten_neu, demodaten_2, by = "VP_id") #beide Tabellen 
demodaten_zusammen
##    VP_id Geschlecht Alter   Gruppe   RT                      Wort   RT2
## 1      1          m    18      exp 1.34      Apfel, Banane, Kiwi   1.56
## 2      2          w    19 controll 1.78             Kiwi, Banane   2.78
## 3      3          m    22      exp 2.03         Weintraube, Kiwi   2.23
## 4      4          w    25 controll 2.51             Banane, Mango  1.86
## 5      5          w    27      exp 1.56        Mango, Weintraube   1.64
## 6      6          w    18 controll 1.69             Banane, Mango  1.43
## 7      7          m    22      exp 2.00  Weintraube, Kiwi, Mango   1.92
## 8      8          m    19 controll 1.11      Apfel, Banane, Kiwi   1.23
## 9      9          w    23      exp 1.63        Weintraube, Apfel   1.90
## 10    10          m    24 controll 1.52      Apfel, Banane, Kiwi   2.53
## 11    11          w    27      exp 2.16                     Kiwi   2.03
## 12    12          m    19 controll 2.13               Weintraube  99.00
## 13    13          w    19      exp 1.89                        99  1.88
## 14    14          m    28 controll 1.75                      <NA>  1.56
## 15    15          w    27      exp 1.00                    Äpfel   1.92
## 16    16          w    19 controll 1.45          Kiwi, Weintraube  1.74
## 17    17          m    20 controll 2.34                     Apfel  1.58
## 18    18          m    22      exp 2.08            Banane, Mango   2.01
## 19    19          m    22 controll 1.67 Mango, Weintraube, Apfel   2.25
## 20    20          w    23      exp 1.85        Weintraube, Mängo   1.79

Wir sehen also, dass unsere Tabelle nun aus den Personendaten, der RT und Wortspalte aus der ersten Tabelle und aus der RT2 Variable besteht.

Wenn ihr mehr über die verschiedenen Arten der Datentransformation erfahren wollt, ist hier ein weiteres Tutorial.


6 Umbenennung von Variablen

Für die Umbenennung von Variablen bieten sich in R mehrere Varianten an. In diesem Tutorial habe ich die rename()Funktion aus dem dplyr Paket gewählt. Um unsere Variablennamen noch einmal zu anzuschauen, checken wir diese kurz mit der rename() Funktion.

names(demodaten_zusammen)
## [1] "VP_id"      "Geschlecht" "Alter"      "Gruppe"     "RT"        
## [6] "Wort"       "RT2"

Angenommen wir wollen unsere VP_id Variable in eine kürzere Variante ID und unsere Wort Variable in Antwort umbenennen, dann definiert man in einem Vektor die umzubennenenden Variablen in der Reihenfolge "alter Name" = "neuer Name". Es bietet sich der Nachvollziehbarkeit auch hier an, einen neuen Datensatz mit umbenannten Variablen zu speichern. Für unser Beispiel sieht der Code wie folgt aus:

Vertiefend befinden sich hier weitere Varianten zur Umbenennung von Variablen.


7 Umgang mit fehlenden Werten

Beim Import von Daten kann es manchmal passieren, dass fehlende Werte nicht mehr einheitlich kodiert sind oder nicht korrekt erkannt werden. Um nach fehlenden Werten zu schauen, gibt es die Option, mit dem Befehl is.na() nach Missings zu fragen. Der Output dieser Funktion ist eine logische Abfrage nach fehlenden Werten in den einzelnen Spalten und Zeilen des Datenframes mit TRUE oder FALSE. Man kann auch nur nach einzelnen Variablen aus dem Datensatz fragen.

is.na(demodaten_zusammen_umbenannt)
##          ID Geschlecht Alter Gruppe    RT Antwort   RT2
##  [1,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [2,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [3,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [4,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [5,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [6,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [7,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [8,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
##  [9,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [10,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [11,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [12,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [13,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [14,] FALSE      FALSE FALSE  FALSE FALSE    TRUE FALSE
## [15,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [16,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [17,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [18,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [19,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE
## [20,] FALSE      FALSE FALSE  FALSE FALSE   FALSE FALSE

Wir sehen, dass in unserem Datensatz keine fehlenden Werte erkannt werden. Mit dieser Information sollten wir allerdings sehr vorsichtig umgehen, da wir unsere Daten ja bereits angeschaut haben und wissen sollten, dass es Missings gibt. Solltet ihr euch unsicher sein, ist ein Blick in die summary() Funktion sinnvoll.

summary(demodaten_zusammen_umbenannt)
##        ID        Geschlecht     Alter            Gruppe         RT       
##  Min.   : 1.00   m:10       Min.   :18.00   exp     :10   Min.   :1.000  
##  1st Qu.: 5.75   w:10       1st Qu.:19.00   controll:10   1st Qu.:1.550  
##  Median :10.50              Median :22.00                 Median :1.765  
##  Mean   :10.50              Mean   :22.15                 Mean   :1.774  
##  3rd Qu.:15.25              3rd Qu.:24.25                 3rd Qu.:2.042  
##  Max.   :20.00              Max.   :28.00                 Max.   :2.510  
##    Antwort               RT2        
##  Length:20          Min.   : 1.230  
##  Class :character   1st Qu.: 1.625  
##  Mode  :character   Median : 1.890  
##                     Mean   : 6.742  
##                     3rd Qu.: 2.080  
##                     Max.   :99.000

Hier sticht besonders die Variable RT2 mit einem Maximalwert von 99 heraus. Dieser ist ein fehlender Wert der VP. In vielen Anwendungen wird 99 oder -99 als Kodierung für fehlende Werte genutzt. Ein Blick auf die übrigen Reaktionszeiten macht deutlich, dass dies kein sinnvoller Wert ist (wenn überhaupt ein sehr starker Extremwert, Umgang mit solchen ist noch mal ein ganz anderes Kapitel und gehört in die Datenauswertung).

Um bestimmte Werte (wie in unserem Falle die 99) als Missing zu kodieren, kann man das händisch für einzelne Variablen des Datensatzes tun:

demodaten_zusammen_umbenannt$RT2[demodaten_zusammen_umbenannt$RT2 == 99] <- NA #Werte mit 99 werden mit NA überschrieben 
is.na(demodaten_zusammen_umbenannt$RT2) #Ausgabe fehlender Werte für RT2 als Überprüpfung 
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Wir sehen also, dass nun ein Wert in der Spalte RT2 als fehlender Wert erkannt wird.

In unserem Datensatz gibt es nun jedoch eine Besonderheit in der Variable Antwort. Ein Blick in die Antworten der VPs zeigt uns, dass auch hier zwei Zeilen fehlende Werte aufweisen.

demodaten_zusammen_umbenannt$Antwort 
##  [1] "Apfel, Banane, Kiwi "      "Kiwi, Banane "            
##  [3] "Weintraube, Kiwi "         "Banane, Mango"            
##  [5] "Mango, Weintraube "        "Banane, Mango"            
##  [7] "Weintraube, Kiwi, Mango "  "Apfel, Banane, Kiwi "     
##  [9] "Weintraube, Apfel "        "Apfel, Banane, Kiwi "     
## [11] "Kiwi "                     "Weintraube "              
## [13] "99"                        NA                         
## [15] "Äpfel "                    "Kiwi, Weintraube"         
## [17] "Apfel"                     "Banane, Mango "           
## [19] "Mango, Weintraube, Apfel " "Weintraube, Mängo "

Zeile 13 und 14 weisen zwei unterschiedlich kodierte fehlende Werte auf. Diese wollen wir nun auch noch als solche definieren.

demodaten_zusammen_umbenannt$Antwort[demodaten_zusammen_umbenannt$Antwort == c("99", "NA")] <- NA 
is.na(demodaten_zusammen_umbenannt$Antwort) #zur Überprüfung fehlende Werte der Variable ausgeben lassen
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [13]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE

Wir sehen, dass die Werte aus Zeile 13 und 14 jetzt beide als fehlender Wert erkannt werden.


8 Bearbeiten von Wörtern und Buchstaben

In unserem Datensatz befinden sich sowohl numerische Variablen, als auch Variablen, die aus einer Wortliste bestehen. Die Basics zum Aufbereiten von numerischen Variablen, Faktoren integers sind wir bereits durchgegangen. Ein Aufbereitungsschritt, der oft in der Aufbereitung von textbasierten Variablen notwendig ist, ist das Bearbeiten einzelner Buchstaben oder Wörter. Ein häufiges Problem sind hier Umlaute, wie ä, ö oder ü. So kann es bei der Übersetzung der Daten in unterschiedliche Programmiersprachen zu einem buchstäblichen Buchstabensalat kommen, wenn diese unterschiedliche übersetzt werden.

8.1 Genereller Tipp: Nutzung von UTF-8

Ein genereller Tipp wäre bei der Datenerhebung und Speicherung bereits darauf zu achten, dass alle Dateiformate und Programme mit UTF-8 von Zeichen in Bytes übersetzt werden. Diese Zeichenkodiersprache kann alle deutschen Umlaute und viele andere Sprachen mit komplexeren Zeichen problemlos abbilden.

Wenn ihr Umlaute in euren Daten vorliegen habt, kann es dennoch nützlich sein, diese in ihre volle Ausschreibung zu ändern, damit bei späteren Datentransformationen keine Probleme entstehen.

8.2 Nach Buchstaben / Worten in den Daten suchen

Schauen wir uns zunächst einmal an, wie man in character Variablen in R nach bestimmten Buchstaben oder Zeichenfolgen suchen kann, um auf Vorhandensein von Umlauten zu prüfen. Dafür verwenden wir die grep()Funktion (kein Paket nötig, sie ist Teil von base R). In dieser definieren wir:

  • pattern: der Buchstabe oder Zeichenfolge, nach der gesucht werden soll

  • x: der Vektor, in dem nach dem Pattern gesucht werden soll

  • ignore.case: TRUE (ignoriert Groß- und Kleinschreibung, sodass alle Zeichen unabhängig von Schreibweise erkannt werden) oder FALSE (beachtet Groß- und Kleinschreibung)

  • value: TRUE (R gibt uns Inhalt der erkannten Worte / Sätze an) und FALSE (gibt nur logische Antwort mit TRUE / FALSE, ob gesuchtes Pattern vorhanden ist)

grep(pattern = "ä", demodaten_zusammen_umbenannt$Antwort, ignore.case = TRUE, value = TRUE)
## [1] "Äpfel "             "Weintraube, Mängo "

Wir sehen also, dass wir zwei Zeilen in der Antwort Variable haben, die den Umlaut ä aufweisen.

8.3 Buchstaben / Wörter ersetzen

Im Folgenden wollen wir uns dafür die str_replace_all() Funktion aus dem stringr Paket anschauen. Das Vorgehen ist analog für jegliche Buchstaben und Zeichen, die ihr aus euren Daten direkt ersetzen wollt. Wir nutzen es, um alle ä mit ae zu ersetzen. Dabei ist wichtig, dass die Funktion str_replace_all() case sensitive ist. Wenn ihr also auch großgeschriebene Umlaute / Buchstaben in den Daten habt, müsst ihr diesen extra ersetzen. Man kann wie in unserem Codebeispiel alle zu ersetzenden Buchstaben aber auch in einem Vektor schreiben innerhalb einer Pipe, sodass alle relevanten Zeichen für den gesamten Datensatz gleichzeitig ersetzt werden. Folgende Bestandteile benötigen wir für die Pipe:

  • mutate(): Operator , mit dem vorhandene Spalten verändert werden, er geht zeilenweise durch den Datensatz vor

  • across(): legt fest, dass mehrere Spalten (also jede Spalte in einer Zeile) gleichzeitig verändert werden können

  • where(): sucht nach bestimmten Eigenschaften in den Spalten

  • is.character wählt alle Spalten aus, in denen ein Character enthalten ist (in unserem Falle sollte das nur die Antwort Variable sein)

  • eine Lambda Funktion ~ diese bestimmt, dass die für alle Character-Spalten nun ein bestimmtes Zeichen ersetzt werden soll, in dieser steht .xals Platzhalter für die zuvor ausgewählte Spalte

demodaten_all_clean <- demodaten_zusammen_umbenannt %>% 
  mutate(across(where(is.character), ~ str_replace_all(.x, c("ä" = "ae", "Ä" = "Ae")))) 

grep(pattern = "ae", demodaten_all_clean$Antwort, ignore.case = TRUE, value = TRUE)
## [1] "Aepfel "             "Weintraube, Maengo "

Wie man erkennen kann, wurden der Umlaut in beiden Fällen mit ae ersetzt. Wenn ihr noch mehr Information, über das Suchen und Bearbeiten verschiedener Character Variablen haben wollt, dann findet ihr hier ein hilfreiches Tutorial dazu.


9 Umkodieren und Reskalieren von Items aus Skalen

Zuletzt widmen wir uns einem weiteren wichtigen Schritt der Datenaufbereitung, der häufig Anwendung findet. In vielen Daten ist es notwendig, Items umzukodieren. Das ist zum Beispiel der Fall, wenn man Items einer Likert Skala (z.B. mit den Ausprägungen von “stimme voll und ganz zu” zu “stimme überhaupt nicht zu” hat) entgegengesetzt gepolt hat. Dies ist der Fall für sogenannte Kontrollfragen, die der/dem VersuchsleiterIn erlauben zu prüfen, ob die Fragen von der VP einfach durchgekreuzt wurden. So kann es sein, dass die Items einer Skala hohe Werte für hohe Zustimmung und geringe Werte für geringe Zustimmung aufweisen. Für umgepolte Items ist dann genau das Gegenteil der Fall. Dann ist Umkodierung notwendig, sodass bei späterer Skalenbildung (z.B. Mittelwert) keine Verzerrung entsteht bzw. das Konstrukt unbrauchbar wird.

Hierfür gibt es in R verschiedene Möglichkeiten. In diesem Tutorial verwenden wir den recode()Befehl aus dem dplyr Paket. Da es mehrere recode() Funktionen in verschiedenen Paketen von R gibt, ist es hier wichtig mit dem Aufrufen der Funktion auch das Paket zu spezifizieren, aus welcher die Funktion genutzt werden soll. Das kann man in R über die Schreibweise paket::funktion also in unserem Falle dplyr::recode() machen.

Zu Demonstrationszwecken erstelle ich nur einen Vektor, der aus den Zahlen 1-5 besteht und wir rekodieren diese so in folgender Logik (alt = neu):

  • 1 –> 5
  • 2 –> 4
  • 3 –> 3
  • 4 –> 2
  • 5 –> 1

Die recode() Funktion folgt hier der gleichen Logik, in dem auch "alt" = "neu" definiert werden muss. Es bietet sich außerdem an, die rekodierten Variablen in einem neuem Datenframe oder in unserem einfachen Falle in einer neuen Variable zu speichern, sodass im Falle eines Fehlers immer noch Zugriff auf die alte Variable besteht.

Zahlenvektor <- c(1,2,3,4,5)
Zahlenvektor # unser umzukodierender Vektor 
## [1] 1 2 3 4 5
Zahlenvektor_recoded <- dplyr::recode(Zahlenvektor, #für einen Datenframe daten$variable 
                               `1`= 5, #alt = neu 
                               `2`= 4, 
                               `3`= 3, 
                               `4`= 2, 
                               `5`= 1)
Zahlenvektor_recoded #Prüfen 
## [1] 5 4 3 2 1

Wir sehen also, dass unsere Werte nun nach vorgebener Logik rekodiert wurden.


10 10. Daten speichern

Um unser aufbereitetes Dokument nun als Excel-Datei zu speichern benötigen wir ein weiteres Paket. Mit dem openxl Paket kann man Datenframes in Environment als Excel-Datei speichern.

Mit der write.xlsx() Funktion kann man hier ganz einfach Datenframes als Excel-Datei speichern. Die Funktion braucht folgende Spezifierungen:

  • x: der Datenframe im Environment

  • "file_path": der Dateipfad, unter dem die Datei gespeichert werden soll.

write.xlsx(demodaten_all_clean, file = "outputs/2025_01_09_demodaten_clean.xlsx")