Spezielle Datentypen & fortgeschrittene Programmierung

Data Science 1 - Programmieren & Visualisieren

Saskia Otto & Monika Eberhard

Universität Hamburg, IMF

Wintersemester

Lernziele

Am Ende dieser VL- und Übungseinheit werden Sie

  • Faktoren mit Faktorstufen und Labels erstellen und deren Reihenfolge z.B. in Grafiken ändern können.
  • Datum- und Zeitangaben mit lubridate umwandeln und Komponenten extrahieren können.
  • Strings modifizieren und kombinieren können.
  • Bedingungen mit if..else.., ifelse() und case_when() formulieren können.
  • for-Schleifen für wiederkehrende Aufgaben schreiben können.
  • eigene Funktionen mit Argumenten und Standardwerten definieren können.
  • einen ersten Eindruck von regulären Ausdrücken und funktionaler Programmierung haben.

DSB Cheatsheets

Eine komplette Übersicht der wichtigsten Funktionen gibt es hier:

Handling spezieller Datentypen

Fortgeschrittene Programmierung in R

Diese wie alle anderen DSB Cheatsheets gibt es auf Moodle und der DSB Website unter Ressourcen > Cheatsheets & Guides.

Kategoriale Variablen als Faktoren

Warum Faktoren?

Faktoren sind Vektoren, die nur vorbestimmte Werte (Faktorstufen = levels) enthalten können, und werden für kategoriale Variablen verwendet. Intern sind sie ganzzahlige Vektoren mit den Attributen class (‘factor’) und levels.

Stellen Sie sich vor, Sie haben einen Vektor vom Typ ‘character’, der Monate enthält:

x1 <- c("Dec", "Apr", "Jan", "Mar")


Problem 1

Es findet keine Sortierung in einer hilfreichen Art und Weise statt.

sort(x1)
[1] "Apr" "Dec" "Jan" "Mar"

Problem 2

Es gibt nur zwölf mögliche Monate, und nichts rettet Sie davor Tippfehler zu machen.

x2 <- c("Dec", "Apr", "Jam", "Mar")

→ Ein einfaches factor(x1) oder as.factor(x1) löst die Probleme noch nicht – die Faktorstufen werden weiterhin alphabetisch sortiert.

Faktoren | levels definieren

Lösung in factor()

Übergeben Sie dem Argument levels einen Vektor mit allen gültigen Faktorstufen in gewünschter Reihenfolge.

months <- c("Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
f1 <- factor(x1, levels = months)
  • Sie können hier Faktorstufen einfügen, die nicht unbedingt im zu konvertierenden Vektor sein müssen.
  • Jeder Wert, der nicht im Faktorstufen-Vektor ist, wird “leise” als NA konvertiert.
  • Mit levels(f1) lassen Sie sich die gültigen Faktorstufen anzeigen.
Problem 1 → gelöst
sort(f1)
[1] Jan Mar Apr Dec
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Problem 2 → gelöst
factor(x2, levels = months)
[1] Dec  Apr  <NA> Mar 
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

Faktoren | labels, ordered & cut()

Sichtbare Namen der Faktorstufen umbenennen mit 'labels'
factor(c("Dec", "Apr", "Jan", "Mar"), levels = months, labels = 1:12)
[1] 12 4  1  3 
Levels: 1 2 3 4 5 6 7 8 9 10 11 12

→ labels wird immer zusammen mit levels angegeben und muss dieselbe Länge und Reihenfolge haben: Das erste Label benennt die erste Faktorstufe um, das zweite die zweite usw.

Ordinal-skalierte Variablen: 'ordered'
x3 <- c(2, 1, 3, 1, 1, 2)
lab <- c("low", "medium", "high")
o3 <- factor(x3, 
  levels = 1:3, 
  labels = lab, 
  ordered = TRUE
)
o3
[1] medium low    high   low    low    medium
Levels: low < medium < high
Kontinuierliche Variablen zu Faktoren
x <- c(1.2, 3.8, 2.5, 4.9)
cut(x, breaks = 2)
[1] (1.2,3.05] (3.05,4.9] (1.2,3.05] (3.05,4.9]
Levels: (1.2,3.05] (3.05,4.9]

→ Die Anzahl der Faktorstufen wird mit dem breaks Argument bestimmt.

Weitere Optionen mit ‘forcats’

Ein hilfreiches ‘tidyverse’ Paket

  • Umgedrehte Reihenfolge: fct_rev()
  • Ordnung entsprechend
    • der Häufigkeit: fct_infreq()
    • der ersten Erscheinung: fct_inorder()
  • Zusammenlegung von Faktorstufen: fct_collapse()
  • Sortierung entlang einer anderen Variable: fct_reorder()
  • Manuelle Ordnung der Faktorstufen: fct_relevel()
Code
p <- bshydro15 |> ggplot() +
  guides(x = guide_axis(angle = 45)) +
  theme(text = element_text(size = 16))

p1 <- p + geom_bar(aes(month)) + ggtitle("Standardeinstellung")
p2 <- p + geom_bar(aes(fct_rev(month))) + ggtitle("fct_rev()")
p3 <- p + geom_bar(aes(fct_infreq(month))) + ggtitle("fct_infreq()")
p4 <- p + geom_bar(aes(fct_collapse(
    month, 
    q1 = c("Jan", "Feb", "Mar"),
    q2 = c("Apr", "May", "Jun"),
    q3 = c("Jul", "Aug", "Sep"),
    q4 = c("Oct", "Nov", "Dec")
  ))) + 
  xlab("annual quarter") +
  ggtitle("fct_collapse()")

gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 2)

