library(lubridate)
start_time_dt <- ymd_hms(start_time, tz = "Europe/Zurich")Zum Lösen der nachfolgenden Aufgaben generieren Sie ein neues R Script (oder Quarto Dokument, *.qmd) in RStudio und kopieren Sie die Code-Chunks hinein und vervollständigen Sie diese. Nach Bearbeitung der Aufgabe(n) können Sie ihre Lösungen mit der Musterlösung vergleichen.
Ausgangssituation
Sie arbeiten als Data Analyst/in bei einem E-Scooter-Anbieter in der Schweiz. Ziel ist eine erste EDA, um (i) Nachfrage-Peaks über den Tag zu identifizieren, (ii) typische vs. auffällige Fahrten zu erkennen und (iii) zu prüfen, ob Abbrüche mit der Akkuwarnung zusammenhängen. Auf Basis Ihrer Ergebnisse sollen konkrete betriebliche Maßnahmen (z.B. Rebalancing-/Charging-Zeiten, Umgang mit Ausreißern, Priorisierung von Akku-Checks) abgeleitet werden.
Beschreibung der Daten
In der Woche vom 10.–17. Mai 2025 wurden Fahrten mit E-Scootern eines Anbieters als Testdatensatz in einer Schweizer Stadt (anonymisiert) protokolliert. Der Datensatz enthält 2’000 Fahrten.
Codebook: Variablenbeschreibung
Datensatz: scooter.csv
Beobachtungseinheit: eine Zeile = eine Scooter-Fahrt (trip_id)
| Variable | Typ (wie geliefert) | Bedeutung | Einheit / Wertebereich |
|---|---|---|---|
trip_id |
Zahl | eindeutige Fahrten-ID | 1001… |
start_time |
Text | Startzeitpunkt der Fahrt | ISO-Zeitstempel als String |
scooter_type |
Text | Scooter-Modell | "Model A", "Model B" |
trip_distance_m |
Zahl | gefahrene Distanz | Meter |
trip_duration_s |
Zahl | Fahrtdauer | Sekunden |
user_rating |
Text | Rating durch Nutzer/in | "1"…"5" und "NA" |
battery_low |
logisch | Akkuwarnung bei Start | TRUE/FALSE |
trip_aborted |
logisch | Fahrt abgebrochen? | TRUE/FALSE |
Sie werden in dieser Aufgabe Datums- und Uhrzeitangaben im Rahmen des Data Wranglings anpassen müssen. Die state-of-the-art Library dazu ist lubridate, die eine grosse Hilfe darstellt. Wenn es in der Aufgabe so weit ist, verwenden Sie die Informationen und Hinweise zu lubridate aus der nachfolgenden Box.
Warum lubridate?
Mit lubridate können Sie Zeitstempel (Strings) zuverlässig in Datum/Zeit umwandeln und bequem Bestandteile wie Tag, Wochentag oder Stunde extrahieren.
1) Zeitstempel parsen (String → Datum/Zeit)
Wenn start_time als Text vorliegt, müssen Sie ihn zuerst in ein datetime umwandeln:
2) Bestandteile extrahieren
hour(start_time_dt) # 0–23
minute(start_time_dt) # 0–59
second(start_time_dt) # 0–59
day(start_time_dt) # 1–31
month(start_time_dt) # 1–12 (oder month(..., label=TRUE))
year(start_time_dt)
wday(start_time_dt) # Wochentag als Zahl (1–7)wday(start_time_dt, label = TRUE, abbr = TRUE)3) Datum/Zeit abrunden (für Aggregationen)
Das ist hilfreich, wenn Sie z.B. nach Stunde oder Tag zählen möchten:
floor_date(start_time_dt, unit = "hour") # auf volle Stunde
floor_date(start_time_dt, unit = "day") # auf Mitternacht (Tagesstart)4) Zeitdifferenzen & Dauer
Wenn Sie Zeiten vergleichen oder Differenzen bilden möchten:
difftime(t2, t1, units = "mins")as.duration(trip_duration_s)5) Nützliche Tipps (typisch in mutate())
mutate(
start_time = ymd_hms(start_time, tz = "Europe/Zurich"),
start_hour = hour(start_time),
weekday = wday(start_time, label = TRUE, abbr = TRUE)
)Aufgabe 1: Import & erste Orientierung
Laden Sie die Rohdatendatei scooter.csv aus Moodle herunter: Link
1.1 Import des Datensatzes
Importieren Sie scooter.csv in RStudio. Wie viele verschiedene Scooter-Modelle (scooter_type) wurden verwendet?
library(tidyverse)
scooter <- read_csv("___/scooter.csv", show_col_types = FALSE)
scooter %>%
count(scooter_type)1.2 Variablentypen und Auffälligkeiten in den Daten
Welche Variablen sind numerisch, welche kategorial? Nennen Sie mindestens 2 Variablen pro Typ und begründen Sie kurz. Gibt es Auffälligkeiten?
glimpse(scooter)
summary(scooter)Aufgabe 2: Data Wrangling
2.1 Variablentransformation
Erstellen Sie eine neue Spalte trip_duration_min, die die Fahrtdauer in Minuten. Stören die NAs die Umrechnung (Datentransformation). Begründen Sie kurz?
library(lubridate)
scooter <- scooter %>%
mutate(trip_duration_min = ___ / ___)
summary(scooter)2.2 Entfernung Fahrten < 1 Min.
Häufig kommt es zu Fehlern z.B. bei der Initialisierung des Fahrzeugs und der Vorgang wird abgebrochen. Wir wollen alle Fahrten mit trip_duration_min < 1 Min. entfernen. Warum ist die naheliegende Codelösung: scooter %>% filter(trip_duration_min >= 1) problematisch? Analysieren Sie das summary(scooter), was fällt auf? Wie kann man es korrekt machen?
scooter %>%
filter(___ >= ___) %>%
summary()scooter %>%
mutate(trip_duration_min = ___ / ___) %>%
filter(is.na(trip_duration_min) | trip_duration_min >= 1) %>%
summary()scooter <- scooter %>%
mutate(trip_duration_min = ___ / ___) %>%
filter(is.na(trip_duration_min) | trip_duration_min >= 1)
summary(scooter)2.3 Fehlende Werte
In der Variablen trip_distance_m gibt es Werte, die fehlen. Wie hoch ist ihr Anteil an allen Beobachtungen? Wenn sie unter 10% sind, entfernen Sie sie.
# Anzahl und Anteil fehlender Distanzwerte
summary(scooter$trip_distance_m)
na_dist <- sum(is.na(scooter$trip_distance_m))
n_total <- nrow(scooter)
na_dist
na_dist / n_totalscooter <- scooter %>%
filter(!is.na(___))
summary(scooter$trip_distance_m)
nrow(scooter)2.4 Erzeugung neuer Variablen
Unser Management interessiert sich vor allem dafür, zu welchen Uhrzeiten und an welchen Wochentagen die Nutzer ihren E-Scooter verwenden. Generieren Sie die beiden neuen Variablen start_hour und weekday.
scooter <- scooter %>%
mutate(
start_hour = hour(start_time),
weekday = wday(start_time, label = TRUE, abbr = TRUE)
)
summary(scooter)Aufgabe 3: Datenvisualisierung, Kennwerte (Metriken)
3.1 Wie verteilt sich die durchschnittliche Fahrtdauer auf die Wochentage?
scooter %>%
group_by(___) %>%
summarise(
mean_duration = mean(trip_duration_min, na.rm = TRUE),
n = n()
) %>%
arrange(weekday)scooter %>%
count(start_hour) %>%
ggplot(aes(x = start_hour, y = n)) +
geom_col() +
labs(x = "Startstunde (0–23)", y = "Anzahl Fahrten")3.2 Welche Fahrtdauern und -distanzen sind „typisch“ und welche sind Ausreißer?
Erstellen Sie Boxplots der Fahrtdauer (trip_duration_min)/ Dauer (trip_distance_m) nach Tageszeit-Klasse und bestimmen Sie die Kennwerte (IQR, Mean, Median).
Diskutieren Sie kurz: Was bedeutet das für die Akkulaufzeit und die erwartete Anzahl Fahrten pro Scooter (bei gleicher Verfügbarkeit)?
library(lubridate)
scooter <- scooter %>%
mutate(
start_hour = hour(start_time),
time_block = case_when(
start_hour %in% 0:5 ~ "Nacht (0–5)",
start_hour %in% 6:9 ~ "Morgen (6–9)",
start_hour %in% 10:15 ~ "Tag (10–15)",
start_hour %in% 16:19 ~ "Abend (16–19)",
TRUE ~ "Spät (20–23)"
),
time_block = factor(time_block,
levels = c("Nacht (0–5)","Morgen (6–9)","Tag (10–15)","Abend (16–19)","Spät (20–23)"))
)
ggplot(scooter, aes(x = time_block, y = trip_duration_min)) +
geom_boxplot() +
labs(x = "Tageszeit-Klasse", y = "Fahrtdauer (Minuten)")scooter %>%
group_by(___) %>%
summarise(
n = n(),
mean_duration = mean(trip_duration_min, na.rm = TRUE),
median_duration = median(trip_duration_min, na.rm = TRUE),
iqr_duration = IQR(trip_duration_min, na.rm = TRUE)
) %>%
arrange(time_block)scooter <- scooter %>%
mutate(
start_hour = hour(start_time),
time_block = case_when(
start_hour %in% 0:5 ~ "Nacht (0–5)",
start_hour %in% 6:9 ~ "Morgen (6–9)",
start_hour %in% 10:15 ~ "Tag (10–15)",
start_hour %in% 16:19 ~ "Abend (16–19)",
TRUE ~ "Spät (20–23)"
),
time_block = factor(time_block,
levels = c("Nacht (0–5)","Morgen (6–9)","Tag (10–15)","Abend (16–19)","Spät (20–23)"))
)
ggplot(scooter, aes(x = time_block, y = trip_distance_m)) +
geom_boxplot() +
labs(x = "Tageszeit-Klasse", y = "Distanz (Meter)")scooter %>%
group_by(___) %>%
summarise(
n = n(),
mean_distance = mean(trip_distance_m, na.rm = TRUE),
median_distance = median(trip_distance_m, na.rm = TRUE),
q1_distance = quantile(trip_distance_m, probs = 0.25, na.rm = TRUE),
q3_distance = quantile(trip_distance_m, probs = 0.75, na.rm = TRUE),
iqr_distance = IQR(trip_distance_m, na.rm = TRUE)
)3.3 Abbruchquote wegen battery_low
Einer Kollegin ist noch etwas aufgefallen. Wie viele Fahrten “verlieren” wir, weil der Batteriestand zu niedrig ist, d.h. ein Kunde oder eine Kundin befürchtet aufgrund des Batteriestandes nicht bis ans Ziel zu gelangen?
Analysieren Sie hierfür, ob die Abbruchrate bei niedrigem Batteriestand höher ist und visualisieren Sie den Zusammenhang mit einem Balkendiagramm.
scooter %>%
group_by(battery_low) %>%
summarise(
n = n(),
abort_rate = mean(trip_aborted, na.rm = TRUE)
)scooter %>%
group_by(battery_low) %>%
summarise(abort_rate = mean(trip_aborted, na.rm = TRUE)) %>%
ggplot(aes(x = battery_low, y = abort_rate)) +
geom_col() +
labs(x = "Akkustand-Warnung (battery_low)", y = "Abbruchrate")3.4 Verlorene Fahrten
Ihr Business-Analyst sagt, die Abbruchraten sagen zwar etwas über das Risiko aus, dass ein Kunde oder eine Kundin die Fahrt erst gar nicht antritt, aber wo verlieren wir das meiste Geld während des Tages? Plotten Sie, um dies zu beantworten, ein Balkendiagramm der Anzahl “verlorener” Fahrten pro Tageszeitklasse.
scooter %>%
group_by(___) %>%
summarise(lost_trips = sum(trip_aborted, na.rm = TRUE)) %>%
ggplot(aes(x = time_block, y = lost_trips)) +
geom_col() +
labs(x = "Tageszeit-Klasse", y = "Verlorene Fahrten (Abbrüche)")Aufgabe 4: Nächste mögliche Schritte
Sie haben schon einige Einblicke in die Eigenschaften der Daten erhalten. Im zyklischen Ablauf der EDA wäre nun eine neue Frage/Hypothese an die Daten zu formulieren. Welchen interessanten Fragen könnte man als nächstes nachgehen?