Diskretisierung und anderes

Vorlesung “Datananalyse in der Biologie”

Hausaufgabe

Bevor wir zum neuen Stoff kommen, erst die Hausaufgabe. Aufgabe war, einen Plot mit der Durschschnitts-Größe der Probanden jedes Jahrgangs, aufgeschlüsselt nach Geschlecht, zu erstellen.

Wir laden zunächst Tidyverse und die Tabelle

suppressPackageStartupMessages(
  library( tidyverse ) )

read_csv( "Downloads/nhanes.csv", show_col_types=FALSE ) -> nhanes

nhanes
# A tibble: 8,704 × 6
   subjectId gender   age height weight ethnicity  
       <dbl> <chr>  <dbl>  <dbl>  <dbl> <chr>      
 1     93703 female     2   88.6   13.7 NH Asian   
 2     93704 male       2   94.2   13.9 NH White   
 3     93705 female    66  158.    79.5 NH Black   
 4     93706 male      18  176.    66.3 NH Asian   
 5     93707 male      13  158.    45.4 Other/Mixed
 6     93708 female    66  150.    53.5 NH Asian   
 7     93709 female    75  151.    88.8 NH Black   
 8     93710 female     0   NA     10.2 NH White   
 9     93711 male      56  171.    62.1 NH Asian   
10     93712 male      18  173.    58.9 Mexican    
# ℹ 8,694 more rows

Nun der Plot:

nhanes %>%
filter( !is.na(height) ) %>%
group_by( gender, age ) %>%
summarise( mean_height = mean( height ) ) %>%
ggplot( aes( x=age, y=mean_height, col=gender ) ) +
  geom_line() + geom_point() 
`summarise()` has grouped output by 'gender'. You can override using the
`.groups` argument.

Anmerkungen:

  • Wir gruppieren nach Geschlecht und Alter. Da “age” diskrete Werte (ganze Zahlen) hat funcktioniert das. Wenn “age” eine kontinuierliche Größe wäre (wie height), könnten wir keine Gruppen bilden, zumindest nicht, ohne vorher zu diskretisieren (z.B. durch Abrunden auf ganze Zahlen).
  • Wir haben hier zwei “Geoms”, die beide dieselben Daten verwenden. So erhalten wir Linien mit Punkten. Man kann einem Geom auch einen eigenen “aes()”-Block geben, wenn man möchte, das die geoms die Daten aus den Spalten verschieden verwenden.

Durch Facetting können wir noch nach Ethnie separieren:

nhanes %>%
filter( !is.na(height) ) %>%
group_by( gender, age, ethnicity ) %>%
summarise( mean_height = mean( height ) ) %>%
ggplot( aes( x=age, y=mean_height, col=gender ) ) +
  facet_wrap( ~ ethnicity ) + 
  geom_line() + geom_point() 
`summarise()` has grouped output by 'gender', 'age'. You can override using the
`.groups` argument.

Anmerkungen:

  • Wir haben hier “ethnicity” als dritte Gruppenvariable zu group_by hinzugefügt. Damit hat sich die Zahl der Gruppen versechsfacht.
  • Statt facet_grid habe ich diesmal facet_wrap verwendet. Es teilt die Plots in Kacheln auf, und ordnet die in einem Gitter an.

Inferenz: Erste Schritte

Sehen wir uns den ersten Plot nochmal genauer an und zoomen auf die linke Hälfte, indem wir in der x-Achse auf 0 bis 35 Jahre hinein zoomen (+ xlim( 0, 35 )).

nhanes %>%
filter( !is.na(height) ) %>%
group_by( gender, age ) %>%
summarise( mean_height = mean( height ) ) %>%
ggplot( aes( x=age, y=mean_height, col=gender ) ) +
  geom_line() + geom_point() + xlim( 0, 35 )
`summarise()` has grouped output by 'gender'. You can override using the
`.groups` argument.
Warning: Removed 90 rows containing missing values (`geom_line()`).
Warning: Removed 90 rows containing missing values (`geom_point()`).