Your turn …

Quiz 1-2 | Faktoren

mantas <- data.frame(
  month = factor(c(9, 3, 5, 1, 7, 12),
    levels = 1:12,
    labels = c('Jan','Feb','Mar','Apr','May','Jun',
      'Jul','Aug','Sep','Oct','Nov','Dec') ),
  catch = c(13,11,26,5,22,8)
)
ggplot(mantas, aes(x = month, y = catch)) +
  geom_col()

mantas |>
  mutate(month = fct_reorder(
    .f = month, .x = catch)) |>
  ggplot(aes(x = month, y = catch)) +
  geom_col()

Der Umgang mit Datum und Zeit

Zeit ist nicht gleich Zeit

Datum und Zeit in R

Sys.Date()
[1] "2026-10-06"
class(Sys.Date())
[1] "Date"
unclass(Sys.Date())
[1] 20732
Sys.time()
[1] "2026-10-06 21:35:03 CEST"
class(Sys.time())
[1] "POSIXct" "POSIXt" 
unclass(Sys.time())
[1] 1791315304
  • → Zuerst kommt Jahr-Monat-Tag (yyyy-mm-dd), dann die Zeit (hh:mm:ss) und dann die Zeitzone (hier CEST = Central European Summer Time). Die angezeigte Zeitzone hängt von den Systemeinstellungen des jeweiligen Rechners ab und kann bei Ihnen daher anders aussehen.
  • → unclass() zeigt die Anzahl der TAGE bzw. SEKUNDEN seit dem 1. Januar 1970 (00:00:00).
  • Date → repräsentiert nur Kalenderdaten (in Tibbles: <date>)
  • POSIXct → repräsentiert Kalenderdaten und Uhrzeit bis zur Sekunde, einschließlich Zeitzone (in Tibbles: <dttm>)

Datum und Zeit definieren mit ‘lubridate’

Datum- und Zeitangaben werden beim Import oft nicht automatisch erkannt, sondern als ‘character’ eingelesen. Das Paket lubridate (seit tidyverse 2.0 ein Kernpaket) macht die Umwandlung einfach:

Nur Datum (Date)

  • Abhängig von der Reihenfolge der Datumskomponenten:
    • ymd(), ydm(), mdy(), myd(), dmy(), dym()
  • Die Reihenfolge ist entscheidend, das Format wird automatisch erkannt.
ymd("2026-11-17")
[1] "2026-11-17"
mdy("Nov 17th, 2026")
[1] "2026-11-17"
dmy(c("17-Nov-2026", "20.11.2026"))
[1] "2026-11-17" "2026-11-20"

Datum und Zeit (POSIXct)

Eine der 6 Datumfunktionen kombinieren mit:

  • _h → nur Stunde
  • _hm → Stunde und Minute
  • _hms → Stunde, Minute und Sekunde
mdy_hm("11/17/2026 12:11")
[1] "2026-11-17 12:11:00 UTC"
ymd_hms("2026-11-17 12:11:59", 
  tz = "CET")
[1] "2026-11-17 12:11:59 CET"

→ Ohne tz Argument wird die Zeitzone UTC (Coordinated Universal Time) angenommen.

Datumskomponenten extrahieren

  • Für Aggregationszwecke ist es häufig hilfreich, individuelle Komponenten zu extrahieren.
  • Die Helferfunktionen in lubridate haben einfach den Namen der Komponente:
  • year(), semester(), quarter()
  • month(), week()
  • mday() - day of the month
  • yday() - day of the year
  • wday() - day of the week
  • hour(), minute(), second()
dt_tm <- mdy_hm("11/17/2026 12:11")
year(dt_tm)
[1] 2026
month(dt_tm)
[1] 11
mday(dt_tm)
[1] 17
yday(dt_tm)
[1] 321

Zwischen POSIXct und Date wechseln Sie mit as_date() und as_datetime() – dabei kann Information (die Uhrzeit) verloren gehen.

