Skip to content
Snippets Groups Projects
dplyr-grammar.Rmd 13.68 KiB
title: "Einführung in dplyr-Grammatik"
subtitle: "Daten bändigen & visualisieren"
author: "B. Philipp Kleer"
date: "11. Oktober 2021"
output: 
  slidy_presentation:
      footer: "Copyright: CC BY-SA 4.0, B. Philipp Kleer"
      widescreen: true
      highlight: pygments
      theme: readable
      css: styles/style-slides.css
      df_print: paged
      mathjax: default
      self_contained: false
      incremental: false #True  dann jedes Bullet einzeln
      collapse: true # means the text output will be merged into the R source code block
library("knitr")
library("rmarkdown")
library("tidyverse")

uni <- readRDS("../datasets/uni.rds")

opts_chunk$set(fig.path = 'pics/s6-', # path for calculated figures
               fig.align = 'center',  # alignment of figure (also possible right, left, default)
               fig.show = 'hold', # how to show figures: hold -> direct at the end of code chunk; animate: all plots in an animation
               fig.width = 3,   # figure width
               fig.height = 4,  # figure height
               echo = TRUE,     # Code is printed
               eval = FALSE,    # Code is NOT evaluated
               warning = FALSE, # warnings are NOT displayed
               message = FALSE, # messages are NOT displayed
               size = "tiny",  # latex-size of code chunks
               background = "#E7E7E7", # background color of code chunks
               comment = "", # no hashtags before output
               options(width = 80),
               results = "markdown",
               rows.print = 15
)

htmltools::tagList(
  xaringanExtra::use_clipboard(
    button_text = "<i class=\"fa fa-clipboard\"></i>",
    success_text = "<i class=\"fa fa-check\" style=\"color: #90BE6D\"></i>",
    error_text = "<i class=\"fa fa-times-circle\" style=\"color: #F94144\"></i>"
  ),
  rmarkdown::html_dependency_font_awesome()
)

Starten wir!

Nun tauchen wir in die Welt von dplyr ein. Das Paket nutzt man oft, um Datenstrukturen zu erkunden oder Transformationen vorzunehmen. Dabei gibt es einen Grundstock an Vokabeln, die über piping miteinander verbunden werden.

Dazu installieren wir zuerst tidyverse:

install.packages("tidyverse")
library("tidyverse")

# alternativ: 
# install.packages("dplyr")
# library("dplyr")

Anschließend laden wir den Datensatz uni ins environment.

uni <- readRDS("../datasets/uni.rds") #oder eigener Pfad, wenn nicht in der Cloud

Wir verschaffen uns einen Überblick über den Datensatz:

uni

Einen Überblick über die Variablen:

# ID: laufende Nummer
# mot: Studienmotivation (0 <sehr niedrig> - 10 <sehr hoch>)
# study: Studienfach (1 <Political Science>, 2 <Sociology>, 3 <Educational Science>, 4 <Psychology>)
# city: Studienort (1 <Gießen>, 2 <Marburg>, 3 <Frankfurt>)
# distance: Anfahrtsdauer zur Uni in Minuten
# abi: Abiturnote
# term: Fachsemester

Grundvokabeln in dplyr

In dplyr gibt es nicht viele Vokabeln, die aber effektiv miteinander verbunden werden können, um Daten zu sortieren bzw. zu manipulieren.

Die Grundvokabeln lernen wir jetzt im Folgenden erstmal ohne piping kennen:

  • select()
  • slice()
  • filter()
  • arrange()
  • mutate()
  • summarise() / summarize()
  • group_by()

select()

Mit select() wählen wir Spalten aus, die uns angezeigt werden

select(uni,   # Datenquelle
       c(mot, # ausgewählte Spalten
         term
         )
       )

slice()

Demgegenüber können wir mit slice() Zeilen auswählen, also Fälle:

slice(uni,    # Datenquelle
      50:55
      )  # ausgewählte Zahlen

filter()

Mit filter() können wir spezifische Fälle des Datensatzes auswählen. Zur Erinnerung die logischen Verknüpfungen in R:

  • logisches und: &
  • logisches oder: |
  • logisches gleich: ==
  • logisches ungleich: !=
  • logisches größer: >
  • logisches kleiner: <
  • logisches kleiner gleich: <=
  • logisches größer gleich: >=
filter(uni,  # Datenquelle
       city == "Giessen" # Filtervariable
       )