Wir erkennen, dass Jungen und Mädchen in etwa bis zum 12. Lebensjahr gleich groß sind. Dann verlangsamt sich das Wachstum der Mädchen, wärend die Jungen noch 2 Jahre mit gleicher Geschwindigkeit weiter wachsen.

Im Alter von 11 Jahren scheinen die Mädchen aber etwas größer zu sein. Ist dieser Unterschied “echt” oder nur eine zufällige Fluktuation?

Anders ausgedrückt: ist dieser Unterschied statistisch signifikant?

Damit ist gemeint: Wenn die NHANES-Wissenschaftler ihre Untersuchung im selben Jahr wiederholt hätten, also nochmal so viele Probanden vermessen hätten, wäre zu erwarten, dass sich der Unterschied bestätigt hätte?

Oder auch: Wenn man im selben Jahr alle 11-jährigen Jungen und Mädchen vermessen hätte, die damals in den USA gelebt haben, könnten wir die Frage definitiv beantworten, ob damals die 11-jährigen Mädchen größer als die 11-jährigen Jungs gewesen sind. Dürfen wir annehmen, dass unser Ergebnis, das lediglich auf einer Stichprobe beruht, die Situation der Gesamtbevölkerung widerspiegelt?

Solche Fragen zu beantworten ist die Aufgabe der schließenden Statistik (inferential statistics).

Als Vorausschau betrachten wir einige Lösungsmöglichkeiten, die wir später genauer diskutieren werden.

Standardfehler

In der Mathe-Vorlesung haben Sie gelernt:

Zieht man eine Stichprobe der Länge \(n\) (also eine Stichprobe mit \(n\) Werten) aus einer Verteilung mit Erwartungswert \(\mu\) und Standardabweichung \(\sigma\), so hat die Verteilung des Mittelwerts der Stichprobe ebenfalls den Erwartungswert \(\mu\), aber die Standardabweichung \(\sigma_\text{M} = \frac{\sigma}{\sqrt{n}}\). Dies bezeichnet man als den Standardfehler des Mittelwerts (standard error of the mena, S.E.M.)

Kurz: \[\text{SEM}=\frac{\normalsize\text{SD}}{\normalsize\sqrt{n}} \]

Wir werden später wiederholen, was das genau bedeutet.

Zunächst berechnen wir den SEM für jeden Mittelwert:

nhanes %>%
filter( !is.na(height) ) %>%
group_by( gender, age ) %>%
summarise( 
  mean_height = mean( height ),
  sd_height = sd( height ),
  n = n() ) %>%
mutate( mean_sem = sd_height / sqrt(n) ) -> mean_heights_tbl
`summarise()` has grouped output by 'gender'. You can override using the
`.groups` argument.
mean_heights_tbl
# A tibble: 158 × 6
# Groups:   gender [2]
   gender   age mean_height sd_height     n mean_sem
   <chr>  <dbl>       <dbl>     <dbl> <int>    <dbl>
 1 female     2        89.5      4.56   101    0.454
 2 female     3        97.7      4.71    74    0.548
 3 female     4       106.       5.67    93    0.588
 4 female     5       112.       6.02    87    0.645
 5 female     6       119.       5.97    75    0.689
 6 female     7       124.       6.47    79    0.727
 7 female     8       132.       7.79    85    0.844
 8 female     9       137.       7.28    94    0.750
 9 female    10       144.       6.72    93    0.697
10 female    11       153.       7.24    95    0.743
# ℹ 148 more rows

Nun

mean_heights_tbl %>%
ggplot( aes( x=age, y=mean_height, col=gender, fill=gender ) ) +
  geom_line() +
  geom_errorbar( aes( 
    ymin = mean_height - mean_sem, 
    ymax = mean_height + mean_sem ), width=.4 ) +
  xlim( 0, 25 )