Your turn …

Quiz 3 | Datum & Zeit

Folgender Tibble enthält mehrere Datum- bzw. Datum/Zeit-Variablen, die aber alle als ‘character’ Datentyp vorliegen:

tbl
# A tibble: 10 × 5
   x1         x2         x3           x4                  x5              
   <chr>      <chr>      <chr>        <chr>               <chr>           
 1 2015-07-26 2015/26/07 Jul 26, 2015 2015-07-26 18:16:00 26.07.2015T18:16
 2 2015-07-03 2015/03/07 Jul 03, 2015 2015-07-03 16:42:00 03.07.2015T16:42
 3 2015-09-01 2015/01/09 Sep 01, 2015 2015-09-01 04:45:00 01.09.2015T04:45
 4 2015-06-03 2015/03/06 Mar 03, 2015 2015-06-03 08:08:00 03.06.2015T08:08
 5 2015-10-15 2015/15/10 Oct 15, 2015 2015-10-15 21:05:00 15.10.2015T21:05
 6 2015-10-01 2015/01/10 Oct 01, 2015 2015-10-01 10:38:00 01.10.2015T10:38
 7 2015-03-14 2015/14/03 Mar 14, 2015 2015-03-14 05:55:00 14.03.2015T05:55
 8 2015-02-25 2015/25/02 Jun 25, 2015 2015-02-25 08:12:00 25.02.2015T08:12
 9 2015-06-03 2015/03/06 Sep 03, 2015 2015-06-03 11:09:00 03.06.2015T11:09
10 2015-09-14 2015/14/09 Jul 14, 2015 2015-09-14 05:38:00 14.09.2015T05:38
tbl <- mutate(tbl,
  x1 = ymd(x1), x2 = ydm(x2),
  x3 = mdy(x3), x4 = ymd_hms(x4),
  x5 = dmy_hm(x5) )
tbl # check
# A tibble: 10 × 5
   x1         x2         x3         x4                  x5                 
   <date>     <date>     <date>     <dttm>              <dttm>             
 1 2015-07-26 2015-07-26 2015-07-26 2015-07-26 18:16:00 2015-07-26 18:16:00
 2 2015-07-03 2015-07-03 2015-07-03 2015-07-03 16:42:00 2015-07-03 16:42:00
 3 2015-09-01 2015-09-01 2015-09-01 2015-09-01 04:45:00 2015-09-01 04:45:00
 4 2015-06-03 2015-06-03 2015-03-03 2015-06-03 08:08:00 2015-06-03 08:08:00
 5 2015-10-15 2015-10-15 2015-10-15 2015-10-15 21:05:00 2015-10-15 21:05:00
 6 2015-10-01 2015-10-01 2015-10-01 2015-10-01 10:38:00 2015-10-01 10:38:00
 7 2015-03-14 2015-03-14 2015-03-14 2015-03-14 05:55:00 2015-03-14 05:55:00
 8 2015-02-25 2015-02-25 2015-06-25 2015-02-25 08:12:00 2015-02-25 08:12:00
 9 2015-06-03 2015-06-03 2015-09-03 2015-06-03 11:09:00 2015-06-03 11:09:00
10 2015-09-14 2015-09-14 2015-07-14 2015-09-14 05:38:00 2015-09-14 05:38:00

Strings

Was ist nochmal ein ‘string’?

Im Deutschen: Zeichenkette oder Zeichenfolge

  • 2 oder mehr Zeichen (Zahlen, Buchstaben, Schriftzeichen), die in innerhalb eines Paares von einfachen oder doppelten Anführungszeichen geschrieben werden.
  • Variablen oder Vektoren mit Zeichenketten sind in R immer vom Datentyp ‘character’.
Ein berühmtes Zitat von Albert Einstein:
einstein <- c("The difference", 'between stupidity', 
  "and genius is that", 'genius has its limits.')
typeof(einstein)
[1] "character"
length(einstein)
[1] 4

→ Der Vektor einstein beinhaltet 4 Elemente oder präziser 4 Zeichenketten.

Manipulation von Zeichenketten & das Paket ‘stringr’

Auch wenn Sie keine aufwendige Textanalyse vorhaben, kann es für Sie hilfreich sein Wissen über die Handhabung und Verarbeitung von Zeichenketten in R zu haben!

Wozu brauche ich das?

Sie möchten z.B.

  • Spaltennamen vereinheitlichen,
  • Stations- oder Artnamen korrigieren, kürzen oder kombinieren,
  • Kategorien re-klassifizieren,
  • viele Dateien mit ähnlichem Dateinamen importieren.

→ In stringr (Teil des tidyverse) starten alle Funktionen mit str_ und haben konsistente Argumente.

