In dieser Übung wollen wir uns mit kontinuierlichen Daten
beschäftigen. Hierzu betrachten wir den Datensatz
airquality
, welcher verschiedene Messungen bezüglich
Luftqualität zwischen Mai und September im Jahre 1973 umfasst.
Für mehr Information siehe ?airquality
.
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.4.0 ✔ purrr 0.3.5
✔ tibble 3.1.8 ✔ dplyr 1.0.10
✔ tidyr 1.2.1 ✔ stringr 1.4.1
✔ readr 2.1.3 ✔ forcats 0.5.2 ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
<- as_tibble(airquality)
airquality # Faktoren aus Monat und Tag machen:
<- airquality %>%
airquality mutate_at(c("Month", "Day"), factor)
# Alternativ:
# airquality %>%
# mutate(Month = factor(Month), Day = factor(Day))
airquality
airquality
. Welche Aspekte fallen ins Auge und könnten
interessant sein?Anhang des tibbles
sehen wir direkt die Anzahl der
Beobachtungen und Variablen. Alternativ auch oben rechts unter
Environment
oder mit dim()
airquality
dim(airquality)
[1] 153 6
summary(airquality)
Ozone Solar.R Wind Temp Month Day
Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00 5:31 1 : 5
1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00 6:30 2 : 5
Median : 31.50 Median :205.0 Median : 9.700 Median :79.00 7:31 3 : 5
Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88 8:31 4 : 5
3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00 9:30 5 : 5
Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00 6 : 5
NA's :37 NA's :7 (Other):123
Hier gibt es mehrere Möglichkeiten. Naiv könnten wir die Tabelle durchschauen und jeden einzelnen Tag und Monat nachschlagen, ob dieser vorhanden ist. Hier kann aber leicht das Gesamtbild aus den Augen verloren gehen, daher wollen wir das kurz veranschaulichen:
ggplot(airquality) +
geom_bar(aes(x = Day))
ggplot(airquality) +
geom_bar(aes(x = Month))
Wenn wir Balkendiagramme für Tag und Monat separat zeichnen, deutet
sich an, dass diese scheinbar kontinuierlich erfasst wurden.
Seien Sie hierbei vorsichtig, denn es wäre prinzipiell auch möglich,
mehrere Messungen an demselben Tag durchzuführen, um das Ergebnis zu
verändern.
Für eine deutlichere Einsicht können wir uns das gemeinsame Balkendiagramm anzeigen:
ggplot(airquality) +
geom_bar(aes(x = Day, fill = Month))
ggplot(airquality) +
geom_bar(aes(x = Month, fill = Day)) +
facet_wrap(~ Day)
Scheinbar liegen die kleinen Unterschiede lediglich an dem 31. Tag im Monat. Die Daten wurden somit kontinuierlich erfasst.
airquality
. Welche Aspekte fallen ins Auge und könnten
interessant sein?Wir könnten uns hier jede einzelne Variable im Detail anschauen, aber
verbleiben beispielhaft bei der Variablen Ozone
.
Schauen wir uns zunächst die allgemeine Verteilung anhand von Boxplot und Histogramm an:
ggplot(airquality) +
geom_boxplot(aes(y = Ozone), fill = "steelblue")
ggplot(airquality, aes(x = Ozone)) +
geom_histogram(fill = "steelblue", bins = 15) +
geom_freqpoly(bins = 15)
Viele Werte scheinen sich zwischen ~10 und ~30 aufzuhalten. Das erste Quartil wird durch 18 und das dritte Quartial durch 63 markiert.
Aus der Auskunft (?airquality
) sehen wir, dass es sich
hierbei um durchschnittliche Teilchen pro Milliarde zwischen 13 und 15
Uhr, gemessen auf Roosevelt Island, handelt. Scheinbar liegen auch
einige fehlende Werte und Ausreißer vor, welche wir zunächst betrachten
wollen.
Ausreißer können wir erhalten, in dem wir (in diesem Fall) nach hohen
Werte von Ozone
filtern:
%>%
airquality filter(Ozone > 125)
Wir sehen, dass am 1.07.1973 und am 25.08.1973 Ausreißer bei den
Ozonwerten vorlagen. Um die Ausreißer der Boxplots direkt zu erhalten
(ohne selbstständiges Filtern), können wir aus rstatix
die
Funktion identify_outliers()
verwenden:
library(rstatix)
Attache Paket: ‘rstatix’
Das folgende Objekt ist maskiert ‘package:stats’:
filter
%>%
airquality identify_outliers(Ozone)
Wir sehen, dass dieselben zwei Werte in der Spalte
is.outlier
als Ausreißer (TRUE
) markiert
wurden, jedoch nicht als extreme Ausreißer (FALSE
).
Ausreißer weichen um einen IQR von mehr als 1.5 von dem 3. Quartil ab,
extreme Ausreißer ab dem dreifachen IQR.
Um die fehlenden Einträge zu betrachten, können wir nach
is.na()
filtern:
%>%
airquality filter(is.na(Ozone))
Scheinbar wurden an 2/3 der Tage des Junis keine Daten bezüglich der Ozonwerte erhoben. Wohlmöglich gab es Störungen der Messgeräte?
Nun stellt sich die Frage, wieso so viele Messwerte fehlen. Das lässt sich ohne weitere Information des Datenerhebers nicht einfach klären, allerdings können wir nach Mustern suchen:
%>%
airquality group_by(Month) %>%
summarise(n = sum(is.na(Ozone)))
Schauen wir, ob es an zusammenhängenden Zeitpunkten Probleme bei der Datenerhebung gab. Hierzu fügen wir zunächst Monat und Tag zu einem Objekt für (Kalender-)Daten zusammen:
<- airquality %>%
airquality mutate(date = as.Date(paste("1973", Month, Day, sep = "-")))
airquality
ggplot(airquality) +
geom_line(aes(x = date, y = Ozone), color = "tomato1")
In den meisten Fällen scheinen die fehlenden Einträge nicht mehrere aufeinanderfolgende Tage zu beschreiben. Im Juni scheinen gänzlich am Anfang und Ende des Monats die Einträge zu fehlen.
Es könnte auch interessant sein, den Verlauf der Ozonwerte an den verschiedenen Monatstagen zu vergleichen oder zwischen den Monat (oder beides zugleich):
geom_histogram(aes(x = Ozone, fill = Day)) +
facet_wrap(~ Day)
ggplot(airquality) +
geom_boxplot(aes(Day, Ozone, fill = Day))
ggplot(airquality) +
geom_histogram(aes(x = Ozone, fill = Day)) +
facet_wrap(~ Day)
ggplot(airquality) +
geom_boxplot(aes(Month, Ozone, fill = Month))
ggplot(airquality, aes(x = Ozone, fill = Month)) +
geom_histogram(aes(y = after_stat(density))) +
geom_density(alpha = 0.2) +
facet_wrap(~ Month) +
theme(legend.position = NULL)
Bei der Betrachtung der Monatstage sehen wir tendenziell am Anfang und Ende des Monats mehr Variabilität (wohlmöglich wegen der fehlenden Werte im Juni). In der Mitte des Monats scheinen die Werte weniger auseinanderzugehen, auch wenn es Ausreißer gibt.
Bei der Betrachtung der Monate sehen wir, dass es durchschnittlich vermutlich mehr Ozon im Juli und August gab, wobei hier auch die Varianz höher war. Besonders im September gab es viele Ausreißer. In dem Histogramm vom Juni sehen wir insbesondere die fehlenden Werte. Auch hier werden die Muster der Ozonwerte etwas deutlicher, dass im Mai und September (und ggf. Juni) tendenziell (gehäuft) geringere Werte vorliegen.
Der Vorteil an rstatix
liegt auch darin, dass sich
verschiedene Funktionen auf Gruppen berechnen lassen. So können wir auch
separat für jeden Tag/Monat die Ausreißer von Ozone
bestimmen:
Wir hatten auch bereits gelernt, wie wir für Gruppen/Kategorien
statistische Zusammenfassungen machen können, um die Sachverhalte auch
durch Zahlenwerte betrachten zu können. Hierbei muss auf die
NAs
geachtet werden:
Meist ist es hilfreich, bei interessanten Aspekten zusätzlich zu einer Grafik auch markante statistische Maßzahlen zu benennen. Beispielsweise sind die Standardabweichungen und Mediane/Mittelwerte von Juli und August hoch (hohe Varianz). Somit kann auch die entsprechende Maßzahl angegeben werden.
Betrachten wir letztlich noch, ob die Variable Ozone
normalverteilt ist:
%>%
airquality shapiro_test(Ozone)
%>%
airquality group_by(Month) %>%
shapiro_test(Ozone)
shapiro_test()
eingeben würden, könnten wir direkt alle
gemeinsam auf Normalverteilung testen und müssten dies nicht einzeln
machen.Entsprechende Überlegungen könnten auch für die anderen Variablen in Betracht gezogen werden.
Eine Herangehensweise ist, für jedes Paar an Variablen ein neues Streudiagramm zu zeichnen:
ggplot(airquality, aes(x = Ozone, y = Temp)) +
geom_point(aes(color = Month)) +
geom_smooth(color = "red")
Eine Standardfunktion in R, die einen Überblick über einen gesamten
Dataframe liefert, ist die plot()
Funktion:
plot(airquality)
pairs()
oder
ggpairs()
aus dem Paket GGally
:pairs(airquality, panel = panel.smooth) # panel = panel.smooth zeichnet die Kurven ein
library(GGally)
%>%
airquality select(-c(Day, date)) %>%
ggpairs(progress = FALSE)
NAs
entfernt
wurden.Fokussieren wir uns nur auf die kontinuierlichen Daten und erweitern die Grafik etwas:
# In columns lassen sich ebenfalls die Variablen auswählen:
%>%
airquality ggpairs(columns = 1:4, aes(color = Month, alpha = 0.01), progress = FALSE)
ggpairs()
können wir ebenfalls alle
Grafiken gruppieren (hier nach dem Monat)Wir hatten zuvor die Korrelationskoeffizienten schon gesehen, wollen das an dieser Stelle noch einmal gesamteinheitlich machen:
%>%
airquality select(-c(Day, Month, date)) %>% # Faktoren/Gruppen nicht beachten
cor_mat()
Temp
und Ozone
sind stark positiv
korreliertWind
und Ozone
sowie Temp
und
Wind
sind moderat negativ korreliert%>%
airquality select(-c(Day, Month, date)) %>% # Faktoren/Gruppen nicht beachten
cor_mat() %>%
cor_reorder() %>%
cor_plot(type = "upper", label = TRUE,
palette = ggpubr::get_palette(c("darkred", "cornsilk", "darkcyan"), 200)
)
Die Zusammenhänge wurden oben bereits erläutert. Zusätzlich haben
Wind
und Solar.R
keinen Zusammenhang und die
restlichen Paare nur schwache Zusammenhänge.
Inbesondere Wind
und Ozone
sowie
Temp
und Ozone
könnten interessante
Zusammenhänge sein:
Wind kann das das Ozongas wegtransportieren, sodass die Anzahl der Ozonmoleküle in der Luft sinkt
Sonneneinstrahlung und hohe Temperaturen begünstigen die Reaktion von Stickstoff und Sauerstoff zu Ozon
ggplot(airquality, aes(Wind, Ozone)) +
geom_point() +
geom_smooth()
Erstellen wir das zugehörige Modell:
# Linearer Zusammenhang:
<- lm(Ozone ~ Wind, data = airquality)
lin_modell summary(lin_modell)
# Quadratischer Zusammenhang:
<- lm(Ozone ~ Wind * I(Wind ** 2), data = airquality)
quad_modell summary(quad_modell)
Dasselbe können wir nun auch für Temp
und
Ozone
machen:
ggplot(airquality, aes(Temp, Ozone)) +
geom_point() +
geom_smooth()
Erstellen wir wieder Regressionsmodelle:
# Linearer Zusammenhang:
<- lm(Ozone ~ Temp, data = airquality)
lin_modell summary(lin_modell)
# Quadratischer Zusammenhang:
<- lm(Ozone ~ Temp * I(Temp ** 2), data = airquality)
quad_modell summary(quad_modell)
predict(quad_modell, list(Temp = 80))
Vergleichen wir diesen Wert (42) mit den tatsächlich gemessenen Werten:
# Werte nahe 80 Fahrenheit:
<- airquality %>%
nahe_80f filter(between(Temp, 75, 85) & !is.na(Ozone)) %>%
select(Ozone, Temp)
%>%
nahe_80f summarise(mittelwert = mean(Ozone))
ggplot(nahe_80f, aes(Temp, Ozone)) +
geom_jitter() +
geom_smooth(se = FALSE) +
geom_hline(yintercept = 42, color = "red")
Letztendlich können wir auch ein etwas komplexeres Modell erstellen,
um den Wert von Ozone
zu berechnen:
<- lm(Ozone ~ Wind + I(Wind ** 2) + I(Temp ** 2), data = airquality)
modell summary(modell)
<- lm(Ozone ~ Wind * I(Wind ** 2) * Temp * Solar.R * Month, data = airquality)
modell summary(modell)
Um ein Modell visuell darzustellen, kann das Paket
GGally
mit den Funktionen ggnostic()
und
ggcoef_model()
helfen:
Um sich die Koeffizienten schnell anzuschauen (anstelle der Ansicht
in der R-Konsole), kann ggcoef_model()
verwendet werden.
Hierzu wird zunächst broom.helpers
benötigt
(install.packages("broom.helpers"
):
library(broom.helpers)
<- lm(Ozone ~ Temp + Wind + Solar.R + Month, data = airquality)
modell summary(modell)
ggcoef_model(modell)
Beta
zeigt hierbei den standardisierten
Koeffizienten und dessen Unsicherheit an
Temp
und Wind
haben in dem Modell einen
signifikanten Einfluss (kleiner p-Wert).
Solar.R
ist ebenfalls noch signifikant.
Month
hingegen scheint nur im September einen
signifikanten Einfluss auf Ozone
zu haben und weist hohe
Schwankungen auf.
Um weitere Aspekte des Modells zu betrachten und um seine Güte zu
beurteilen, kann danach ggnostic()
verwendet werden:
ggnostic(modell, aes(color = Month), progress = FALSE)
Die verschiedenen Bereiche auf der y-Achse stellen das Modell auf unterschiedliche Arten dar (Residueen, Diagonale der Hat-Matrix, Schätzung der SD von Residueen, wenn die Beobachtung aus dem Modell genommen wurde, Cooks-Distanz)
Der wichtigste Aspekt liegt darin, dass Ausreißer anhand der Trennlinien erkennbar werden
Auf der x-Achse sind die Variablen des Modells
Filtern wir die zwei Ausreißer von Ozone
, um ggf. das
Modell zu verbessern:
# Entfernen von hohen Ozonwerten
<- airquality %>%
airquality_gefiltert filter(Ozone < 125)
<- lm(Ozone ~ Temp + Wind + Month, data = airquality_gefiltert)
modell summary(modell)
ggnostic(modell, aes(color = Month), progress = FALSE)