Warning: Removed 110 rows containing missing values (`geom_line()`).

Wenn sich die Fehlerbalken überlappen, gibt es keinen signifikanten Unterschied. Aber wie viel Lücke sollte zwischen den Fehlerbalken sein, damit wir glauben können, dass der Unterschied signifikant ist? Dass werden wir noch klären müssen.

t-Test

Sicher erinnern Sie sich noch an den t-Test.

Wir reduzieren die Tabelle auf nur die 11-jährigen:

nhanes %>%
filter( age == 11, !is.na(height) )
# A tibble: 168 × 6
   subjectId gender   age height weight ethnicity     
       <dbl> <chr>  <dbl>  <dbl>  <dbl> <chr>         
 1     93733 female    11   143.   40.8 NH White      
 2     93736 male      11   143.   36.9 Mexican       
 3     93820 female    11   163.   72.6 NH Black      
 4     93922 male      11   148.   49.3 Other Hispanic
 5     93928 male      11   166.   37.5 NH White      
 6     93997 female    11   160.   44.8 NH White      
 7     94009 female    11   155    48.5 Other Hispanic
 8     94091 male      11   147.   40.7 Mexican       
 9     94107 female    11   141.   42.3 NH White      
10     94257 male      11   146.   33   NH Asian      
# ℹ 158 more rows

Nun vergleichen wir mit dem t-Test:

nhanes %>%
filter( age == 11, !is.na(height) ) %>%
t.test( height ~ gender, . )

    Welch Two Sample t-test

data:  height by gender
t = 2.7816, df = 152.91, p-value = 0.00609
alternative hypothesis: true difference in means between group female and group male is not equal to 0
95 percent confidence interval:
 0.9221609 5.4427418
sample estimates:
mean in group female   mean in group male 
            152.7537             149.5712 

Ein p-Wert von 0.6% – ist das signifikant? Auch dass werden wir noch besprechen müssen.

Zur Funtion t.test:

  • Diese Funktion erwartet als erstes Argument eine “Formel”, als zweites die Tabelle mit den Daten.
  • Die Tabelle mit den Daten haben wir mit dem Pipe-Pfeil %>% in die t-Test-Funtion hinein geschoben. Normalerweise schiebt der Pipe-Pfeil das, was links von ihm stellt, in die Position des ersten Arguments. Hier wird die Tabelle aber als zweites Argument erwartet. Der Punkt (.) im zweiten Argument markiert für den Pipe-Pfeil, wo die Tabelle hin soll. (Nur wenn der Punkt fehlt, wird ins erste Argument geschoben. Das ist eine Subtilität der Tidyverse-Pipes, die wir bisher übergangen haben.)
  • Zur “Formel” im ersten Argument:
    • In R heisst alles “Formel” (formula), was eine Tilde (~) enthält.
    • t.test liesst die Formel wie folgt:
      • Links von der Tilde steht der Name der Spalte, die die Werte enthält
      • Rechts von der Tilde steht der Name einer diskreten Spalte, die genau zwei verschiedene Levels enthält und so die Werte zwei Gruppen zuordnet.

Hier teilt die Spalte gender die Werte in height auf zwei Gruppen, male und female auf.

Die t.test-Funktion testet die Nullhypothese, dass der Mittelwerte der height-Werte in den beiden Gruppen derselbe ist.

Diskretisierung