Strings modifizieren | 1

Schreibweise ändern

  • str_to_lower()
    → Umwandlung zu Kleinbuchstaben
  • str_to_upper()
    → Umwandlung zu Großbuchstaben
  • str_to_sentence()
    → erster Buchstabe groß
  • str_to_title()
    → Anfangsbuchstabe jedes Wortes groß
str_to_lower(einstein)
[1] "the difference"         "between stupidity"      "and genius is that"    
[4] "genius has its limits."
str_to_upper(einstein)
[1] "THE DIFFERENCE"         "BETWEEN STUPIDITY"      "AND GENIUS IS THAT"    
[4] "GENIUS HAS ITS LIMITS."
str_to_sentence(einstein)
[1] "The difference"         "Between stupidity"      "And genius is that"    
[4] "Genius has its limits."
str_to_title(einstein)
[1] "The Difference"         "Between Stupidity"      "And Genius Is That"    
[4] "Genius Has Its Limits."

Strings modifizieren | 2

Schreibweise ändern

Anwendungsbeispiel: Spaltennamen überschreiben
names(iris) <- str_to_lower(names(iris))
head(iris)
  sepal.length sepal.width petal.length petal.width species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Strings modifizieren | 3

Leerzeichen entfernen

  • str_trim() → Entfernt ‘white space’ am Anfang und Ende einer Zeichenkette.
Anwendungsbeispiel: Kategorien mit Leerzeichen bereinigen
dat <- read.csv("data/medley.csv")
str(dat)
'data.frame':   34 obs. of  3 variables:
 $ station  : chr  "ER1         " "ER2         " "ER3         " "ER4         " ...
 $ zinc     : chr  "BACK        " "HIGH        " "HIGH        " "MEDIUM      " ...
 $ diversity: num  2.27 1.25 1.15 1.62 1.7 0.63 2.05 1.98 1.04 2.19 ...
unique(dat$zinc)  # uuups
[1] "BACK        " "HIGH        " "MEDIUM      " "LOW         "
dat$zinc <- str_trim(dat$zinc)
unique(dat$zinc)
[1] "BACK"   "HIGH"   "MEDIUM" "LOW"   

Strings kombinieren | 1

  • str_c(..., sep = "", collapse = NULL) → Fügt 2 oder mehr Skalare oder Vektoren (egal welcher Datentyp!) elementweise zu einem Zeichenvektor zusammen.
    • sep → Optionales Trennzeichen zwischen den einzelnen zu verkettenden Elementen.
    • collapse → Trennzeichen zwischen den Elementen des Output Vektors unter weiterer Verkettung zu einem einzigen String.
Mit/ohne 'sep' Argument
# 2 Skalare - Standardeinstellung 
str_c("A", "B")
[1] "AB"
# 2 Skalare - mit Trennzeichen
str_c("A", "B", sep = "/")
[1] "A/B"
# 2 Vektoren - mit Trennzeichen 
str_c(c("A","B"), 1:2, 
  sep = "-")
[1] "A-1" "B-2"
Mit 'collapse' Argument
# 1 Vektor - kollabierend zu 1 String
str_c(c("A","B"), collapse = " + ")
[1] "A + B"
str_c(einstein, collapse = " ")
[1] "The difference between stupidity and genius is that genius has its limits."
# 2 Vektoren - kollabierend zu 1 String
str_c(c("A","B","C"), 1:3, sep = ".", 
  collapse = ", ") 
[1] "A.1, B.2, C.3"

Strings kombinieren | 2

Anwendungsbeispiel 1

Legendentext ändern
iris |> mutate(
  Species = str_c("I. ", Species)) |>
  ggplot(aes(Sepal.Length, Petal.Length)) +
  geom_point(aes(colour = Species)) +
  theme(
    legend.text = element_text(
    face = "italic"), 
    text = element_text(size = 12)
  )

Strings kombinieren | 3

Anwendungsbeispiel 2

Deskriptive Statistiktabelle
iris |> mutate(
  Species = str_c("Iris ", Species)) |>
  group_by(Species) |>
  summarise(
    slm = round(mean(Sepal.Length), 1),
    sls = round(sd(Sepal.Length), 2),
    slms = str_c(slm, sls, sep=" ± ")
  ) |>
  select(Species, slms) |>
  knitr::kable(
    col.names = c("Species", 
      "Mean Sepal Length (± st.dev)")
  ) # erstellt Tabellen in LaTeX, HTML
Species Mean Sepal Length (± st.dev)
Iris setosa 5 ± 0.35
Iris versicolor 5.9 ± 0.52
Iris virginica 6.6 ± 0.64

Muster in Zeichenketten

Muster austauschen mit str_replace() | 1

Muster austauschen mit str_replace() | 2