filter(uni,
       city == "Frankfurt" & study == "Political Science"
       )

arrange()

Mit arrange() können wir den Datensatz sortieren.

arrange(uni, 
        abi
        )

Die Sortierung ist dabei immer aufsteigend. Dies kann man über die Funktion desc() ändert (descending):

arrange(uni,
        desc(abi)
        )

Alternativ kann man auch einfach ein Minuszeichen vor die Variable, nach der sortiert werden soll, setzen:

arrange(uni, 
        -abi
        )

mutate()

Mit mutate() werden neue Variablen geschaffen.

Zum Beispiel könnten wir eine Variable schaffen, die den Abstand zum Mittelwert in der Variable abi misst:

mutate(uni, 
       abiDist = abi - mean(abi,
                             na.rm = TRUE
                            )
       )

Wichtig: Wir haben zwar hier die Variable abiDist gespeichert, aber diese nicht im Datensatz gespeichert.

Bei der Erstellung kategorieller Variablen muss man zusätzlich die Funktion case_when() nutzen. case_when() funktioniert wie eine Aneinanderreihung von if-Bedingung, wobei die spezifischste Bestimmung zuerst kommen sollte. (spezifisch -> allgemein).

Im Beispiel schaffen wir eine Dummy-Variable, die anzeigt, ob die Person in Marburg studiert (1) oder nicht (0).

Die Grammatik in case_when() ist wie folgt:

case_when(Fallauswahl ~ neuer Codewert)
mutate(uni,
       dum.pum = case_when(city == "Marburg" ~ 1, # "Fallauswahl" ~ "neuer Codewert"
                           city == "Gießen" ~ 0, 
                           city == "Frankfurt" ~ 0
                           )
       )

Auch hier könnten mehrere Bedingungen verknüpft werden: So möchten wir einen Dummy schaffen, der anzeigt, ob eine Person in Marburg Erziehungswissenschaften studiert.

Wir würden wie folgt beginnen:

mutate(uni, 
       dum.pum.es = case_when(city == "Marburg" & study == "Educational Science" ~ 1
                              )
       )

Wenn man nicht alle verschiedenen Kombinationen eingeben möchte und zum Beispiel nur eine von Interesse ist, kann man mit TRUE ~ 0 allen restlichen Fällen direkt einen Wert zuordnen (aber nur denselben Wert!). Alle Kombinationen, die nicht vor TRUE ~ 0 definiert wurden, erhalten automatisch den in der TRUE-Zeile definierten Wert.

mutate(uni, 
       dum.pum.es = case_when(city == "Marburg" & study == "Educational Science" ~ 1,
                              TRUE ~ 0
                              )
       )

summarize()

Mit summarize() können vereinfacht erste Einblicke in die Daten erfolgen. So könnten wir uns z.B. den Mittelwert von term ausgeben lassen.

summarize(uni, 
          mean(term)
          )

In summarize() können verschiedene Funktionen genutzt werden, die auf die Variablen im Datensatz angewendet werden können. Auch können direkt mehrere Werte ausgegeben werden. Wichtig: Das Ausgabe-Format ist immer ein tibble.

summarize(uni,
          mean(term),
          mean(mot)
          )

Die Unterfunktion summarize_if() bietet dazu die Möglichkeit leicht auf eine Gruppe von Variablen Funktionen anzuwenden, also zum Beispiel auf alle numerischen Variablen:

summarize_if(uni,
             is.numeric, 
             list(mean = mean, 
                  sd = sd
                  )
             )

Wer weiß, warum hier teils NA angezeigt wird?

Die Unterfunktion summarise_at() bietet die Möglichkeit nur bei bestimmten Variablen die Funktion anzuwenden:

summarize_at(uni,
             vars(mot,
                  abi,
                  term
                  ),
             list(mean = mean, 
                  sd = sd
                  )
             )

group_by()

Mit group_by() kann der Datensatz gruppiert werden, also zum Beispiel nach eine kategoriellen Variable. In uni-Datensatz zum Beispiel nach study:

group_by(uni,
         study
         )

Was sehen wir? Nichts!

group_by() macht nichts weiter als die Daten zu gruppieren, die Ausgabe verändert sich dabei erstmal nicht. Erst in Kombination mit weiteren Funktionen, wird dies sichtbar:

summarize(group_by(uni, 
                   study
                   ), 
          mean(term)
          )

Jetzt haben wir für jeden Studienort einen Mittelwert für das Fachsemester (term).

