library( tidyverse )
library( haven )Musterlösung zur Hausaufgabe über die Winterferien
Aufgabenstellung hier
Die angegebene Bepunktung ist vorläufig!
Aufgabe 1
Die benötigte Dateien DPQ_J.xpt findet sich auf der NHANES-Webseite, hier.
Wir laden zunächst Tidyverse, sowie das haven-Paket, dass zum Laden der Dateien im XPT-Format benötigt wird.
Nun laden wir die Tabelle:
read_xpt( "data/DPQ_J.xpt" ) -> dpq
head(dpq)# A tibble: 6 × 11
SEQN DPQ010 DPQ020 DPQ030 DPQ040 DPQ050 DPQ060 DPQ070 DPQ080 DPQ090 DPQ100
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 93705 0 0 0 0 0 0 0 0 0 NA
2 93706 0 0 0 0 0 0 0 0 0 NA
3 93708 0 0 0 0 0 0 0 0 0 NA
4 93709 NA NA NA NA NA NA NA NA NA NA
5 93711 1 0 1 0 0 0 0 0 0 0
6 93712 0 0 1 0 0 0 0 0 0 0
Die Tabelle hat folgende Spalten:
colnames(dpq) [1] "SEQN" "DPQ010" "DPQ020" "DPQ030" "DPQ040" "DPQ050" "DPQ060" "DPQ070"
[9] "DPQ080" "DPQ090" "DPQ100"
Zunächst wandeln wir die Tabelle in ein langes Format um, dann entfernen wir die nicht benötigte Frage 10, und anschließen ersetzen wir alle Antworten mit Codes > 3 durch NA.
dpq %>%
pivot_longer( -SEQN, names_to="question", values_to="answer" ) %>%
filter( question != "DPQ100" ) %>%
mutate( answer = ifelse( answer>3, NA, answer ) ) -> dpq_long
head( dpq_long, 15 )# A tibble: 15 × 3
SEQN question answer
<dbl> <chr> <dbl>
1 93705 DPQ010 0
2 93705 DPQ020 0
3 93705 DPQ030 0
4 93705 DPQ040 0
5 93705 DPQ050 0
6 93705 DPQ060 0
7 93705 DPQ070 0
8 93705 DPQ080 0
9 93705 DPQ090 0
10 93706 DPQ010 0
11 93706 DPQ020 0
12 93706 DPQ030 0
13 93706 DPQ040 0
14 93706 DPQ050 0
15 93706 DPQ060 0
Nun können wir die die PUnktzahlen für jeden Probanden aufaddieren
dpq_long %>%
group_by( SEQN ) %>%
summarise( dpq_score = sum(answer) ) %>%
rename( subjectId = SEQN ) -> dpq_scores
head( dpq_scores )# A tibble: 6 × 2
subjectId dpq_score
<dbl> <dbl>
1 93705 0
2 93706 0
3 93708 0
4 93709 NA
5 93711 2
6 93712 1
Bei der Gelegenheit haben wir auch die Spalte SEQN in SubjectID umbenannt
Bepunktung
- Laden der Tabelle: 1 Punkt
pivot_longer: 1 Punkte- Frage 10 korrekt entfernt: 1 Punkt
group_byundsummarisekorrekt: 1 Punkt- Code erklärt (zumindest wie die NAs behandelt wurden): 1 Punkt
Häufige Fehler
- Die Antwort für DPQ100 durch NA zu ersetzen ist nicht korrekt, da die NAs dann das
sum“infiziert”, es sei denn, man entfern alle Zeilen mit NA. - Die Zeilen mit NA zu entfernen ist aber auch nicht korrekt, dannd ann werden nicht beantwortete Fragen als 0 gezählt, statt (wie es sein sollte) zu eine Punktsumme von NA zu führen
Aufgabe 2
Wir laden die Datei DEMO_J.XPT, die man hier auf den NHANES-Webseiten findet. Dabei wählen wir gleich die Spalten aus, die wir benötigen:
read_xpt( "data/DEMO_J.XPT" ) %>%
select( subjectId = SEQN, gender_code = RIAGENDR, age = RIDAGEYR, marital_status_code = DMDMARTL ) -> demo
head(demo)# A tibble: 6 × 4
subjectId gender_code age marital_status_code
<dbl> <dbl> <dbl> <dbl>
1 93703 2 2 NA
2 93704 1 2 NA
3 93705 2 66 3
4 93706 1 18 NA
5 93707 1 13 NA
6 93708 2 66 1
Nun erstellen wir eine Tabelle mit den Gender-Codes, wobei wir die Beduutung der Codes dem Codebuch hier entnehmen.
tibble(
gender_code = c( 1, 2 ),
gender = c( "male", "female" )
) -> gender_codes
gender_codes# A tibble: 2 × 2
gender_code gender
<dbl> <chr>
1 1 male
2 2 female
Die Codes für marital status finden wir hier. Wir übertragen die Codes:
tibble(
marital_status_code = 1:6,
marital_status = c( "married", "widowed", "divorced", "separated",
"never married", "living with partner" ) ) -> marital_status_codes
marital_status_codes # A tibble: 6 × 2
marital_status_code marital_status
<int> <chr>
1 1 married
2 2 widowed
3 3 divorced
4 4 separated
5 5 never married
6 6 living with partner
Die Codes für “refused” (77) und “don’t know” (99) brauchen wir nicht, da sie ohnehin als NA gewertet werden sollen.
Wir fügen die beiden Code-Tabellen, sowie die DPQ-Punktsummen, der demo-Tabelle hinzu. Danach entfernen wir die nun nicht mehr benötigten Spalten mit den Codes:
demo %>%
left_join( gender_codes, by="gender_code", relationship="many-to-one" ) %>%
left_join( marital_status_codes, by="marital_status_code", relationship="many-to-one" ) %>%
left_join( dpq_scores, by="subjectId", relationship="one-to-one" ) %>%
select( -gender_code, -marital_status_code ) %>%
filter( age >= 18 ) -> full_tbl
head( full_tbl )# A tibble: 6 × 5
subjectId age gender marital_status dpq_score
<dbl> <dbl> <chr> <chr> <dbl>
1 93705 66 female divorced 0
2 93706 18 male <NA> 0
3 93708 66 female married 0
4 93709 75 female widowed NA
5 93711 56 male married 2
6 93712 18 male <NA> 1
Als letzten Schritt haben wir hier auch noch die nicht erwachsenen Probanden entfernt, wie in der Aufgabe gefordert. (Wenn dieser Schritt, erhält man dennoch dasselbe Ergebnis, da die DPQ-Scores für alle Minderjährigen NA sind, weil der DPQ-Frageboden bei diesen nicht erhoben wurde.)
Bepunktung
Codes für Geschlecht und marital status korrekt aus Codebuch übertragen: 1 Punkt
Codes für Geschlecht und marital status durch Begriffe ersetzt (oder ergänzt): 1 Punkt
DPQ-Punktsummen angefügt: 1 Punkt
sichergestellt, dass nur Probanden ab 18 in der Tabelle verbleiben: 1 Punkt
Code zumindest kurz erklärt: ½ Punkt
Kein Punktabzug, wenn
relationshipinleft_joinfehlt (nicht erforderlich)Kein Punktabzug, wenn unter-18-jährige nicht enfernt wurden, aber dafür bemerkt wurde, dass diese alle NA sind
Aufgabe 3
Wir berechnen die Mittelwerte der DPQ-Summen für Männer und Frauen:
full_tbl %>%
group_by( gender ) %>%
summarise( mean( dpq_score, na.rm=TRUE ) )# A tibble: 2 × 2
gender `mean(dpq_score, na.rm = TRUE)`
<chr> <dbl>
1 female 3.69
2 male 2.77
Um Männer und Frauen zu vergleichen, führen wir einen t-Test durch:
t.test( dpq_score ~ gender, full_tbl )
Welch Two Sample t-test
data: dpq_score by gender
t = 7.8251, df = 5048.6, p-value = 6.133e-15
alternative hypothesis: true difference in means between group female and group male is not equal to 0
95 percent confidence interval:
0.6939073 1.1578215
sample estimates:
mean in group female mean in group male
3.693822 2.767958
Der t-Test ist hoch signifikant (\(p<10^{-14}\)), der Unterschied ist also sicher echt.
Anmerkung: Da der t-Test ind er letzten Zeile auch die Mittelwerte ausgibt, ist die explizite Berechnung der Mittelwerte im Chunk davor eigentlich nicht erforderlich.
Um die Standardfehler der Mittelwerte pro Geschlecht zu erhalten, benutzen wir ebenfalls t.test, aber rufen die t-Test-Funktion un mit den Werten für jedes Geschlecht getrennt auf. Der Übersicht halber behalten wir nur die benötigten Spalten des Ergebnisses.
full_tbl %>%
group_by( gender ) %>%
summarise( broom::tidy( t.test( dpq_score ) ) ) %>%
select( gender, mean=estimate, conf.low, conf.high ) -> dpq_means
dpq_means# A tibble: 2 × 4
gender mean conf.low conf.high
<chr> <dbl> <dbl> <dbl>
1 female 3.69 3.52 3.86
2 male 2.77 2.61 2.93
Alternativ kann man das 95%-KI auch so berechnen:
full_tbl %>%
filter( !is.na( dpq_score ) ) %>%
group_by( gender ) %>%
summarise(
n = n(),
mean = mean( dpq_score ),
sd = sd( dpq_score ) ) %>%
mutate(
conf.low = mean + qt( .025, n-1 ) * sd / sqrt(n),
conf.high = mean + qt( .975, n-1 ) * sd / sqrt(n) )# A tibble: 2 × 6
gender n mean sd conf.low conf.high
<chr> <int> <dbl> <dbl> <dbl> <dbl>
1 female 2590 3.69 4.43 3.52 3.86
2 male 2478 2.77 3.99 2.61 2.93
Da die Anzahl der Probanden sehr groß ist, ist es auch in Ordnung, qt( .025, n-1 ) einfach durch -2 und qt( .975, n-1 ) durch 2 zu ersetzen.
Nun die Säulendiagramme. Hier kann man geom_histogram verwenden.
ggplot( full_tbl ) +
geom_histogram( aes( x=dpq_score, y=after_stat(density) ), binwidth=1 ) +
facet_grid( rows=vars(gender) ) +
geom_vline( aes( xintercept=mean ), col="pink", data=dpq_means ) +
geom_vline( aes( xintercept=conf.low ), col="red", data=dpq_means ) +
geom_vline( aes( xintercept=conf.high ), col="red", data=dpq_means ) 
Wir haben hier geom_histogram das Argument binwidth=1 gegeben, so dass jede Säule einer Einheit auf der Punkteskala entspricht. Das after_stat(density) ist nicht wirklich erforderlich, erleichtert aber den Vergleich zwischen den Geschlechtern.
Statt geom_histogram kann man auch geom_bar verwenden:
ggplot( full_tbl ) +
geom_bar( aes( x=dpq_score ) ) +
facet_grid( rows=vars(gender) )
Bepunktung
- Mittelwerte berechnet (ggf implizit in t-Test): 1 Punkt
- t-Test durchgeführt: 1 Punkt
- 95-KIs der Mittelwerte berechnet: 1 Punkt
- Histogramm geplottet, aufgeteilt nach Geschlecht: 1 Punkt
- Histogramm hat eine Säule pro Punktwert: 1 Punkt
- Mittelwert und KIs eingezeichnet: 1 Punkt
- Code kurz erklärt: ½ Punkt
- Ergebnis t-Test kommentiert: 1 Punkt
- Histogramm interpretiert: 1 Punkt
Aufgabe 4
Den Anteil von Probanden mit mindestens mittelschwerer Depression, aufgeschlüsselt nach Geschlecht und marital status, kann man wie folgt berechnen:
full_tbl %>%
filter( !is.na( dpq_score ) ) %>%
group_by( gender, marital_status ) %>%
summarise( fraction_depressed = mean( dpq_score >= 10 ) )# A tibble: 14 × 3
# Groups: gender [2]
gender marital_status fraction_depressed
<chr> <chr> <dbl>
1 female divorced 0.161
2 female living with partner 0.135
3 female married 0.0717
4 female never married 0.108
5 female separated 0.185
6 female widowed 0.131
7 female <NA> 0.118
8 male divorced 0.127
9 male living with partner 0.0529
10 male married 0.0510
11 male never married 0.101
12 male separated 0.0986
13 male widowed 0.151
14 male <NA> 0.0661
Wenn man aber auch KIs braucht, geht man besser folgendermaßen vor:
full_tbl %>%
filter( !is.na( dpq_score ) ) %>%
group_by( gender, marital_status ) %>%
summarise( broom::tidy( binom.test( sum( dpq_score >= 10 ), n() ) ) ) %>%
select( gender, marital_status, fraction=estimate, conf.low, conf.high, n_depr=statistic, n=parameter ) -> depr_fracs
depr_fracs# A tibble: 14 × 7
# Groups: gender [2]
gender marital_status fraction conf.low conf.high n_depr n
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 female divorced 0.161 0.122 0.206 50 311
2 female living with partner 0.135 0.0926 0.186 30 223
3 female married 0.0717 0.0573 0.0883 81 1130
4 female never married 0.108 0.0805 0.142 46 424
5 female separated 0.185 0.117 0.271 20 108
6 female widowed 0.131 0.0930 0.178 35 267
7 female <NA> 0.118 0.0676 0.187 15 127
8 male divorced 0.127 0.0876 0.175 31 245
9 male living with partner 0.0529 0.0276 0.0905 12 227
10 male married 0.0510 0.0396 0.0645 65 1275
11 male never married 0.101 0.0746 0.133 45 446
12 male separated 0.0986 0.0406 0.193 7 71
13 male widowed 0.151 0.0848 0.240 14 93
14 male <NA> 0.0661 0.0290 0.126 8 121
HIer haben wir binom.test verwendet, aber nicht, um einen Binomial-Test durchzuführen, sondern nur, um ein binomiales KI zu erhalten. Dazu haben wir binom.test die Anzahl der Probanden mit DPQ-Score ab 10 (sum(depr_score>=10)) übergeben, sowie die Gesamtzahld er Probanden (n()) in der jeweiligen Gruppe.
Wir erzeugen damit Säulendiagramme:
depr_fracs %>%
ggplot() +
geom_col( aes( x = marital_status, y = fraction, fill = gender ), position=position_dodge(.9), alpha=.6 ) +
geom_errorbar( aes( x = marital_status, ymin = conf.low, ymax= conf.high, col = gender ), position=position_dodge(.9), width=.6 ) +
ylab( "fraction with DPQ score ≥ 10")
Anmerkung: Oft wurde der marital status “refused” nicht (wie hier) durch NA ersetzt sondern beibehalten, was zu einer Säule mit extrem großem KI führt. Diese Säule sollte idealerweise entfernt werden, oder die y-Ache angepasst.
Bepunktung
- Anteil mit DPQ-Score >= 10 berechnet, aufgeschlüsselt nach Geschlecht und marital status: 1 Punkt
- ½ Punkt Abzug, fals >10 statt >=10
- KIs berechnet: 2 Punkte
- Säulendiagramm erstellt: 1 Punkt
- Säulen für die beiden Geschlechter gut dargestellt: 1 Punkt
- Fehlerbalken eingetragen: 1 Punkt
- Code erklärt: ½ Punkt
- Plot interpretiert: 1 Punkt
Weitere Punkte
Punkte zur äußeren Form des gesamten Notebooks:
- Korrekt zu HTML formattiert mit allen Plots: 2 Punkte
- Für alle Ergebnis-Tabellen Tabellen-Anfang ausgegeben: 1 Punkt
- Notebook übersichtlich formattiert/gegliedert: 1 Punkt