Anwendungsbeispiel: Bereinigen der Spaltennamen
hydro <- read.csv("data/1111473b.csv")
names(hydro)
 [1] "Cruise"                   "Station"                 
 [3] "Type"                     "yyyy.mm.ddThh.mm"        
 [5] "Latitude..degrees_north." "Longitude..degrees_east."
 [7] "Bot..Depth..m."           "PRES..db."               
 [9] "TEMP..deg.C."             "PSAL..psu."              
[11] "DOXY..ml.l."             
names(hydro) <- names(hydro) |>
  str_to_lower() |>
  str_replace(pattern = "\\.$", replacement = "") |>
  str_replace_all(pattern = "\\.+", replacement = "_") 
names(hydro)
 [1] "cruise"                 "station"                "type"                  
 [4] "yyyy_mm_ddthh_mm"       "latitude_degrees_north" "longitude_degrees_east"
 [7] "bot_depth_m"            "pres_db"                "temp_deg_c"            
[10] "psal_psu"               "doxy_ml_l"             

Überblick der regulären Ausdrücke

  • Die Muster werden mit regulären Ausdrücken (‘regex’) beschrieben, einer kurzen und prägnanten Sprache zur Beschreibung von Mustern in Zeichenketten.
  • Mehr dazu auf der Webseite www.regular-expressions.info mit vielen Beispielen und Tutorials.

Adaptiert vom RegEx cheatsheet von Ian Kopacka.

Your turn …

Quiz 4-5 | Strings

Q4

x
[1] "Placement in the"                    
[2] "genus is provisional pending"        
[3] "the discovery of juvenile specimens."
y <- ...(x)
y
[1] "Placement In The"                    
[2] "Genus Is Provisional Pending"        
[3] "The Discovery Of Juvenile Specimens."

Q6

str_c('A', 1:5, sep = '.', collapse = ' / ')
[1] "A.1 / A.2 / A.3 / A.4 / A.5"

Bedingungen

Bedingungen | 3 Typen in R

In der Abfrage können neben Operatoren auch Funktionen verwendet werden, solange diese TRUE oder FALSE zurückgeben.

if.. Anweisung

if (Abfrage) {
   mache folgendes
}

x <- 10L
if ( is.integer(x) ) {
  print(
    "Yes, x is an integer!")
}
[1] "Yes, x is an integer!"
if ( is.double(x) ) {
  x*2
}

if..else.. Anweisung

if (Abfrage) {
   mache dies
} else {
   mache das
}

y <- 12.5
if (y < 6) {
  y <- 0
} else {
  y <- 1
}
y
[1] 1

ifelse() Funktion

ifelse (Abfrage, mache dies, mache das)

z <- 1:3
ifelse(z == 2, z^2, 0)
[1] 0 4 0

→ vektorisiert: prüft jedes Element einzeln

Wenn bei der Abfrage TRUE rauskommt, wird die (erste) Anweisung ausgeführt.

Bedingungen | Typ 3: ifelse()

Anwendungsbeispiel: Farbeinstellung bei Basisplots
plot(x = iris$Sepal.Length, y = iris$Petal.Length, 
  col = ifelse(iris$Species == "setosa", "blue", "red"),
  pch = 20,  cex = 1, xlab = "Länge Kelchblatt", ylab = "Länge Bütenblatt", 
  main = "setosa vs. versicolor/virginica")

→ ifelse() erzeugt hier nicht eine Farbe, sondern einen Farbvektor mit einem Eintrag pro Datenpunkt: wenn ‘setosa’, dann “blue”, ansonsten “red”.

Mehrere Bedingungen: case_when()

  • Mehrere Bedingungen lassen sich mit if.. → else if.. → else.. verschachteln.
  • Für Spalten in Datensätzen gibt es in dplyr die vektorisierte Variante case_when():
    • Jeder Fall wird nacheinander ausgewertet, und die erste Übereinstimmung bestimmt den Wert. - Trifft kein Fall zu, wird der Wert von .default verwendet.
starwars |>
  select(name:mass, gender, species) |>
  mutate(
    type = case_when(
      height > 200 | mass > 200 ~ "large", # if..
      species == "Droid" ~ "robot",  # else if..
      .default = "other"  # else ..
    )
  )
# A tibble: 87 × 6
   name               height  mass gender    species type 
   <chr>               <int> <dbl> <chr>     <chr>   <chr>
 1 Luke Skywalker        172    77 masculine Human   other
 2 C-3PO                 167    75 masculine Droid   robot
 3 R2-D2                  96    32 masculine Droid   robot
 4 Darth Vader           202   136 masculine Human   large
 5 Leia Organa           150    49 feminine  Human   other
 6 Owen Lars             178   120 masculine Human   other
 7 Beru Whitesun Lars    165    75 feminine  Human   other
 8 R5-D4                  97    32 masculine Droid   robot
 9 Biggs Darklighter     183    84 masculine Human   other