*Wichtig: Wenn Daten gespeichert oder übergeben werden, sollte am Ende die Befehlskette immer mit ungroup() enden, um die Datenteilung nicht zu übergeben!

Piping

Mit den sogenannte pipes können Ergebnisse von Ausführungsschritten weitergegeben werden. Dies ist vorteilhaft, da so verschiedene Schritte direkt ausgeführt werden können. Auch kann so Code oftmas leichter nachvollzogen werden.

Den pipe-Operator %>% kann man einfach per Tastenkürzel hinzufügen (Strg/Cmd + Shift + M).

Seit R Version 4.0 gibt es den Pipe-Operator auch in R Base, allerdings wird in R Base |> als pipe-Operator genutzt. Es kann sein, dass es bereits packages gibt, die damit umgehen können, allerdings benötigt das auf Entwicklerseite meist etwas Zeit.

Hier mal ein Beispiel: Das Ziel ist es eine Variable zu erstellen, die den Abiturschnitt pro Uni-Stadt ausgibt. Das könnte die Frage beantworten, ob besonders gute Schüler:innen einen der drei Studienorte präferieren.

Die Schritte, die wir hierbei machen, sind folgende:

  1. Wir geben den Datensatz uni weiter.
  2. Wir gruppieren den Datensatz nach city.
  3. Wir berechnen eine neue Variable abiMean.
  4. Wir heben die Gruppierung wieder auf. 5./0. Wir überspeichern den alten Datensatz.
uni <- uni %>%     # Schritt 1 / Schritt 5/0
  group_by(city) %>%  # Schritt 2
  mutate(abiMean = mean(abi, 
                         na.rm = TRUE
                        )
         ) %>% # Schritt 4
  ungroup() #Schritt 5

table(uni$city, 
      uni$abiMean)

Alternativ könnten wir uns dies auch erstmal nur ausgeben lassen.

uni %>% 
  group_by(city) %>% 
  summarize(mean = mean(abi, 
                        na.rm = TRUE
                        )
            )

Ein weiteres Beispiel: Wir möchten Studierende nach der Anzahl des Fachsemesters kategorisieren. Die neue Variable termg soll zwischen:

  • Anfänger:innen (<=2 Semester)
  • Erfahrene (>2 & <= 6 Semester)
  • Langzeitstudierende (>6 Semester)

unterscheiden.

uni <- uni %>%
  mutate(termg = case_when(term <= 2 ~ "Anfänger:in", 
                           term > 2 & term <= 6 ~ "Erfahrene", 
                           term > 6 ~ "Langzeit"
                           )
         )
table(uni$termg)
str(uni$termg)

Etwas komplexer wäre folgende Aufgabe: Wir möchten nicht die Abweichung zum Mittelwert des Abiturs in unserer gesamten Erhebung berechnen, sondern die Abweichung zum Mittelwert der einzelnen Universitäten. Damit wir die Gruppen-Mittelwerte angezeigt bekommen, berechnen wir auch eine Variable für den Gruppen-Mittelwert.

uni <- uni %>%
  group_by(city) %>%
  mutate(abigm = mean(abi)) %>%
  mutate(abid = abi - abigm) %>%
  ungroup()

uni[, c("ID",
        "abi",
        "city", 
        "abigm", 
        "abid"
        )
    ]

Alternativ könnten wir die Daten auch hierarchisch nach Standort und Studienfach gruppieren und uns dann einfach die unterschiedlichen Mittelwerte mit summarize() ausgeben lassen:

mCityStudy <- uni %>%
  group_by(city,
           study
           ) %>%
  summarize(mean(abi))

mCityStudy

Versuchen wir es zusammen zu lösen!

Versucht euch mit dem Grundvokabular an folgenden Aufgaben in den Breakout-Rooms oder allein:

  1. Teile den Datensatz uni in drei Datensätze, die jeweils nur eine Universitätsstadt inkludieren.

  2. Berichte die durchschnittliche Semesterzahl pro Uni und Studiengang!

  3. Berechne eine Variable, die die Abweichung von der durchschnittlichen Semesterzahl nach Studienfach angibt.

Lab Task

In der nächsten halbe Stunde sollt ihr euch in Gruppen (Breakout-Rooms) oder einzeln an den folgenden Aufgaben versuchen. Es müssen nicht alle Aufgaben in der Zeit geschafft werden, es geht viel mehr um die Auseinandersetzung mit dem neuen Vokabular.