library(learnr) data(mdbf, package = 'PsyBSc7') knitr::opts_chunk$set(echo = FALSE) learnr::tutorial_options(exercise.eval = FALSE)
mdbf_r <- mdbf neg <- c(3, 4, 5, 7, 9, 11)
mdbf_r <- mdbf mdbf_r[, 3] <- -1 * (mdbf_r[, 3] - 5) neg <- c(3, 4, 5, 7, 9, 11)
empVar <- function(x) { n <- length(x) s2 <- sum((x - mean(x))^2)/n return(s2) }
empVar <- function(x) { n <- length(x) s2 <- sum((x - mean(x))^2)/n return(s2) }
Vari <- function(x, empirical) { n <- length(x) if (empirical) { s2 <- sum((x - mean(x))^2)/n } else { s2 <- sum((x - mean(x))^2)/(n-1) } return(s2) }
Vari <- function(x, empirical) { n <- length(x) if (empirical) { s2 <- sum((x - mean(x))^2)/n } else { s2 <- sum((x - mean(x))^2)/(n-1) } return(s2) }
Vari <- function(x, empirical = TRUE) { n <- length(x) if (empirical) { s2 <- sum((x - mean(x))^2)/n } else { s2 <- sum((x - mean(x))^2)/(n-1) } return(s2) }
if
und else
Im Prozess der Datenaufbereitung und -auswertung kommt man häufig an den Punkt, an dem etwas nur unter bestimmten Bedingungen durchgeführt werden sollte. Wie in eigentlich allen Programmiersprachen werden wenn-dann Beziehungen auch in R mit if
erzeugt. Dabei folgt auf ein if
in normalen Klammern die Bedingung und dann in geschwungenen Klammern die Konsequenz. Zum Beispiel:
if (weekdays(Sys.Date()) == 'Donnerstag') { 'R Kurs um 8!' }
Hier wird zuerst die Bedingung evaluiert:
(weekdays(Sys.Date()) == 'Donnerstag')
Als Ergebnis wird ein logisches Element erwartet: also ein Objekt das entweder TRUE
oder FALSE
ist. Wie im letzten Semester besprochen, können logische Bedingungen mit &
(logisches "und") und |
(logisches "oder") verknüpft werden. Wenn das logische Objekt TRUE
enthält, wird die R-Syntax in den geschwungenen Klammern ausgeführt; wenn es FALSE
enthält, passiert nichts. Zum Beispiel:
(weekdays(Sys.Date()) == 'Samstag' | weekdays(Sys.Date()) == 'Sonntag')
Um zwischen zwei Alternativen auszuwählen, kann mit dem else
gearbeitet werden:
if (weekdays(Sys.Date()) == 'Donnerstag') { 'R Kurs um 8!' } else { 'Ausschlafen' }
Für einzelne Ereignisse kann in R die Notation außerdem mithilfe der ifelse
-Funktion abgekürzt werden. Die Funktion nimmt drei Argumente entgegen:
- test
: die Bedingung
- yes
: was getan werden soll, wenn die Bedingung zutrifft
- no
: was getan werden soll, wenn die Bedingung nicht zutrifft
ifelse(weekdays(Sys.Date()) == 'Donnerstag', 'R Kurs um 8!', 'Ausschlafen')
Die ausgiebige Notation ist immer dann von Vorteil, wenn der auszuführende R-Code mehrere Zeilen lang ist oder z.B. weitere Bedingungen enthält. Natürlich sind wenn-dann Ausführungen eigentlich hauptsächlich dann nützlich, wenn Code für verschiedene Daten, Objekte oder Funktionen mehrfach genutzt werden soll und man nicht in jedem Einzelfall weiß, welche Inhalte die Objekte haben, mit denen man arbeitet. Ein einfaches Beispiel mit einer Zufallszahl könnte so aussehen:
x <- sample(1:10, 1) if (x > 5) { y <- 1 } else { y <- 0 } x y
Loops (oder Schleifen) bieten die Möglichkeit den gleichen R-Code mehrmals anzuwenden, ohne ihn wiederholt schreiben zu müssen. Gerade in Kombination mit if
und else
kann man so sehr kurze, leserliche Skripte verfassen und potentielle Fehler, die sich in sehr lange Skripte gerne einschleichen, umgehen. In R werden drei Arten von Loops unterschieden: for
-Loops, while
-Loops und repeat
-Loops.
Loops sind zum Beispiel nützlich für das Rekodieren von Items. Der mdbf
Datensatz enthält 98 Beobachtungen auf 12 Variablen, allesamt Items des Mehrdimensionalen Befindlichkeitsfragebogens. In diesem Fragebogen werden Adjektive zur Beschreibung der aktuellen Stimmung genutzt um die drei Dimensionen der Stimmung - Gut vs. Schlecht, Wach vs. Müde und Ruhig vs. Unruhig - zu erheben:
data(mdbf, package = 'PsyBSc7') str(mdbf)
Variable | Adjektiv | Richtung | Dimension
-------- | -------- | -------- | ---------
stim1
| zufrieden | positiv | Gut vs. Schlecht
stim2
| ausgeruht | positiv | Wach vs. Müde
stim3
| ruhelos | negativ | Ruhig vs. Unruhig
stim4
| schlecht | negativ | Gut vs. Schlecht
stim5
| schlapp | negativ | Wach vs. Müde
stim6
| gelassen | positiv | Ruhig vs. Unruhig
stim7
| müde | negativ | Wach vs. Müde
stim8
| gut | positiv | Gut vs. Schlecht
stim9
| unruhig | negativ | Ruhig vs. Unruhig
stim10
| munter | positiv | Wach vs. Müde
stim11
| unwohl | negativ | Gut vs. Schlecht
stim12
| entspannt | positiv | Ruhig vs. Unruhig
Um die drei Skalenwerte berechnen zu können müssen die jeweils "negativen" Adjektive ins Positive umgepolt werden. Hierzu gibt es zum Beispiel folgende zwei Möglichkeiten:
mdbf$stim4_r <- -1 * (mdbf$stim4 - 5) mdbf$stim4_r[mdbf$stim4 == 1] <- 4 mdbf$stim4_r[mdbf$stim4 == 2] <- 3 mdbf$stim4_r[mdbf$stim4 == 3] <- 2 mdbf$stim4_r[mdbf$stim4 == 4] <- 1
Mit Hilfe der Loops können wir uns die Arbeit ersparen, diesen Abschnitt für jedes negative Adjektiv schreiben zu müssen.
for
-LoopsIn for
-Loops wird ein Abschnitt von R-Syntax für jedes Element in einem vorab festgelegten Objekt durchgeführt. Es ist also nötig, vorher zu wissen, für welche Fälle ein Skript durchgeführt werden muss.
# Kopie des Datensatzes erstellen # Datenverlust vorbeugen mdbf_r <- mdbf # Vektor der negativen Items neg <- c(3, 4, 5, 7, 9, 11)
In neg
wird kodiert, welche Items negativ formuliert. Mit der for
-loop wird jedes Element dieses dann Vektors ein mal genutzt:
for (i in neg) { mdbf_r[, i] <- -1 * (mdbf_r[, i] - 5) }
Zur Prüfung des Erfolges:
cor(mdbf[, 3], mdbf_r[, 3])
while
-LoopsIn while
-Loops wird der Code so lange ausgeführt, wie ein vorab definiertes Kriterium erfüllt ist. Ein einfaches Beispiel wäre es, so lange einen Münzwurf zu simulieren, bis man 10 mal "Kopf" geworfen hat.
# Münze erstellen coin <- c('Kopf', 'Zahl') # Leeres Objekt für die Aufzeichnung erstellen toss <- NULL # Loop while (sum(toss == 'Kopf')<10) { toss <- c(toss, sample(coin, 1)) } # Würfe ansehen toss
repeat
-LoopsIm Gegensatz zu for
und while
wird bei repeat
zunächst kein explizites Abbruchkriterium definiert. Stattdessen wird repeat
häufig genutzt, wenn es verschiedene oder veränderliche Abbruchkriterien für den Loop gibt. Diese Kriterien werden bei repeat
allerdings innerhalb des Loops definiert - in den meisten Fällen wird dazu über if
mindestens eine Bedingung definiert, unter der die Ausführung abgebrochen werden soll.
Ein einfaches Beispiel hierfür ist es, eine Fibonacci Sequnz zu bilden (eine Sequenz in der eine Zahl immer die Summe der vorherigen beiden Zahlen ist) und die Sequenz abzubrechen, wenn die letzte Zahl z.B. größer als 1000 ist. Ich kann nicht vorab bestimmen, welches Element das sein wird, wodurch es geschickter wird innerhalb des Loops das Kriterium zu evaluieren.
fibo <- c(1, 1) repeat { n <- length(fibo) fibo <- c(fibo, fibo[n] + fibo[n - 1]) if (fibo[n+1] > 1000) break } fibo
Loops können mit break
unterbrochen werden - das gilt nicht nur für repeat
, sondern auch für die anderen beiden Formen von Loops.
Generell sollten Loops in R nur genutzt werden, wenn keine Vektor-basierte Alternative zur Verfügung steht. Zum Beispiel: um eine Variable zu zentrieren sollte nicht ein Loop genutzt werden, der von jedem Element den Mittelwert abzieht. Stattdessen ist R in der Lage den Mittelwert direkt von jedem Element des Vektors abzuziehen - diese Umsetzung ist also direkt Vektor-basiert und in R (beinahe ausnahmslos) die schnellere und effizientere Variante.
Die apply
-Familie ermöglicht darüber hinaus Loops sehr kurz zu schreiben, weil sie eine Funktion für alle Elemente eines Objekts via Loop anwendet.
Funktionen, die in R angewendet werden können, sind ebenfalls Objekte. Dadurch können eigene Funktionen wie andere Objekte auch angelegt werden - dazu müssen sie lediglich mit der function
-Funktion erstellt werden. Im Allgeimenen sieht das wie folgt aus:
eigene_funktion <- function(argument1, argument2, ...) { # Durchgeführte Operationen }
Wie bei Loops auch, wird die durchgeführte Syntax durch geschwungene Klammern umschlossen. Als Argumente können beliebig viele Einstellungen für die Funktion definiert werden, auf die dann in der Funktion Bezug genommen wird. Wichtig ist dabei, dass Funktionen keinen generellen Zugriff auf den Workspace haben, sondern alle Objekte, die sie benötigen durch die Argumente an sie weitergegeben werden sollten.
Ein einfaches Beispiel: in R wird mit der var
-Funktion die Schätzung für die Populationsvarianz $\widehat{\sigma}^2$ und nicht die empirische Varianz $s$ bestimmt. Wir können also eigene Funktion anlegen, die die empirische Varianz schätzt. Dafür können wir die Formel zur Varianzberechnung einfach in R-Code übersetzen:
$$s^2 = \frac{\sum_{i=1}^n(x_i - \bar{x})^2}{n}$$
Als R-Code also:
x <- mdbf[, 1] n <- length(x) s2 <- sum((x - mean(x))^2) / n s2
Dieser Code funktioniert allerdings nur für eine einzige Variable mit dem Namen x
und wir müssten den Code für jede einzelne Anwendung wiederholen. Um das abzukürzen, können wir eine eigene, wiedervendbare Funktion anlegen:
empVar <- function(x) { n <- length(x) s2 <- sum((x - mean(x))^2)/n return(s2) }
Diese Funktion kann jetzt auf jede beliebige Variable angewendet werden:
empVar(mdbf[, 1]) empVar(mdbf[, 2])
Das Einzige, was diese Funktion von in R implementierten Paketen unterscheidet ist, dass sie im Workspace angezeigt wird:
ls()
Weil beim Durchführen von Funktionen als erstes der Workspace nach definierten Funktionen durchsucht wird, sollten Funktionen möglichst einzigartig benannt werden, weil sonst nicht mehr (so leicht) auf die R-internen Funktionen zugegriffen werden kann.
Funktionen sollten prinzipiell mit return
Ergebnisse nach außen geben. Eine Funktion ohne return
wird zwar durchgeführt, man hat aber keinen Zugriff auf das Ergebnis, weil alle innerhalb der Funktion angelegten Objekte entfernt werden, sobald die Durchführung der Funktion abgeschlossen ist:
empVar <- function(x) { n <- length(x) s2 <- sum((x - mean(x))^2)/n } empVar(mdbf[, 2]) s2
Hier wird mit s2
also das schon vorhandene Objekt abgefragt. Das s2
in der Funktion ist nur innerhalb der Funktion ansprechbar - das im Workspace liegende s2
hingegen, ist nur außerhalb der Funktion ansprechbar! Zusätzlich sollte beachtet werden, dass return
nur ein einziges Argument entgegennimmt: Funktionen in R können also nur ein einziges Objekt als Ergebnis liefern. Wenn mehrere Ergebnisse ausgegeben werden sollen, müssen diese vorher zu einem Objekt (meistens einer Liste) zusammengefasst werden.
empVar <- function(x) { n <- length(x) s2 <- sum((x - mean(x))^2)/n out <- list(s2 = s2, n = n) return(out) } empVar(mdbf[, 2])
Funktionen können also eine beliebige Anzahl von Argumente entgegennehmen, aber nur ein einziges Objekt als Ergebnis liefern. Um eine gemeinsame Funktion für beide Formen der Varianz zu haben, könnten wir die Anzahl der Argumente erweitern:
Vari <- function(x, empirical) { n <- length(x) if (empirical) { s2 <- sum((x - mean(x))^2)/n } else { s2 <- sum((x - mean(x))^2)/(n-1) } return(s2) }
In diesem Fall wird also eine Einstellung für empirical
benötigt, die dann von ìf
als TRUE
oder FALSE
bewertet werden kann:
Vari(mdbf[, 2], TRUE) Vari(mdbf[, 2], FALSE)
Wenn wir die Einstellung vergessen, wird - wie bei allen anderen R Funktionen auch - ein Fehler produziert:
Vari(mdbf[, 2])
Wenn Voreinstellungen für Argumente festgelegt werden sollen, erreichen wir das durch:
Vari <- function(x, empirical = TRUE) { n <- length(x) if (empirical) { s2 <- sum((x - mean(x))^2)/n } else { s2 <- sum((x - mean(x))^2)/(n-1) } return(s2) }
Vari(mdbf[, 2])
Solange jetzt nicht explizit etwas bei der Anwendung der Funktion für empirical
deklariert wird, wird von dieser Voreinstellung ausgegangen. Wie hier gezeigt, sind if
und else
in Funktionen häufig relevant, um die Funktion für verschiedene Situationen nutzbar zu machen. Auch Loops können in Funktionen definiert und genutzt werden.
Zur Bearbeitung der Aufgaben öffnen Sie bitte ein neues RStudio-Fenster. Nutzen Sie für die Aufgaben den Datensatz mdbf.rds
. Die Daten können mittels des PsyBSc7::Aufgaben_3()
-Befehls geladen werden. Der Datensatz enthält 98 Beobachtungen auf 12 Variablen, allesamt Items des Mehrdimensionalen Befindlichkeitsfragebogens. In diesem Fragebogen werden Adjektive zur Beschreibung der aktuellen Stimmung genutzt um die drei Dimensionen der Stimmung - Gut vs. Schlecht, Wach vs. Müde und Ruhig vs. Unruhig - zu erheben.
Variable | Adjektiv | Richtung | Dimension
-------- | -------- | -------- | ---------
stim1
| zufrieden | positiv | Gut vs. Schlecht
stim2
| ausgeruht | positiv | Wach vs. Müde
stim3
| ruhelos | negativ | Ruhig vs. Unruhig
stim4
| schlecht | negativ | Gut vs. Schlecht
stim5
| schlapp | negativ | Wach vs. Müde
stim6
| gelassen | positiv | Ruhig vs. Unruhig
stim7
| müde | negativ | Wach vs. Müde
stim8
| gut | positiv | Gut vs. Schlecht
stim9
| unruhig | negativ | Ruhig vs. Unruhig
stim10
| munter | positiv | Wach vs. Müde
stim11
| unwohl | negativ | Gut vs. Schlecht
stim12
| entspannt | positiv | Ruhig vs. Unruhig
Schreiben Sie eine Funktion, die für alle Variablen in einem beliebigen Datensatz gleichzeitig jeweils die univariate Normalverteilung mit dem Shapiro-Wilk-Test prüft und die Verteilungen grafisch darstellt. Ermöglichen Sie dabei durch ein Argument die Wahl zwischen Histogramm und QQ-Plot und legen Sie dafür eine Voreinstellung fest. Wenden Sie die Funtion zum Schluss auf den Datensatz mdbf
an. Vergessen nicht, den Datensatz hierfür aber vorher zu laden.
Bonus:
- Nutzen Sie das main
-Argument um die Abbildungen mit den korrekten Namen der dargestellten Variablen zu versehen.
- Geben Sie die Ergebnisse der Shapiro-Tests als data.frame
mit den Spalten Variablenname, Teststatistik und $p$-Wert aus.
Für die einfache Variante:
Mit den Bonus-Aufgaben:
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.