10 Obi-Wan Kenobi        182    77 masculine Human   other
# ℹ 77 more rows

Auf älteren dplyr-Versionen (< 1.1.0) gibt es .default noch nicht; dort wird stattdessen TRUE ~ "other" als letzte Zeile verwendet.

Your turn …

Quiz 6-7 | Bedingungen

Q6

Der Körper einer Bedingung (wie auch Schleife und Funktion) wird immer in geschweifte Klammern gesetzt, damit er über mehrere Zeilen gehen kann.

In der Abfrage muss ein Operator stehen, welcher die Abfrage wahr macht, denn der Codeblock in der Bedingung wird ja ausgeführt. Richtig ist daher:

x <- 25
if (x <= 25) {
  print('Yep, this is correct')
}
[1] "Yep, this is correct"

Q7

Anhand der zweimaligen geschweiften Klammern wird deutlich, dass es sich um eine ‘if..else..’ Bedingung handelt.

An der Ausgabe 0 ist zu erkennen, dass der Alternativbefehl (in ‘else’) ausgeführt wurde. In die zweite Lücke muss daher ein Operator, bei dem die Abfrage FALSE ergibt:

i <- 1
if (i != 1) {
  i + 1
} else {
  i - 1
}
[1] 0

Schleifen & Simulationen

Schleifen

Eine Schleife enthält Code (den Schleifenkörper), der wiederholt ausgeführt wird. So muss der Code nicht zehnmal geschrieben werden, wenn er zehnmal ausgeführt werden soll. Schleifen können beliebig verschachtelt werden.

Schleifen

  • Zählschleifen (for): führen eine bestimmte Anzahl an Wiederholungen aus, die durch einen Zähler kontrolliert wird.
  • Kopf- (while) bzw. fußgesteuerte Schleifen (repeat): laufen, solange eine logische Bedingung erfüllt ist. Achtung: Ändert sich die Bedingung nie, entsteht eine Endlosschleife!

for Schleife | Struktur 1


for Schleife | Struktur 2


Ausführung in R
for (i in c(2,4,7) ) {
  print(i)
}
[1] 2
[1] 4
[1] 7

for Schleife | Stile

for Schleife | Bestes Vorgehen

1. Platz für Ergebnisse reservieren  2. Zähler mit seq_along()  3. Per Index speichern
x <- c(2, 4, 7)
out <- numeric(length(x)) # 1.
for (i in seq_along(x)) { # 2.
  out[i] <- x[i]^2        # 3.
}
out
[1]  4 16 49
Warum seq_along() statt 1:length() bzw. 1:3?
y <- numeric(0) # leerer Vektor
1:length(y)     # 2 Iterationen!
[1] 1 0
seq_along(y)    # keine Iteration
integer(0)
  • Platz reservieren: Lässt man das Ergebnisobjekt in jeder Iteration mit c() wachsen, wird die Schleife bei vielen Wiederholungen sehr langsam.
  • seq_along(): Ist der Vektor leer, führt 1:length() trotzdem Iterationen aus (mit Fehlern oder NAs), seq_along() dagegen keine.

Anwendungsbeispiel

Einlesen vieler Dateien

Speichern der Dateinamen im Arbeitsverzeichnis
# Wir gehen davon aus, dass nur die zu importierenden Dateien im 
# Arbeitsverzeichnis sind
files <- dir()
files
 [1] "file_001.csv" "file_002.csv" "file_003.csv" "file_004.csv" "file_005.csv"
 [6] "file_006.csv" "file_007.csv" "file_008.csv" "file_009.csv" "file_010.csv"
Import aller 10 Dateien und Zusammenführung zu EINEM Dataframe
# Leere Liste für die importierten Datensätze
data_list <- vector("list", length = length(files)) 

# Schleife, bei der in jeder Iteration eine Datei importiert wird
for (i in seq_along(files)) {
  data_list[[i]] <- read.csv(files[i]) 
}

# Zusammenfügen der einzelnen Dataframes in der Liste
data_df <- dplyr::bind_rows(data_list)
str(data_df)
'data.frame':   200 obs. of  3 variables:
 $ station: chr  "A" "A" "A" "A" ...
 $ x      : int  40 24 23 12 46 48 34 35 36 41 ...
 $ y      : num  28.93 14.49 1.67 9.51 20.64 ...
data_df |> 
  group_by(station) |> 
  count()
# A tibble: 10 × 2
# Groups:   station [10]
   station     n
   <chr>   <int>
 1 A          20
 2 B          20
 3 C          20
 4 D          20
 5 E          20
 6 F          20
 7 G          20
 8 H          20
 9 I          20
