Beispiel für eine logistische Regression

Wozu ist das gut?

Kurz gesagt ist die logistische Regression ein Werkzeug, um dichotome (zweiwertige) Ereignisse vorherzusagen (auf Basis eines Datensatzes mit einigen Prädiktoren).

Was sagt uns die logistische Regression?

Möchte man z.B. vorhersagen, ob eine E-Mail Spam ist oder nicht, so ist es nützlich, für jede zu prüfende Mail eine Wahrscheinlichkeit zu bekommen. So könnte uns die logistische Regression sagen: “Eine Mail mit diesen Ausprägungen in den Prädiktoren hat eine Wahrschenlichkeit von X Prozent, dass es sich um Spam handelt”.

Warum nicht die “normale” Regression?

Die “normale” Regression ist für so eine Situation nicht (gut) geeignet, da die normale Regression sowohl negative Werte als auch Werte über 1 (100%) vorhersagen kann - was für Wahrscheinlichkeiten keinen Sinn machen würde.

Beispiel bitte!

Nehmen wir den Datensatz Titanic als Beispiel-Datensatz. Die Forschungsfrage lautet:

Hat die Passagierklasse einen Einfluss auf die Überlebenswahrscheinlichkeit?

Vorbereitung: Daten und Pakete laden

library("titanic")
data("titanic_train")
library(mosaic)

Datensatz anschauen

inspect(titanic_train)
## 
## categorical variables:  
##       name     class levels   n missing
## 1     Name character    891 891       0
## 2      Sex character      2 891       0
## 3   Ticket character    681 891       0
## 4    Cabin character    148 891       0
## 5 Embarked character      4 891       0
##                                    distribution
## 1 Abbing, Mr. Anthony (0.1%) ...               
## 2 male (64.8%), female (35.2%)                 
## 3 1601 (0.8%), 347082 (0.8%) ...               
## 4  (77.1%), B96 B98 (0.4%) ...                 
## 5 S (72.3%), C (18.9%), Q (8.6%) ...           
## 
## quantitative variables:  
##             name   class  min       Q1   median    Q3      max        mean
## ...1 PassengerId integer 1.00 223.5000 446.0000 668.5 891.0000 446.0000000
## ...2    Survived integer 0.00   0.0000   0.0000   1.0   1.0000   0.3838384
## ...3      Pclass integer 1.00   2.0000   3.0000   3.0   3.0000   2.3086420
## ...4         Age numeric 0.42  20.1250  28.0000  38.0  80.0000  29.6991176
## ...5       SibSp integer 0.00   0.0000   0.0000   1.0   8.0000   0.5230079
## ...6       Parch integer 0.00   0.0000   0.0000   0.0   6.0000   0.3815937
## ...7        Fare numeric 0.00   7.9104  14.4542  31.0 512.3292  32.2042080
##               sd   n missing
## ...1 257.3538420 891       0
## ...2   0.4865925 891       0
## ...3   0.8360712 891       0
## ...4  14.5264973 714     177
## ...5   1.1027434 891       0
## ...6   0.8060572 891       0
## ...7  49.6934286 891       0

Aha! Etwa 38% der Menschen an Board haben überlebt.

Deskriptive Analyse

Ob die Überlebensrate (Wahrscheinlichkeit) von der Passagierklasse (pclass) abhängt? Schauen wir uns die Häufigkeitsunterschied an:

tally(~ Survived | Pclass, 
      data = titanic_train)
##         Pclass
## Survived   1   2   3
##        0  80  97 372
##        1 136  87 119

Besser in Anteilen:

tally(~ Survived | Pclass, 
      data = titanic_train,
      format = "proportion")
##         Pclass
## Survived         1         2         3
##        0 0.3703704 0.5271739 0.7576375
##        1 0.6296296 0.4728261 0.2423625

In der ersten Klasse lag die Überlebensrate bi ca. 63%, in der dritten nur noch bei 24%. Das ist ein großer Unterschied. Rein deskriptiv können wir unsere Forschungsfrage also bejaen, oder genauer, festhalten, dass die Daten diesen Schluss nahe legen.