Begriffe

  • Größen, die beliebige Werte innerhalb eines Wertebereichs annehmen können, heißen kontinuierlich (continuous). Beispiele hierfür in unserer NHANES-Tabelle sind height und weight.

  • Das Gegenteil von kontinuierlich ist diskret (discrete). (In den MINT-Fächern wird das Wort “diskret” oft in dieser Bedeutung, oder in der verwandten Bedeutung “einzeln, getrennt”, verwendet. Es hat nichts mit “geheim” zu tuin.)

  • In unserer Tabelle is age diskret, da auf ganze Jahre abgerundet wurde.

  • Ein wichtiger Sonderfall diskreter Größen sind Faktoren, d.h., Größen die nur bestimmte, aus einer vorgegebenen Gesamtheit entnommene Werte (die Levels) annehmen dürfen. Faktoren in unserer Tabelle sind gender (mit den 2 Levels male und female) und ethnicity (mit 6 Levels).

  • In R gibt es einen feinen Unterschied zwischen “character vectors” und “factors”. Unsere Tabellenspalten haben den Typ “character”; wir könnten ihn aber auf “factor” ändern. WO das einen unterschied macht, kommt später.

Diskretiserung durch Binning

Anfangs haben wir bemerkt, dass wir nur nach Alter gruppieren konnten, weil das Alter auf ganze Zahlen abgerundet ist und wir daher eine überschaubare Anzahl möglicher Werte haben. Bei Alter, Gewicht, oder BMI hingegen kommt wahrscheinlich nie exakt derselbe Werte zweimal vor. Wenn wir also nach solchen kontinuierlichen Werte gruppieren, wird jede Tabellenzeile in einer eigenen Gruppe landen.

Hier macht es oft Sinn, kontinuierliche Werte anhand eines Rasters aus Grenzen zu diskretisieren.

Beispiel 1: Beim Histogramm bildet R den Wertebereich auf ein Raster von “Bins” (Eimern) ab, und ordnet jeden Wert in eine Bin, die dann als ein Balken im Diagramm dargestellt wird.

Beispiel 2: Die WHO schlägt vor, BMI-Werte wie folgt zu interpretieren:

  • unter 18.5: untergewichtig
  • 18.5 bis 25: normal
  • 25 - 30: übergewichtig
  • über 30: adipös

Zunächst zu Beispiel 1:

Hier ist nochmal das Histogramm der BMI-Werte:

nhanes %>%
filter( age >= 18 ) %>%
mutate( bmi = weight / (height/100)^2 ) %>%
filter( !is.na(bmi) ) -> nhanes_adults
nhanes_adults %>%
ggplot( aes( x=bmi ) ) + 
  geom_histogram(bins=40) + 
  geom_vline( xintercept = c( 18.5, 25, 30 ) )

Hier sehen wir beide Möglichkeiten:

  1. Die Histogramm-Funktion hat den gesamten Wertebereich in 40 “Bins” eingeteilt und gezählt, wieviele Werte in jedes Bin fallen.

  2. Die vertikalen Linien (die ich mit geom_vline eingefügt habe), markieren die WHO-Grenzwerte.

Nun möchten wir zählen, wie viele der Probanden in die vier WHO-Kategorien jeweils fallen.

Die R-Funktion cut ermöglicht uns, die BMI-Werte entlang dieser Grenzen zu diskretisieren:

nhanes_adults %>%
mutate( weight_state = cut( bmi, breaks = c( 0, 18.5, 25, 30, Inf ) ) )
# A tibble: 5,434 × 8
   subjectId gender   age height weight ethnicity     bmi weight_state
       <dbl> <chr>  <dbl>  <dbl>  <dbl> <chr>       <dbl> <fct>       
 1     93705 female    66   158.   79.5 NH Black     31.7 (30,Inf]    
 2     93706 male      18   176.   66.3 NH Asian     21.5 (18.5,25]   
 3     93708 female    66   150.   53.5 NH Asian     23.7 (18.5,25]   
 4     93709 female    75   151.   88.8 NH Black     38.9 (30,Inf]    
 5     93711 male      56   171.   62.1 NH Asian     21.3 (18.5,25]   
 6     93712 male      18   173.   58.9 Mexican      19.7 (18.5,25]   
 7     93713 male      67   179.   74.9 NH White     23.5 (18.5,25]   
 8     93714 female    54   148.   87.1 NH Black     39.9 (30,Inf]    
 9     93715 male      71   171.   65.6 Other/Mixed  22.5 (18.5,25]   