10 J          20
Visualisierung der einzelnen Datensätze
ggplot(data_df, aes(x, y)) +
  geom_point() + 
  geom_smooth() +
  facet_wrap(~station, nrow = 2)

Your turn …

Quiz 8-9 | Schleifen

Q8


Q9

Hier ist keine Abfrage zu sehen, sondern es wird jeweils ein Zähler definiert. Also muss es sich um die Zählschleife (‘for..’) handeln, und zwar um 2 ineinander verschachtelte. Wichtig ist hierbei, dass der Zähler unterschiedlich ist: Die erste Schleife hat meist den Index i, die nächste j, usw.

Im innersten Schleifenkörper werden die beiden Indizes mit paste() und dem Trennzeichen ‘/’ zusammengefügt. Damit dies in der Konsole sichtbar ist, braucht es die Funktion print().

for (i in c(1,3)) {
  for (j in c(5,7)) {
    print(str_c(i, j, sep = '/'))
  }
}
[1] "1/5"
[1] "1/7"
[1] "3/5"
[1] "3/7"

Eigene Funktionen

Komponenten von Funktionen

Eine Funktion ist eine Sammlung von Code, die jederzeit über ihren Namen aufgerufen werden kann. Funktionen strukturieren die Arbeit und unterteilen komplexe Probleme in kleine, einfache Einheiten.


Argumente und Standardwerte

Funktion mit Argument
sag_hi <- function(name) {
  # Funktionskörper
  print(paste0("Hi ", name, "!"))
}
sag_hi("Anne")
[1] "Hi Anne!"
sag_hi()
Error in sag_hi() : argument "name" is missing, with no default
Funktion mit Argument und Standardwert
sag_moin <- function(name = "Welt") {
  # Funktionskörper
  print(paste0("Moin ", name, "!"))
}
sag_moin("Jan")
[1] "Moin Jan!"
sag_moin()
[1] "Moin Welt!"

→ Ist ein Standardwert definiert, gibt es keine Fehlermeldung, wenn das Argument fehlt.

Funktion zur Berechnung der Schiefe | 1

\[g_1 = \frac{\frac{1}{n}\sum\limits_{i=1}^{n}\left(x_i - \bar{x}\right)^3} {\left(\frac{1}{n}\sum\limits_{i=1}^{n}\left(x_i - \bar{x}\right)^2\right)^{3/2}}\]

Von der Gleichung zum R Code
x <- iris$Sepal.Length

num <- sum((x-mean(x))^3)/length(x)
den <- (sum((x-mean(x))^2)/length(x))^(3/2)
g_1 <- num/den
g_1
[1] 0.3117531
Vom R Code zur Funktion
skewness <- function(x) {
  # Funktionskörper
  num <- sum((x-mean(x))^3)/length(x)
  den <- (sum((x-mean(x))^2)/length(x))^(3/2)
  g_1 <- num/den
  return(g_1)
}
skewness(iris$Sepal.Length)
[1] 0.3117531

Eine Funktion gibt immer nur ein Objekt zurück. Sollen mehrere Ergebnisse ausgegeben werden, fassen Sie diese mit list() zu einem Listenobjekt zusammen, z.B. return(list(mean = m, sd = s)).

Funktion zur Berechnung der Schiefe | 2

Was machen wir, wenn der Vektor NAs enthält?

Wir führen ein weiteres Argument ein und bauen eine Bedingung in die Funktion!
skewness <- function(x, na.rm = TRUE) {
  if (na.rm == TRUE) { # Bedingung einbauen
    x <- x[!is.na(x)] 
  }
  num <- sum( (x - mean(x))^3) / length(x)
  den <- (sum( (x - mean(x))^2) / length(x))^(3/2)
  g_1 <- num/den
  return(g_1)
}
skewness(c(NA,3,7,0,1,NA,12,8,5))
[1] 0.3237934
skewness(c(NA,3,7,0,1,NA,12,8,5), na.rm = FALSE)
[1] NA

Your turn …

Quiz 10 | Funktionen

Eine Funktion wird mit function() definiert. Sie sollte nicht auf Objekte zugreifen, die nicht innerhalb der Funktion definiert oder als Argument übergeben werden. Hier wird auf x und y zugegriffen, daher müssen diese als Argumente definiert werden (function(x, y)). Das berechnete Produkt z wird explizit mit return(z) ausgegeben.

calc_product <- function(x, y) {
  z <- x*y
  return(z)
}
calc_product(10, 4)
[1] 40

Funktionale Programmierung