Regressionsmodell spezifizieren

mein_glm1 <- glm(Survived ~ Pclass, 
                 data = titanic_train,
                 family = "binomial")

Ergebnisse betrachten

summary(mein_glm1)
## 
## Call:
## glm(formula = Survived ~ Pclass, family = "binomial", data = titanic_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -1.4390  -0.7569  -0.7569   0.9367   1.6673  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  1.44679    0.20743   6.975 3.06e-12 ***
## Pclass      -0.85011    0.08715  -9.755  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1186.7  on 890  degrees of freedom
## Residual deviance: 1084.4  on 889  degrees of freedom
## AIC: 1088.4
## 
## Number of Fisher Scoring iterations: 4

Der Einfluss der Passagierklasse ist statistisch signifikant, das ist einfach zu sehen.

Überlebenswahrscheinlichkeit pro Klasse

Aber wo sieht man jetzt die Überlebenswahrscheinlichkeit pro Klasse? Vielleicht ist es so am einfachsten:

predict(mein_glm1, 
        newdata = data.frame(Pclass = c(1,2,3)),
        type = "response")
##         1         2         3 
## 0.6448970 0.4369809 0.2490789

Wir sagen R, dass sie auf Basis unseres Modells (mein_glm1) vorhersagen (predict) soll, wie die Überlebenswahrscheinlichkeit für eine Person der 1,2 und 3. Klasse ist. Mt type = "response" sagen wir R, dasss wir als Einheit die Skala der Response-Variablen (also Survived) haben wollen, das heißt, in Wahrscheinlichkeiten.

Die Wahrscheinlichkeit sind ziemlich genau die Werte, die uns die einfache deskriptive Analyse auch gezeigt hat, das macht Sinn.

Einflussgewicht des Prädiktors verstehen

Möchte man die Odds des Überlebens wissen, so muss man den in der R-Ausgabe angegebenen Wert delogarithmieren, also \(e^x\) rechnen, wobei \(x\) der Wert in der Ausgabe ist.

exp(-0.85)
## [1] 0.4274149

Die Einflussstärke des Prädiktors liegt also bei -.42 Odds. Von Passagierklasse 1 auf 2 (bzw. 2 auf 3) sinkt die Chance zu Überleben um 0.42. Grob gesagt sind das 0,5 oder 1/2. Liegt die Chance zu Überleben bei 1/2 bzw. 1:2, so heißt das, das 1 Person überlebt, und zwei sterben.

In Passagierklasse “Null” beträgt die Odds (die Chance) zu Überlgen:

exp(1.44679)
## [1] 4.249452

Also etwa 4:1, 4 überleben, 1 stirbt. Das ist hypothetisch, weil es gibt keine Passagierklasse Null. Schauen wir uns also die Chance für Klasse 1 an:

exp(1.45 - 0.42)
## [1] 2.801066

Etwa 2.8, grob gesagt: 3 überleben, 1 stirbt. Mit anderen Worten die Überlebenschance ist (etwas weniger als) 75%, wenn pclass = 1.

Visualisierung

plotModel(mein_glm1)

Hm, sieht man nicht so viel. Vielleicht nur den Scatterplot:

gf_jitter(Survived ~ Pclass, data = titanic_train, 
          width = .1, alpha = .3)

Oder mit Balkendiagrammen:

Zuerst die Häufigkeit in braver ("tidy), d.h. zum Visualisieren geeigneter Form:

tally(Survived ~ Pclass, format = "data.frame",
      data = titanic_train)
##   Survived Pclass Freq
## 1        0      1   80
## 2        1      1  136
## 3        0      2   97
## 4        1      2   87
## 5        0      3  372
## 6        1      3  119

Diese kleine Häufigkeitstabelle geben wir an gf_col (col wie Column) weiter.

tally(Survived ~ Pclass, 
      format = "data.frame",
      data = titanic_train) %>% 
  gf_col(Freq ~ Pclass, fill = ~ Survived,
         position = "fill")

Klassifikationsgüte

Das Modell gibt uns für jeden Passagier die Überlebenswahrscheinlichkeit \(p\) zurück. Jetzt müssen wir uns noch entscheiden, welches Ereignis \(E\) wir der Wahrscheinlichkeit zuordnen. Die Ergebnismenge \(\Omega\) beinhaltet zwei Elemente: Überleben \(ü\) und sterben \(s\), \(\Omega = {ü, s}\) (oder wir könnten schreiben: \(\Omega = {1, 0}\). Die Frage ist also, bei welcher Wahrscheinlichkeit wir “überleben” und bei welcher “sterben” vorhersagen. Sagen wir, dass wir von “überleben” ausgehen, wenn das Modell eine Wahrscheinlichkeit größer 50% vorhersagt, andernfalls gehen wir von “sterben” aus:

\[ E= \begin{cases} {ü,} \mbox{ wenn } p > .5 \\ {s,} \mbox{ ansonsten } \end{cases} \]

Fügen wir als erstes die vorhergesagten Wahrscheinlichkeiten und das zugehörige Ereignis (ü, s) zum Datensatz hinzu. Es bietet sich an, überleben wird mit 1 und sterben mit 0 zu klassizieren.

titanic2 <- titanic_train %>% 
  mutate(Survived_p_est = predict(mein_glm1, 
                                  type = "response"),
         Survived_est = case_when(
           Survived_p_est > .5 ~ 1,
           Survived_p_est <= .5 ~ 0))

titanic2 %>% 
  select(Survived, 
         Pclass, 
         Survived_p_est, 
         Survived_est) %>% 
  head()
##   Survived Pclass Survived_p_est Survived_est
## 1        0      3      0.2490789            0
## 2        1      1      0.6448970            1
## 3        1      3      0.2490789            0
## 4        1      1      0.6448970            1
## 5        0      3      0.2490789            0
## 6        0      3      0.2490789            0

Das Suffix est steht für “estimated”, also für “vom Modell geschätzt”.

Jetzt vergleichen wir mal die tatsächlichen Ereignisse (ü,s) mit den Klassifikationen des Modells:

tally(~Survived | Survived_est, 
      data = titanic2)
##         Survived_est
## Survived   0   1
##        0 469  80
##        1 206 136

Die Gegenüberstellung von tatsächlichen und vom Modell vorhergesagten (klassifizierten) Ereignissen bezeichnet man auch als Konfusionsmatrix. Daraus lassen sich Gütekennzahlen ableiten. Einige wichitge sind:

  • Wie viele Personen (Fälle) wurden insgesamt korrekt klassifiziert? Gesamtgenauigkeit
  • Wie viele ü-Fälle wurden richtig (also als ü) klassifiziert? Sensitivität
  • Wie viele s-Fälle (Nicht-ü-Fälle) wurden richtig (also als s) klassifiziert? Spezifität

In diesem Fall gilt:

  • Gesamtgenauigkeit (accuracy)
a <- (469 + 136) / (469+80+206+136) 
a
## [1] 0.6790123

Komfortabler geht es mit einem mundgerechten Befehl, z.B. dem:

library(caret)

confusionMatrix(data = factor(titanic2$Survived_est), 
                reference = factor(titanic2$Survived))
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 469 206
##          1  80 136
##                                           
##                Accuracy : 0.679           
##                  95% CI : (0.6472, 0.7096)
##     No Information Rate : 0.6162          
##     P-Value [Acc > NIR] : 5.536e-05       
##                                           
##                   Kappa : 0.2707          
##                                           
##  Mcnemar's Test P-Value : 1.453e-13       
##                                           
##             Sensitivity : 0.8543          
##             Specificity : 0.3977          
##          Pos Pred Value : 0.6948          
##          Neg Pred Value : 0.6296          
##              Prevalence : 0.6162          
##          Detection Rate : 0.5264          
##    Detection Prevalence : 0.7576          
##       Balanced Accuracy : 0.6260          
##                                           
##        'Positive' Class : 0               
##