10     93716 male      61   159.   77.7 NH Asian     30.7 (30,Inf]    
# ℹ 5,424 more rows

Beachte: Die Liste mit den Grenzwerten für cut muss auch die “äußeren” Grenzen enthalten, hier 0 und Inf (= infinity).

Wir können auch Labels (Faktor-Levels) angeben:

nhanes_adults %>%
mutate( weight_state = cut( bmi, 
     breaks = c( 0, 18.5, 25, 30, Inf ),
     labels = c( "underweight", "normal", "overweight", "obese" ) ) )
# A tibble: 5,434 × 8
   subjectId gender   age height weight ethnicity     bmi weight_state
       <dbl> <chr>  <dbl>  <dbl>  <dbl> <chr>       <dbl> <fct>       
 1     93705 female    66   158.   79.5 NH Black     31.7 obese       
 2     93706 male      18   176.   66.3 NH Asian     21.5 normal      
 3     93708 female    66   150.   53.5 NH Asian     23.7 normal      
 4     93709 female    75   151.   88.8 NH Black     38.9 obese       
 5     93711 male      56   171.   62.1 NH Asian     21.3 normal      
 6     93712 male      18   173.   58.9 Mexican      19.7 normal      
 7     93713 male      67   179.   74.9 NH White     23.5 normal      
 8     93714 female    54   148.   87.1 NH Black     39.9 obese       
 9     93715 male      71   171.   65.6 Other/Mixed  22.5 normal      
10     93716 male      61   159.   77.7 NH Asian     30.7 obese       
# ℹ 5,424 more rows

Aufgabe

  • Zählen Sie: Wie viele Frauen und wie viele Männer sind jeweils in den 4 Kategorien?

  • Berechnen Sie daraus Prozent-Werte. Achten Sie darauf, dass sich die Prozentwerte für Männer und Frauen separat auf 100% addieren.

  • Plotten Sie das Ergebnis in einer geeigneten Weise.

Lösung

Wir gruppieren nach den WHO-Kategorien und dem Geschlecht, und zählen dann:

nhanes_adults %>%
mutate( weight_state = cut( bmi, 
     breaks = c( 0, 18.5, 25, 30, Inf ),
     labels = c( "underweight", "normal", "overweight", "obese" ) ) ) %>%
group_by( gender, weight_state ) %>%
summarize( n = n() )
`summarise()` has grouped output by 'gender'. You can override using the
`.groups` argument.
# A tibble: 8 × 3
# Groups:   gender [2]
  gender weight_state     n
  <chr>  <fct>        <int>
1 female underweight     57
2 female normal         743
3 female overweight     795
4 female obese         1216
5 male   underweight     43
6 male   normal         639
7 male   overweight     930
8 male   obese         1011

Hier haben wir bei summarise einen Spaltennamen angegeben, nämlich n (statt bisher n()), indem wir den gewünschten Spaltennamen, gefolgt von einem =, vor die Summarisierungs-Operation gesetzt haben.

Nun möchten wir die diese Tabelle noch um eine Spalte mit Prozenten ergänzen:

nhanes_adults %>%
mutate( weight_state = cut( bmi, 
     breaks = c( 0, 18.5, 25, 30, Inf ),
     labels = c( "underweight", "normal", "overweight", "obese" ) ) ) %>%
group_by( gender, weight_state ) %>%
summarize( n = n() ) %>%
  
group_by( gender ) %>%
mutate( percent  = n / sum(n) * 100 ) -> bmi_perc
`summarise()` has grouped output by 'gender'. You can override using the
`.groups` argument.
bmi_perc
# A tibble: 8 × 4
# Groups:   gender [2]
  gender weight_state     n percent
  <chr>  <fct>        <int>   <dbl>
1 female underweight     57    2.03
2 female normal         743   26.4 
3 female overweight     795   28.3 
4 female obese         1216   43.3 
5 male   underweight     43    1.64
6 male   normal         639   24.4 
7 male   overweight     930   35.5 
8 male   obese         1011   38.5 