Von der Schleife zur funktionalen Programmierung

  • R ist eine funktionale Programmiersprache: for Schleifen lassen sich in eine Funktion packen, der man dann eine andere Funktion als Argument übergibt.
  • Das reduziert Code, ‘copy & paste’-Fehler und macht den Code leichter verallgemeinerbar.

apply-Familie und das Paket purrr

Die ‘*apply’ Familie in base R (apply(), lapply(), sapply(), …) und die ‘map’ Familie im tidyverse-Paket purrr wenden eine Funktion auf jedes Element einer Liste, jede Spalte eines Datensatzes usw. an – ohne eine Schleife schreiben zu müssen:


base R

base R | ‘apply’ Familie 1

apply()
# Mittelwert pro Spalte (MARGIN = 2)
apply(X = df, MARGIN = 2, FUN = mean)
           x            y            z 
 0.190523876 -0.006471519  0.138796773 
# Summe pro Zeile (MARGIN = 1)
apply(df, 1, sum)[1:5]
[1]  0.12799996  0.71241794 -0.06410025  0.16259230  0.26057783
lapply()
# Output als Liste
lapply(df, range)
$x
[1] -2.214700  1.595281

$y
[1] -1.989352  1.358680

$z
[1] -1.129363  1.980400

base R | ‘apply’ Familie 2

Beispiel für eine nützliche Anwendung von lapply()

Anzeige der einzigartigen Werte jeder Spalte in einem (heterogenen) Datensatz
lapply(iris, unique)
$Sepal.Length
 [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.4 4.8 4.3 5.8 5.7 5.2 5.5 4.5 5.3 7.0 6.4 6.9 6.5
[20] 6.3 6.6 5.9 6.0 6.1 5.6 6.7 6.2 6.8 7.1 7.6 7.3 7.2 7.7 7.4 7.9

$Sepal.Width
 [1] 3.5 3.0 3.2 3.1 3.6 3.9 3.4 2.9 3.7 4.0 4.4 3.8 3.3 4.1 4.2 2.3 2.8 2.4 2.7
[20] 2.0 2.2 2.5 2.6

$Petal.Length
 [1] 1.4 1.3 1.5 1.7 1.6 1.1 1.2 1.0 1.9 4.7 4.5 4.9 4.0 4.6 3.3 3.9 3.5 4.2 3.6
[20] 4.4 4.1 4.8 4.3 5.0 3.8 3.7 5.1 3.0 6.0 5.9 5.6 5.8 6.6 6.3 6.1 5.3 5.5 6.7
[39] 6.9 5.7 6.4 5.4 5.2

$Petal.Width
 [1] 0.2 0.4 0.3 0.1 0.5 0.6 1.4 1.5 1.3 1.6 1.0 1.1 1.8 1.2 1.7 2.5 1.9 2.1 2.2
[20] 2.0 2.4 2.3

$Species
[1] setosa     versicolor virginica 
Levels: setosa versicolor virginica

Übungen

Optionale swirl-Lektionen zur Vertiefung

Kurs DS1-05-Handling spezieller Datentypen

  • L01-Faktoren
  • L02-Faktoren_und_das_forcats_Paket
  • L03-Datum_und_Zeit_in_R
  • L04-Datum_Zeit_und_das_lubridate_Paket
  • L05-Grundfunktionen_zur_Textmanipulation
  • L06-Zeichenketten_und_das_stringr_Paket
  • L07-Textmanipulation_und_regulaere_Ausdruecke

Kurs DS1-06-Fortgeschrittene R Programmierung

  • L01-Dateihandling aus R heraus
  • L02-Bedingungen
  • L03-Eigene Funktionen schreiben
  • L04-Schleifen und erste Simulationen
  • L05-FizzBuzz
  • L06-Funktionales Programmieren mit apply Funktionen

Übungsaufgaben Woche 13

Alle Aufgaben der Übungswoche finden Sie im Handbuch:

  • die zu bearbeitenden Übungsskripte
  • die (optionalen) swirl-Lektionen und
  • den aktuellen Stand der Fallstudie.
  • Die Übungsskripte und Daten liegen in Ihrem R-Projektordner DS1-R-Uebungen (Unterordner uebungsskripte bzw. data).
  • Öffnen Sie immer zuerst die Projektdatei DS1-R-Uebungen.Rproj, dann stimmen alle Dateipfade.

Wie fühlen Sie sich jetzt…?

Total konfus?


Dann empfehle ich Ihnen:

Total gelangweilt?


Dann testen Sie doch Ihr Wissen in folgendem Abschlussquiz…

Abschlussquiz

Bei weiteren Fragen: saskia.otto(at)uni-hamburg.de

Creative Commons License
Diese Arbeit is lizenziert unter einer Creative Commons Attribution-ShareAlike 4.0 International License mit Ausnahme der entliehenen und mit Quellenangabe versehenen Abbildungen.