Wie funktioniert dies?

  • Das zweite group_by teilt die 8 Zeilen in zwei Gruppen (male, female) zu je 4 Zeilen ein.
  • Das nachfolgende mutate wird für jede Gruppe getrennt durchgeführt. Daher ergibt sum(n) die Summe der Spalte n nur über die Zeilen der Gruppe. Jedes n wird also durch die Summe der vier n-Werte des jeweiligen Geschlechts geteilt. Somit addieren sich die Geschlechter jeweils getrennt zu 100%.

Zum Plotten verwenden wir ein gestapeltes Säulendiagramm (stacked bar chart):

bmi_perc %>%
ggplot( aes( x=gender, y=percent, fill=weight_state ) ) +
  geom_col()

Anmerkungen zum Plot:

  • Es gibt zwei Geoms für Bar-Charts: geom_col und geom_bar.
    • geom_col entnimmt die Höher der (Teil-)Säulen der in aes für y angegebenen Spalte.
    • geom_bar zählt, wie viele Zeilen es gibt, die denselben Wert micht – versucht also automatische zu machen, was wir schon manuell erledigt haben. Wir verwenden es hier nicht, da wir geom_bar nicht klar machen können, dass wir bereits auf 100% normalisiert haben.
  • In der Datentabelle, die wir ggplot gegeben haben, gibt es jeweils vier Zeilen mit demselben Wert in gender, der Spalte, die wir der x-Achse zugewiesen haben. Daher gibt es zu den beiden Positionen auf der x-Achse je 4 Säulen, die R einfach übereinander gestapelt hat.

Hausaufgabe

Erweitern Sie diesen Plot, indem Sie nach Ethnie facettieren.

Lösung

Wir ändern den Code von oben leicht ab, indem wir bei den beiden group_bys noch ethnicity hinzufügen:

nhanes_adults %>%
mutate( weight_state = cut( bmi, 
     breaks = c( 0, 18.5, 25, 30, Inf ),
     labels = c( "underweight", "normal", "overweight", "obese" ) ) ) %>%
group_by( gender, ethnicity, weight_state ) %>%
summarize( n = n() ) %>%
  
group_by( gender, ethnicity ) %>%
mutate( percent  = n / sum(n) * 100 ) -> bmi_perc_2
`summarise()` has grouped output by 'gender', 'ethnicity'. You can override
using the `.groups` argument.
bmi_perc_2
# A tibble: 48 × 5
# Groups:   gender, ethnicity [12]
   gender ethnicity weight_state     n percent
   <chr>  <chr>     <fct>        <int>   <dbl>
 1 female Mexican   underweight      4    1.06
 2 female Mexican   normal          60   16.0 
 3 female Mexican   overweight     125   33.2 
 4 female Mexican   obese          187   49.7 
 5 female NH Asian  underweight     15    3.59
 6 female NH Asian  normal         208   49.8 
 7 female NH Asian  overweight     128   30.6 
 8 female NH Asian  obese           67   16.0 
 9 female NH Black  underweight     11    1.65
10 female NH Black  normal         116   17.4 
# ℹ 38 more rows

Mit dieser Tabelle können wir nun den Plot genauso wie vorher erstellen. Wir fügen einfach ein facet_grid hinzu:

bmi_perc_2 %>%
ggplot( aes( x=gender, y=percent, fill=weight_state ) ) +
  geom_col() + facet_grid( cols=vars(ethnicity) )

Vielleicht ist es übersichtlicher, wenn wir Facette und x-Achse tauschen:

bmi_perc_2 %>%
ggplot( aes( x=ethnicity, y=percent, fill=weight_state ) ) +
  geom_col() + facet_grid( cols=vars(gender) ) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

Hier habenw ir am Schluss noch Code eingefügt, um die Beschriftung der x-Achse zu drehen.