“Denn im Formulieren des Problems ist die Lösung schon enthalten!” — Käptn Peng & Die Tentakel von Delphi, Meister und Idiot
Die Selektion von Variablen in einem Modell. Ein schwieriges Thema. Entweder kenne ich mein Experiment und habe das Experiment so geplant, dass nur die bedeutenden Variablen mit in dem Experiment sind oder ich habe keine Ahnung. Gut, dass ist überspitzt und gemein formuliert. Wir wollen uns in diesem Kapitel den Fall anschauen, dass du sehr viele Variablen \(x\) erhoben hast und nun statistisch bestimmen willst, welche Variablen nun mit in das finale Modell sollen. Achtung, ich spreche hier nicht von einem Blockdesign oder aber einem Feldexperiment. Da hat die Variablenselektion nichts zu suchen. Daher tritt der Fall der Variablenselektion eher in dem Feld Verhaltensbiologie oder aber auch Ökologie auf. Ebenfalls kann die Anwendung in automatisch erfassten Daten einen Sinn machen. Wir nutzen dann die Variablenselektion (eng. feature selection) zu Dimensionsreduktion des Datensatzes. Der Datensatz soll damit einfacher sein… ob der Datensatz das damit auch wird, ist wieder eine andere Frage.
In diesem Kapitel prügeln wir aber einen statistischen Engel. Wir werden hier mal schauen müssen, was alles geht und was nicht. Variablen Selektion ist faktisch nicht ein Kapitel sondern ein Regal(kilo)meter voll mit Büchern.
Zu der Frage welches Verfahren denn nun das richtige Verfahren zur Selektion von Variablen ist, gibt es die Standardantwort in der Statistik: Es kommt auf die Fragestellung an…. Oder aber was ist wie gut implementiert, dass wir das Verfahren einigermaßen gut nutzen können. Wir gehen daher von einfach zu kompliziert und du musst dann schauen, was du nutzen willst und kannst. Wir müssen zum Beispiel unterscheiden, welcher Verteilung das Outcome \(y\) folgt. Wenn wir ein normalverteiltes \(y\) haben, dann haben wir andere Möglichkeiten, als wenn wir uns ein poissonverteiltes oder binominalverteiltes \(y\) anschauen.
Das R Paket {olsrr} erlaubt eine weitreichende Variablen Selektion, wenn ein normalverteiltes Outcome \(y\) vorliegt.
Im Folgenden will ich kurz die fünf Mythen der Variablenselektion von Heinze und Dunkler (2017) zusammenfassen. Wie immer ersetzt meine deutsche Zusammenfassung und Auswahl nicht das eigenständige Lesen der englischen Orgnialquelle, wenn du die Informationen in deiner Abschlussarbeit zitieren willst.
Die Anzahl der Variablen in einem Modell sollte reduziert werden, bis es 10 Ereignisse pro Variable gibt. Simulationsstudien haben gezeigt, dass multivariable Modelle bei zu niedrigen Verhältnissen von Ereignissen bzw. Beobachtungen pro Variable (eng events per variable, abk. EPV) sehr instabil werden. Aktuelle Empfehlungen besagen, dass je nach Kontext mindestens 5-15 EPV verfügbar sein sollten. Wahrscheinlich sind sogar viel höhere Werte wie 50 EPV erforderlich, um annähernd stabile Ergebnisse zu erzielen.
Nur Variablen mit nachgewiesener Signifikanz des univariaten Modells sollten in ein Modell aufgenommen werden. Die univariable Vorfilterung trägt nicht zur Stabilität des Auswahlprozesses bei, da sie auf stochastischen Quanten beruht und dazu führen kann, dass wichtige Anpassungsvariablen übersehen werden.
Nicht signifikante Effekte sollten aus einem Modell entfernt werden. Regressionskoeffizienten hängen im Allgemeinen davon ab, welche anderen Variablen in einem Modell enthalten sind, und ändern folglich ihren Wert, wenn eine der anderen Variablen in einem Modell weggelassen wird.
Der berichtete P-Wert quantifiziert den Typ-I-Fehler einer fälschlich ausgewählten Variablen. Ein P-Wert ist ein Ergebnis der Datenerhebung und -analyse und quantifiziert die Plausibilität der beobachteten Daten unter der Nullhypothese. Daher quantifiziert der P-Wert nicht den Fehler vom Typ I. Es besteht auch die Gefahr einer falschen Eliminierung von Variablen, deren Auswirkungen durch die bloße Angabe des endgültigen Modells eines Variablenauswahlverfahrens überhaupt nicht quantifiziert werden können.
Variablenauswahl vereinfacht die Analyse. Für das jeweilige Problem muss eine geeignete Variablenauswahlmethode gewählt werden. Statistiker haben die Rückwärtselimination als die zuverlässigste unter den Methoden empfohlen, die sich mit Standardsoftware leicht durchführen lassen. Eine Auswahl ist eine “harte Entscheidung”, die aber oft auf vagen Größen beruht. Untersuchungen zur Modellstabilität sollten jede angewandte Variablenauswahl begleiten, um die Entscheidung für das schließlich berichtete Modell zu rechtfertigen oder zumindest die mit der Auswahl der Variablen verbundene Unsicherheit zu quantifizieren.
Im Weiteren sei auch noch auf Heinze u. a. (2018) und Talbot und Massamba (2019) verwiesen. Beide Veröffentlichungen liefern nochmal einen fundierten Block auf die Variablenselektion. Wiederum ist das natürlich nur ein winziger Ausschnitt aus der Literatur zur Variablenselektion. Im Zweifel einfach einmal bei Google Scholar nach Variablenselektion suchen und schauen was so in dem Feld der eigenen Forschung alles gemacht wird.
Sensitivitätsanalysen nach der Variablenselektion
“Variable selection should always be accompanied by sensitivity analyses to avoid wrong conclusions.”(Heinze und Dunkler 2017, p. 9)
Nachdem wir Variablen aus unseren Daten entfernt haben, ist es üblich noch eine Sensitivitätsanalysen durchzuführen. Wir Vergleich dann das selektierte Modell mit anderen Modellen. Oder wir wollen die Frage beantworten, was hat eigentlich meine Variablenselektion am Ergebnis geändert? Habe ich eine wichtige Variable rausgeschmissen? Das machen wir dann gesammelt in dem Kapitel 48 zu den Sensitivitätsanalysen.
46.1 Genutzte R Pakete
Wir wollen folgende R Pakete in diesem Kapitel nutzen.
An der Seite des Kapitels findest du den Link Quellcode anzeigen, über den du Zugang zum gesamten R-Code dieses Kapitels erhältst.
46.2 Daten
Um die Variablenselektion einmal durchzuführen nurtzen wir zwei Datensätze. Zum einen den Datensatz zu den Kichererbsen in Brandenburg mit einem normalverteilten Outcome \(y\) mit dryweight. Wir laden wieder den Datensatz in R und schauen uns einmal die Daten in Tabelle 46.1 als Auszug aus dem Tabellenblatt an.
Wir du schon siehst, wir brauchen Fallzahl um hier überhaupt was zu machen. Bitte keine Variablenselektion im niedrigen zweistelligen Bereich an Beobachtungen.
R Code [zeigen / verbergen]
chickpea_tbl <-read_excel("data/chickpeas.xlsx")
Wir sehen, dass wir sehr viele Variablen vorleigen haben. Sind denn jetzt alle Variablen notwendig? Oder können auch ein paar Variablen raus aus dem Modell. So viele Beobachtungen haben wir mit \(n = 95\) ja nicht vorliegen. Daher wollen wir an diesem Datensatz die Variablenselektion unter der Annahme eines normalverteilten \(y\) durchgehen.
Tabelle 46.1— Auszug aus dem Daten zu den Kichererbsen in Brandenburg.
temp
rained
location
no3
fe
sand
forest
dryweight
25.26
high
north
5.56
4.43
63
>1000m
253.42
21.4
high
northeast
9.15
2.58
51.17
<1000m
213.88
27.84
high
northeast
5.57
2.19
55.57
>1000m
230.71
…
…
…
…
…
…
…
…
29.04
low
north
5.64
2.87
53.27
>1000m
236.07
24.11
high
northeast
4.31
3.66
63
<1000m
259.82
28.88
low
northeast
7.92
2
65.75
>1000m
274.75
Was wir auch noch wissen, ist wie die Effekte in den Daten wirklich sind. Die Daten wurden ja künstlich erstellt, deshalb hier die Ordnung der Effektstärke für jede Variable. Im Prinzip müsste diese Reihenfolge auch bei der Variablenselektion rauskommen. Schauen wir mal, was wir erhalten.
Viele Beispiele laufen immer unter der Annahme der Normalverteilung. Deshalb als zweites Beispiel nochmal die Daten von den infizierten Ferkeln mit einem binomialverteilten Outcome \(y\) mit infected. Auch hier können wir uns den Auszug der Daten in Tabelle 46.2 anschauen.
R Code [zeigen / verbergen]
pig_tbl <-read_excel("data/infected_pigs.xlsx")
Das schöne an diesem Datensatz ist jetzt, dass wir mit \(n = 412\) Beobachtungen sehr viele Daten vorliegen haben. Daher können wir auch alle Methoden gut verwenden und haben nicht das Problem einer zu geringen Fallzahl.
Tabelle 46.2— Auszug aus dem Daten zu den kranken Ferkeln.
age
sex
location
activity
crp
frailty
bloodpressure
weight
creatinin
infected
61
male
northeast
15.31
22.38
robust
62.24
19.05
4.44
1
53
male
northwest
13.01
18.64
robust
54.21
17.68
3.87
1
66
female
northeast
11.31
18.76
robust
57.94
16.76
3.01
0
…
…
…
…
…
…
…
…
…
…
61
male
northwest
15.26
23.1
robust
57.18
15.55
3.08
1
59
female
north
13.13
20.23
robust
56.64
18.6
3.41
0
63
female
north
10.01
19.89
robust
57.46
18.6
4.2
1
Auch in diesem Beispiel wurden die Daten von mir mit folgenden Effekten generiert. Schauen wir mal, was die Variablenselektion bei der hohen Fallzahl mit den Variablen macht bzw. welche Sortierung am Ende rauskommt.
Damit haben wir unsere beiden Beispiel und jetzt gehen wir mal eine Auswahl an Methoden zur Variablenselektion durch. Besonders hier, haltet den statistsichen Engel nah bei euch. Es wird leider etwas ruppig für den statistischen Engel.
46.3 Methoden der Variablenselektion
In den folgenden Abschnitten wollen wir uns eine Reihe an Methoden anschauen um eine Variablenselektion durchzuführen. Dabei gehen wir von einfach nach komplex. Wobei das komplex eher die Methode und nicht die Anwendung meint. Wir nutzen R Pakete und gehen nicht sehr ins Detail wie der Algorithmus jetzt die Auswahl trifft. Für den Hintergrund sind dann die Verweise auf die anderen Kapitel.
46.3.1 Per Hand
Manchmal ist der Anfang auch das Ende. Wir müssen ja gar keinen Algorithmus auf unsere Daten loslassen um eine Variablenselektion durchzuführen. Deshalb möchte ich gleich den ersten Abschnitt mit einem Zitat von Heinze und Dunkler (2017) beginnen.
“Oft gibt es keinen wissenschaftlichen Grund, eine (algorithmische) Variablenauswahl durchzuführen. Insbesondere erfordern Methoden der (algorithmische) Variablenselektion einen viel größeren Stichprobenumfang als die Schätzung eines multiplen Modells mit einem festen Satz von Prädiktoren auf der Grundlage (klinischer) Erfahrung.”(Übersetzt und ergänzt nach Heinze und Dunkler 2017, p. 9)
Fazit dieses kurzen Abschnitts. Wir können auf alles Folgende einfach verzichten und uns überlegen welche Variablen sinnvollerweise mit ins Modell sollen und das mit unserem Expertenwissen begründen. Gut, und was ist, wenn ich kein Experte bin? Oder wir aber wirklich Neuland betreten? Dann können wir eine Reihe anderer Verfahren nutzen um uns algortimisch einer Wahrheit anzunähern.
46.3.2 Univariate Vorselektion
Und weiter geht es mit Zitaten aus Heinze und Dunkler (2017) zu der Variablenselektion. Dazu musst du wissen, dass die univariate Vorselektion sehr beliebt war und auch noch ist. Denn die univariate Vorselektion ist einfach durchzuführen und eben auch gut darzustellen.
“Obwohl die univariable Vorfilterung nachvollziehbar und mit Standardsoftware leicht durchführbar ist, sollte man sie besser ganz vergessen, da sie für die Erstellung multivariabler Modelle weder Voraussetzung noch von Nutzen ist.”(Übersetzt nach Heinze und Dunkler 2017, p. 8)
Ich sage immer, auch mit einem Hammer kann man Scheiben putzen. Halt nur einmal… Deshalb auch hier die univariate Variante der Vorselektion.
Wir sehen also, eigentlich ist die univariate Variablensleketion nicht so das gelbe vom Ei, aber vielleicht musst die Variablenselektion durchführen, so dass her die Lösung in R einmal dargestellt ist. Wir nutzen einmal die gaussian lineare Regression für den Kichererbsendatensatz. Es ist eine ganze Reihe an Code, das hat aber eher damit zu tun, dass wir die Modellausgabe noch filtern und anpassen wollen. Die eigentliche Idee ist simple. Wir nehmen unseren Datensatz und pipen den Datensatz in select und entfernen unser Outcome drymatter. Nun iterieren wir für jede Variable .x im Datensatz mit der Funktion map() und rechnen in jeder Iteration eine gaussian lineare Regression. Dann entferne wir noch den Intercept und sortieren nach den \(p\)-Werten.
R Code [zeigen / verbergen]
chickpea_tbl |>select(-dryweight) |>map(~glm(dryweight ~ .x, data = chickpea_tbl, family = gaussian)) |>map(tidy) |>map(filter, term !="(Intercept)") |>map(select, -term, -std.error, -statistic) |>bind_rows(.id="term") |>arrange(p.value) |>mutate(p.value =pvalue(p.value),estimate =round(estimate, 2))
Würden wir nur nach dem Signifikanzniveau von 5% gehen, dann hätten wir die Variablen sand und location selektiert. Bei der selektion mit dem \(p\)-Wert wird aber eher eine Schwelle von 15.7% vorgeschlagen (Heinze und Dunkler 2017, p. 9). Daher würden wir auch noch no3 und temp mit Selektieren und in unser Modell nehmen.
Es gibt ja immer zwei Wege nach Rom. Deshalb hier auch nochmal die Funktion tbl_uvregression() aus dem R Paket {gtsummary}, die es erlaubt die univariaten Regressionen über alle Variablen laufen zu lassen. Wir kriegen dann auch eine schöne Tabelle 46.3 wieder.
Das R Paket {gtsummary} erlaubt es Ergebnisse der Regression in dem Tutorial: tbl_regression gut darzustellen.
R Code [zeigen / verbergen]
chickpea_tbl |>tbl_uvregression(method = glm,y = dryweight,method.args =list(family = gaussian),pvalue_fun =~style_pvalue(.x, digits =2) ) |>add_global_p() |># add global p-value add_q() |># adjusts global p-values for multiple testingbold_p() |># bold p-values under a given threshold (default 0.05)bold_p(t =0.10, q =TRUE) |># now bold q-values under the threshold of 0.10bold_labels()
Tabelle 46.3— Univariate Regression mit der Funktion tbl_uvregression().
Characteristic
N
Beta
95% CI1
p-value
q-value2
temp
95
1.6
0.41, 2.9
0.009
0.032
rained
95
0.16
0.23
high
—
—
low
4.9
-2.0, 12
location
95
0.31
0.31
north
—
—
northeast
-2.9
-10, 4.3
west
-7.3
-17, 2.1
no3
95
2.5
0.18, 4.8
0.035
0.081
fe
95
2.6
-0.38, 5.6
0.087
0.15
sand
95
2.9
2.5, 3.3
<0.001
<0.001
forest
95
0.19
0.23
<1000m
—
—
>1000m
-4.4
-11, 2.2
1 CI = Confidence Interval
2 False discovery rate correction for multiple testing
Nun führen wir die univariate Regression erneut auf den Ferkeldaten aus. Hier ändern wir nur die family = binomial, da wir hier jetzt eine logistische lineare Regression rechnen müssen. Unser Outcome infected ist ja \(0/1\) codiert. Sonst ändert sich der Code nicht.
R Code [zeigen / verbergen]
pig_tbl |>select(-infected) |>map(~glm(infected ~ .x, data = pig_tbl, family = binomial)) |>map(tidy) |>map(filter, term !="(Intercept)") |>map(select, -term, -std.error, -statistic) |>bind_rows(.id="term") |>arrange(p.value) |>mutate(p.value =pvalue(p.value),estimate =round(estimate, 2))
In diesem Fall reicht die Schwelle von 15.7% nur für zwei Variablen (Heinze und Dunkler 2017, p. 9). Wir erhalten die Variablen crp und bloodpressure für das Modell selektiert.
In der Tabelle 46.4 sehen wir dann nochmal die Anwendung der Funktion tbl_uvregression() auf den Ferkeldatensatz. Ich musste hier die Option pvalue_fun = ~style_pvalue(.x, digits = 2) entfernen, da sonst die Variable crp keinen \(p\)-Wert erhält. Leider sehe ich den \(p\)-Wert mit \(<0.001\) in meiner Ausgabe in R aber wie du siehst, wird die Tabelle auf der Webseite nicht korrekt angezeigt. Das Problem von automatischen Tabellen. Ein Fluch und Segen zugleich. Du musst immer wieder überprüfen, ob die Optionen dann auch für sich und deine Analyse passen.
R Code [zeigen / verbergen]
pig_tbl |>tbl_uvregression(method = glm,y = infected,method.args =list(family = binomial),exponentiate =TRUE ) |>add_global_p() |># add global p-value add_nevent() |># add number of events of the outcomeadd_q() |># adjusts global p-values for multiple testingbold_p() |># bold p-values under a given threshold (default 0.05)bold_p(t =0.10, q =TRUE) |># now bold q-values under the threshold of 0.10bold_labels()
Tabelle 46.4— Univariate Regression mit der Funktion tbl_uvregression().
Characteristic
N
Event N
OR1
95% CI1
p-value
q-value2
age
412
276
1.00
0.95, 1.04
0.9
0.9
sex
412
276
0.2
0.4
female
—
—
male
0.75
0.49, 1.15
location
412
276
0.3
0.5
north
—
—
northeast
1.12
0.61, 2.10
northwest
0.67
0.39, 1.14
west
0.79
0.43, 1.44
activity
412
276
1.02
0.90, 1.16
0.7
0.9
crp
412
276
2.62
2.14, 3.27
<0.001
<0.001
frailty
412
276
0.5
0.7
frail
—
—
pre-frail
1.17
0.59, 2.27
robust
0.88
0.46, 1.64
bloodpressure
412
276
1.08
1.02, 1.15
0.012
0.053
weight
412
276
0.98
0.86, 1.12
0.8
0.9
creatinin
412
276
1.12
0.97, 1.30
0.13
0.4
1 OR = Odds Ratio, CI = Confidence Interval
2 False discovery rate correction for multiple testing
Neben der Berechnung von univariaten logistischen Regressionen ist auch die Darstellung der Daten in einer Tabelle 46.5 bei Medizinern sehr beliebt. Deshalb an dieser Stelle auch die Tabelle 1 (eng. table 1) für die Zusammenfasung der Daten getrennt nach dem Infektionsstatus zusammen mit dem \(p\)-Wert. Ich nutze hier die Funktion tbl_summary() aus dem R Paket {gtsummary}.
R Code [zeigen / verbergen]
pig_tbl |>tbl_summary(by = infected) |>add_p()
Tabelle 46.5— Zusammenfasung der Daten getrennt nach dem Infektionsstatus zusammen mit dem \(p\)-Wert.
Characteristic
0, N = 1361
1, N = 2761
p-value2
age
59.5 (57.0, 63.0)
60.0 (57.0, 63.0)
0.9
sex
0.2
female
47 (35%)
114 (41%)
male
89 (65%)
162 (59%)
location
0.3
north
36 (26%)
85 (31%)
northeast
23 (17%)
61 (22%)
northwest
48 (35%)
76 (28%)
west
29 (21%)
54 (20%)
activity
13.40 (12.25, 14.34)
13.24 (12.28, 14.54)
0.8
crp
19.12 (18.13, 19.83)
20.57 (19.77, 21.46)
<0.001
frailty
0.5
frail
18 (13%)
37 (13%)
pre-frail
42 (31%)
101 (37%)
robust
76 (56%)
138 (50%)
bloodpressure
56.2 (54.3, 58.5)
57.2 (55.1, 59.6)
0.021
weight
18.61 (17.34, 19.41)
18.32 (17.19, 19.60)
0.8
creatinin
4.85 (3.67, 5.93)
4.86 (4.06, 5.85)
0.3
1 Median (IQR); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test
Tja, auch hier ist dann die Frage, wie sortiere ich Variablen. Da es sich bei dem table 1-Stil um eine Übersichtstabelle handelt, ist die Tabelle nach den Variablen sortiert. Auch hier finden wir dann die Variablen crp und bloodpressure wieder. Das Problem hierbei ist natürlich, dass sich die \(p\)-Werte unterscheiden. Das muss ja auch so sein, denn eine logitische Regression ist nun mal kein Wilcoxon rank sum test oder ein Pearson’s Chi-squared test.
Fassen wir als Fazit dieses Abschnitts zusammen wie unsere Modelle nach der Variablenslektion aussehen würde. In unserem Beispiel für die Kichererbsen im sandigen Brandenburg würden wir dann folgendes Modell nehmen.
Unsere infizierten Ferkel würden dann folgendes selektiertes Modell erhalten.
\[
y \sim \beta_0 + crp + bloodpressure
\]
Schauen wir mal, was die anderen Algorithmen noch so finden.
46.3.3 Sonderfall Gaussian linear Regression
Für die gaussian lineare Regression gibt es mit dem R Paket {oslrr} eine große Auswahl an Variable Selection Methods. Einfach mal die Möglichkeiten anschauen, die dort angeboten werden. Wir nutzen jetzt nicht alles was din oslrr möglich ist, sondern nur eien Auswahl. Zuerst müssen wir wieder unser Modell fitten. Wir nehmen alle Variablen mit rein und nutzen die Funktion lm() für ein lineares Modell mit einem normalverteilten Outcome \(y\) mit dryweight.
R Code [zeigen / verbergen]
chickenpea_fit <-lm(dryweight ~ temp + rained + location + no3 + fe + sand + forest, data = chickpea_tbl)
Nun gibt es wirklich viele Lösungen in dem R Paket {oslrr}. Ich möchte einmal die Variante mit ols_step_all_possible präsentieren. In dem Fall rechnen wir alle Modelle die gehen. Und damit meine ich wirklich alle Modelle. Deshalb filtern wir noch nach dem \(R^2_{adj}\) um nicht von dem Angebot erschlagen zu werden. Darüber hinaus möchte ich nur Modelle sehen, die maximal vier Variablen mit in dem Modell haben. Das ist zufällig von mir gewählt… ich will ja ein kleineres Modell haben.
Eine Alternative ist die Funktion ols_step_backward_aic(), die es erlaubt die Selektion anhand dem \(AIC\)-Wert zu machen. Der \(AIC\)-Wert beschreibt die Güte eines Modells und je kleiner der \(AIC\)-Wert ist, desto besser ist das Modell im Vergleich zu anderen Modellen gleicher Art. Da der \(AIC\)-Wert von den Daten abhängt in denen der \(AIC\)-Wert geschätzt wurde, können verschiedene \(AIC\)-Werte nicht übergreifend vergleichen werden.
In Abbildung 46.1 sehen wir einmal den Verlauf der \(AIC\)-Wert durch die Entfernung der jeweiligen Variable. Wenn du auf die y-Achse schaust, ist der Effekt numerisch nicht sehr groß. Davon darf man sich aber nicht beeindrucken lassen., Wir erhalten ein besseres Modell, wenn wir Variablen entfernen. Darüber hinaus sehen wir auch eine Sättigung.
R Code [zeigen / verbergen]
plot(chick_step_aic)
Gut, und wie sieht nun unser finales Modell aus? Dafür müssen wir usn aus dem Objekt chick_step_aic das model raus ziehen. Dafür nutzen wir die Funktion pluck(). Dann noch die Ausgabe in die Funktion model_parameters() gepipt und schon haben wir das finale Modell nach \(AIC\)-Werten in einer gaussian linearen Regression.
Spannenderweise ist location nicht mehr im finalen Modell plus die Variable location flog auch sehr früh raus. Das passt auch besser zu den Daten. Ich hatte die Daten so gebaut, dass der Ort eigentlich keinen Effekt haben sollte. Wir sehen, dass je nach Verfahren was anderes herauskommt. Aber Achtung, das schrittweise Verfahren ist der Auswahl nach \(p\)-Werten auf jeden Fall vorzuziehen!
46.3.4 Schrittweise mit stepAIC
Was das R Paket {oslrr} für die gaussian linear Regression kann, kann das R Paket {MASS} mit der Funktion stepAIC für den Rest der möglichen Verteilungen. Da wir mit dem Fekerldatensatz ein binominales Outcome \(y\) mit infected vorliegen haben nutzen wir un die Funktion stepAIC(). Wir hätten auch den Kichererbsendatensatz mit der Funktion bearbeiten können, aber im Falle der Normalverteilung stehen uns dann eben noch andere Algorithmen zu Verfügung. Wie immer müssen wir zuerst das volle Modell mit der Funktion glm() fitten. Wir geben noch die Verteilungsfamilie mit family = binomial noch mit an und definieren so eine logistische lineare Regression.
R Code [zeigen / verbergen]
fit <-glm(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_tbl, family = binomial)
Nachdem wir das Modell gefittet haben, können wir das Modell direkt in die Funktion stepAIC stecken. Wir nutzen noch die Option direction = "backward" um eine Rückwärtsselektion durchzuführen.
Jetzt ist die Selektion durchgelaufen und wir sehen in jeden Schritt welche Variable jeweils entfernt wurde und wie sich dann der \(AIC\)-Wert ändert. Wir starten mit einem \(AIC = 425.65\) und enden bei einem \(AIC=415.01\). Schauen wir uns nochmal das finale Modell an.
Hier erscheint jetzt noch die Variable sex mit in der Auswahl. Das hat natürlich auch weitreichende Auswirkungen! Es macht schon einen gewaltigen Unterschied, ob wir annehmen das, dass Geschelcht der Ferkel keinen Einfluss auf die Infektion hat oder eben doch. Wir sehen auch hier, dass wir Aufpassen müssen wenn wir eine Variablenselektion durchführen. Aber Achtung, das schrittweise Verfahren ist der Auswahl nach \(p\)-Werten auf jeden Fall vorzuziehen!
46.3.5 Feature Selektion mit ranger
In diesem Abschnitt wollen wir die Variablenselektion mit einem maschinellen Lernverfahren durchführen. Im Bereich des maschinellen Lernens heist die Variablenselektion dann aber Feature Selektion. Wir versuchen jetzt die Selektion auf den Orginaldaten durchzuführen. Eigentlich wird empfohlen die Daten vorher zu normalisieren und dann mit den maschinellen Lernverfahren zu nutzen.
Standardisieren oder Normalisieren von Daten
Eine Herausforderung für maschinelle Lernverfahren sind nicht normalisierte Daten. Das heist, dass wir Variablen haben, die kategorial oder kontinuierlich sein können oder aber sehr unterschiedlich von den Einheiten sind. Deshalb wird empfohlen die Daten vorher zu Standardisieren oder zu Normalisieren. In dem Kapitel 19 kannst du mehr über das Transformieren von Daten nachlesen.
Wir nutzen als erstes einen Random Forest Algorithmus wie er in Kapitel 74 beschrieben ist. Es bietet sich hier die Implementation im R Paket {ranger} an. Bevor wir aber einen Random Forest auf unsere Daten laufen lassen, Standardisieren wir unsere Daten nochmal. Überall wo wir einen numerischen Wert als Variableneintrag haben rechnen wir eine \(z\)-Transformation. Wir erhalten dann die standardisierten Daten zurück.
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `across(where(is.numeric), dlookr::transform, "zscore")`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.
# Previously
across(a:b, mean, na.rm = TRUE)
# Now
across(a:b, \(x) mean(x, na.rm = TRUE))
R Code [zeigen / verbergen]
pig_norm_tbl
# A tibble: 412 × 10
age sex location activity crp frailty bloodpressure
<transfrm> <fct> <fct> <transfrm> <transfrm> <fct> <transfrm>
1 0.2156014 male northeast 1.23906607 1.6154076 robust 1.6152124
2 -1.5521155 male northwest -0.15495130 -0.9942285 robust -0.7895759
3 1.3204244 female northeast -1.18531196 -0.9104969 robust 0.3274677
4 -0.2263278 female north 0.03899895 -0.4848611 robust -0.2085934
5 0.6575306 male northwest 0.87540937 1.0502190 robust -0.4391895
6 -1.1101862 male northwest 1.54211333 0.9664873 robust 1.0312351
7 -2.4359739 male west 0.32386336 -0.7569889 pre-frail -0.6488224
8 -1.5521155 male northwest 0.13597407 -0.7569889 robust -0.4122367
9 -0.4472924 female west -0.87014282 1.2735034 robust 0.6838436
10 -0.6682570 male northwest 0.46326510 0.6245832 robust -0.3343731
# ℹ 402 more rows
# ℹ 3 more variables: weight <transfrm>, creatinin <transfrm>,
# infected <transfrm>
Den Random Forest rechnen wir mit der Funktion ranger(). Dafür müssen wir wieder unser vollständiges Modell definieren und können dann die Funktion starten. Damit wir eine Variablenwichtigkeit (eng. variable importance) wiederbekommen, müssen wir noch die Option importance = "permutation" verwenden.
crp creatinin bloodpressure location frailty
0.097 0.007 0.006 0.002 0.001
sex age activity weight
0.000 -0.001 -0.001 -0.002
Wir sehen, dass wir als wichtigste Variable wiederum crp zurückbekommen. Danach wird es schon etwas schwieriger, da die Werte sehr schnell kleiner werden und auch ein Art Plateau bilden. Daher würde man hier nur annehmen, dass crp bedeutend für das Modell ist. Es kann aber auch sein, dass hier eine kontinuierliche Variable sehr vom Algorithmus bevorzugt wurde. Daher schauen wir uns die Sachlage einmal mit den standardisierten Daten an.
crp creatinin bloodpressure location age
0.415 0.020 0.015 0.005 -0.001
frailty sex weight activity
-0.002 -0.005 -0.011 -0.013
Auch hier erhalten wir ein ähnliches Bild. Audf jeden Fall ist crp bedeutend für den Infektionsstatus. Danach werden die Werte etwas zufällig. Wir können Werte für die variable importance nicht unter Datensätzen vergleichen. Jeder Datensatz hat seine eigene variable importance, die von den Werten in dem Datensatz abhängt.
Wir ziehen als Fazit, dass wir nur crp als bedeutenden Wert für die Klassifikation des Infektionsstatus ansehen würden. Hier stehen wir wirklich etwas wie das Schwein vor dem Uhrwerk, denn was nun richtiger ist, stepAIC oder ranger lässt sich so einfach nicht bewerten. Zum einen wollen wir ja eigentlich mit Random Forest eine Klassifikation durchführen und mit linearen Regressionsmodellen eher kausale Modelle schätzen. Am Ende musst du selber abschätzen, was in das finale Modell soll. Ich kann ja auch den Threshold für den Variablenausschluss selber wählen. Wähle ich einen Threshold von \(0.008\), dann hätte ich crp, weight, bloodpressure und sex mit in dem finalen Modell.
46.3.6 Feature Selektion mit boruta
Mit dem Boruta Algorithmus steht uns noch eine andere Implemnetierung des Random Forest Algorithmus zu verfügung um Feature Selektion zu betreiben. Wir nutzen wieder den Boruta Algorithmus in seine einfachen Form und gehen nicht tiefer auf alle Optionen ein. Wir nehmen wieder als Beispiel den Datensatz zu den infizierten Ferkeln und nutzen in diesem Fall auch nur die rohen Daten. Über eine Standardisierung könnte man wiederum nachdenken.
Eine Herausforderung für maschinelle Lernverfahren sind nicht normalisierte Daten. Das heist, dass wir Variablen haben, die kategorial oder kontinuierlich sein können oder aber sehr unterschiedlich von den Einheiten sind. Deshalb wird empfohlen die Daten vorher zu Standardisieren oder zu Normalisieren. In dem Kapitel 19 kannst du mehr über das Transformieren von Daten nachlesen.
Um die Funktion Boruta() zu nutzen brauchen wir wieder das Modell und den Datensatz. Sonst ist erstmal nichts weiter anzugeben. Die Funktion läuft dann durch und gibt auch gleich den gewollten Informationshappen. Wichtig ist hierbei, dass wir natürlich noch andere Optionen mit angeben können. Wir können die Anzahl an Iterationen erhöhen und andere Tuning Parameter eingeben. Hier muss man immer schauen was am besten passt. Da würde ich auch immer rumprobieren und auf der Hilfeseite der Funktion ?Boruta einmal nachlesen.
R Code [zeigen / verbergen]
set.seed(20221031)boruta_output <-Boruta(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_tbl) boruta_output
Boruta performed 99 iterations in 2.946144 secs.
1 attributes confirmed important: crp;
6 attributes confirmed unimportant: activity, age, frailty, location,
sex and 1 more;
2 tentative attributes left: bloodpressure, creatinin;
Manchmal ist es super praktisch, wenn eine Funktion einem die Antwort auf die Frage welche Variable bedeutend ist, gleich liefert. Wir erhalten die Information, dass die Variable crp als bedeutsam angesehen wird. Wir können uns den Zusammenhang auch in der Abbildung 46.2 auch einmal anschauen. Die grünen Variablen sind die bedeutenden Variablen.
Am Ende finden wir auch hier die Variable crp als einziges als bedeutend wieder. Wenn wir noch Variablen haben die verdächtig oder vorläufig bedeutend sind, angezeigt durch 2 tentative attributes left: bloodpressure, sex, dann können wir noch die Funktion TentativeRoughFix() nutzen. Die Funktion TentativeRoughFix() rechnet die Variablen nochmal nach und versucht alle Variablen in bedeutend oder nicht bedeutend zu klassifizieren. Wir haben ja zwei tentative Variablen in unseren Fall vorliegen, also nutzen wir noch kurz die Funktion um uns auch hier Klarheit zu schaffen.
R Code [zeigen / verbergen]
TentativeRoughFix(boruta_output)
Boruta performed 99 iterations in 2.946144 secs.
Tentatives roughfixed over the last 99 iterations.
2 attributes confirmed important: creatinin, crp;
7 attributes confirmed unimportant: activity, age, bloodpressure,
frailty, location and 2 more;
Am Ende ist die klare Aussage einer Funktion auch immer ein zweischneidiges Schwert. Wir verlieren jetzt noch die beiden tentative Variablen. Wo wir bei ranger die Qual der Wahl haben, werden wir bei Boruta eher vor vollendete Tatsachen gestellt. Meistens neigt man nach einer Boruta-Analyse nicht dazu noch eine zusätzliche Variable mit ins Modell zu nehmen. Dafür ist dann die Ausgabe zu bestimmt, obwohl die Entscheidung am Ende auch genau so unsicher ist wie von ranger und den anderen Modellen.
Referenzen
Abbildung 46.1— Visualisierung der ols_step_backward_aic mit der Reduktion des AIC-Wertes.Abbildung 46.2— Visualisierung der Boruta Ausgabe.
Heinze G, Dunkler D. 2017. Five myths about variable selection. Transplant International 30: 6–10.
Heinze G, Wallisch C, Dunkler D. 2018. Variable selection–a review and recommendations for the practicing statistician. Biometrical journal 60: 431–449.
Talbot D, Massamba VK. 2019. A descriptive review of variable selection methods in four epidemiologic journals: there is still room for improvement. European journal of epidemiology 34: 725–730.
```{r echo = FALSE}pacman::p_load(tidyverse, readxl, knitr, kableExtra)```# Variablenselektion {#sec-variable-selection}*Letzte Änderung am `r format(fs::file_info("stat-modeling-variable-selection.qmd")$modification_time, '%d. %B %Y um %H:%M:%S')`*> *"Denn im Formulieren des Problems ist die Lösung schon enthalten!" --- Käptn Peng & Die Tentakel von Delphi, Meister und Idiot*Die Selektion von Variablen in einem Modell. Ein schwieriges Thema. Entweder kenne ich mein Experiment und habe das Experiment so geplant, dass nur die bedeutenden Variablen mit in dem Experiment sind oder ich habe keine Ahnung. Gut, dass ist überspitzt und gemein formuliert. Wir wollen uns in diesem Kapitel den Fall anschauen, dass du sehr viele Variablen $x$ erhoben hast und nun *statistisch* bestimmen willst, welche Variablen nun mit in das finale Modell sollen. Achtung, ich spreche hier nicht von einem Blockdesign oder aber einem Feldexperiment. Da hat die Variablenselektion nichts zu suchen. Daher tritt der Fall der Variablenselektion eher in dem Feld Verhaltensbiologie oder aber auch Ökologie auf. Ebenfalls kann die Anwendung in automatisch erfassten Daten einen Sinn machen. Wir nutzen dann die Variablenselektion (eng. *feature selection*) zu Dimensionsreduktion des Datensatzes. Der Datensatz soll damit einfacher sein... ob der Datensatz das damit auch wird, ist wieder eine andere Frage.::: {layout="[15,85]" layout-valign="top"}![](images/angel_01.png){fig-align="center" width="100%"}> In diesem Kapitel prügeln wir aber einen statistischen Engel. Wir werden hier mal schauen müssen, was alles geht und was nicht. Variablen Selektion ist faktisch nicht *ein* Kapitel sondern ein Regal(kilo)meter voll mit Büchern.:::Zu der Frage welches Verfahren denn nun das richtige Verfahren zur Selektion von Variablen ist, gibt es die Standardantwort in der Statistik: *Es kommt auf die Fragestellung an...*. Oder aber was ist wie gut implementiert, dass wir das Verfahren einigermaßen gut nutzen können. Wir gehen daher von einfach zu kompliziert und du musst dann schauen, was du nutzen willst und kannst. Wir müssen zum Beispiel unterscheiden, welcher Verteilung das Outcome $y$ folgt. Wenn wir ein normalverteiltes $y$ haben, dann haben wir andere Möglichkeiten, als wenn wir uns ein poissonverteiltes oder binominalverteiltes $y$ anschauen.::: column-marginDas [R Paket `{olsrr}`](https://olsrr.rsquaredacademy.com/articles/variable_selection.html) erlaubt eine weitreichende Variablen Selektion, wenn ein normalverteiltes Outcome $y$ vorliegt.:::Im Folgenden will ich *kurz* die fünf Mythen der Variablenselektion von @heinze2017five zusammenfassen. Wie immer ersetzt meine deutsche Zusammenfassung und Auswahl nicht das eigenständige Lesen der *englischen* Orgnialquelle, wenn du die Informationen in deiner Abschlussarbeit zitieren willst.1) **Die Anzahl der Variablen in einem Modell sollte reduziert werden, bis es 10 Ereignisse pro Variable gibt.** Simulationsstudien haben gezeigt, dass multivariable Modelle bei zu niedrigen Verhältnissen von Ereignissen bzw. Beobachtungen pro Variable (eng *events per variable*, abk. *EPV*) sehr instabil werden. Aktuelle Empfehlungen besagen, dass je nach Kontext mindestens 5-15 EPV verfügbar sein sollten. Wahrscheinlich sind sogar viel höhere Werte wie 50 EPV erforderlich, um annähernd stabile Ergebnisse zu erzielen.2) **Nur Variablen mit nachgewiesener Signifikanz des univariaten Modells sollten in ein Modell aufgenommen werden.** Die univariable Vorfilterung trägt nicht zur Stabilität des Auswahlprozesses bei, da sie auf stochastischen Quanten beruht und dazu führen kann, dass wichtige Anpassungsvariablen übersehen werden.3) **Nicht signifikante Effekte sollten aus einem Modell entfernt werden.** Regressionskoeffizienten hängen im Allgemeinen davon ab, welche anderen Variablen in einem Modell enthalten sind, und ändern folglich ihren Wert, wenn eine der anderen Variablen in einem Modell weggelassen wird.4) **Der berichtete P-Wert quantifiziert den Typ-I-Fehler einer fälschlich ausgewählten Variablen.** Ein P-Wert ist ein Ergebnis der Datenerhebung und -analyse und quantifiziert die Plausibilität der beobachteten Daten unter der Nullhypothese. Daher quantifiziert der P-Wert nicht den Fehler vom Typ I. Es besteht auch die Gefahr einer falschen Eliminierung von Variablen, deren Auswirkungen durch die bloße Angabe des endgültigen Modells eines Variablenauswahlverfahrens überhaupt nicht quantifiziert werden können.5) **Variablenauswahl vereinfacht die Analyse.** Für das jeweilige Problem muss eine geeignete Variablenauswahlmethode gewählt werden. Statistiker haben die Rückwärtselimination als die zuverlässigste unter den Methoden empfohlen, die sich mit Standardsoftware leicht durchführen lassen. Eine Auswahl ist eine "harte Entscheidung", die aber oft auf vagen Größen beruht. Untersuchungen zur Modellstabilität sollten jede angewandte Variablenauswahl begleiten, um die Entscheidung für das schließlich berichtete Modell zu rechtfertigen oder zumindest die mit der Auswahl der Variablen verbundene Unsicherheit zu quantifizieren.Im Weiteren sei auch noch auf @heinze2018variable und @talbot2019descriptive verwiesen. Beide Veröffentlichungen liefern nochmal einen fundierten Block auf die Variablenselektion. Wiederum ist das natürlich nur ein winziger Ausschnitt aus der Literatur zur Variablenselektion. Im Zweifel einfach einmal bei [Google Scholar](https://scholar.google.com/scholar?hl=de&as_sdt=0%2C5&q=variable+selection+review&btnG=) nach Variablenselektion suchen und schauen was so in dem Feld der eigenen Forschung alles gemacht wird.::: callout-caution## Sensitivitätsanalysen nach der Variablenselektion*"Variable selection should always be accompanied by sensitivity analyses to avoid wrong conclusions."* [@heinze2017five, p. 9]Nachdem wir Variablen aus unseren Daten entfernt haben, ist es üblich noch eine Sensitivitätsanalysen durchzuführen. Wir Vergleich dann das selektierte Modell mit *anderen* Modellen. Oder wir wollen die Frage beantworten, was hat eigentlich meine Variablenselektion am Ergebnis geändert? Habe ich eine wichtige Variable rausgeschmissen? Das machen wir dann gesammelt in dem @sec-sensitivity zu den Sensitivitätsanalysen.:::## Genutzte R PaketeWir wollen folgende R Pakete in diesem Kapitel nutzen.```{r echo = TRUE}#| message: falsepacman::p_load(tidyverse, magrittr, dlookr, MASS, ranger, Boruta, broom, scales, olsrr, gtsummary, parameters, conflicted)conflicts_prefer(dplyr::select)conflicts_prefer(dplyr::filter)```An der Seite des Kapitels findest du den Link *Quellcode anzeigen*, über den du Zugang zum gesamten R-Code dieses Kapitels erhältst.## DatenUm die Variablenselektion einmal durchzuführen nurtzen wir zwei Datensätze. Zum einen den Datensatz zu den Kichererbsen in Brandenburg mit einem normalverteilten Outcome $y$ mit `dryweight`. Wir laden wieder den Datensatz in R und schauen uns einmal die Daten in @tbl-chickpea-var als Auszug aus dem Tabellenblatt an.[Wir du schon siehst, wir brauchen Fallzahl um hier überhaupt was zu machen. Bitte keine Variablenselektion im niedrigen zweistelligen Bereich an Beobachtungen.]{.aside}```{r}#| message: false#| warning: falsechickpea_tbl <-read_excel("data/chickpeas.xlsx") ```Wir sehen, dass wir sehr viele Variablen vorleigen haben. Sind denn jetzt alle Variablen notwendig? Oder können auch ein paar Variablen raus aus dem Modell. So viele Beobachtungen haben wir mit $n = 95$ ja nicht vorliegen. Daher wollen wir an diesem Datensatz die Variablenselektion unter der Annahme eines normalverteilten $y$ durchgehen.```{r}#| echo: false#| message: false#| warning: false#| label: tbl-chickpea-var#| tbl-cap: Auszug aus dem Daten zu den Kichererbsen in Brandenburg.#| column: pagerbind(head(chickpea_tbl, 3),rep("...", times =ncol(chickpea_tbl)),tail(chickpea_tbl, 3)) |>kable(align ="c", "pipe")```Was wir auch noch wissen, ist wie die Effekte in den Daten *wirklich* sind. Die Daten wurden ja künstlich erstellt, deshalb hier die Ordnung der Effektstärke für jede Variable. Im Prinzip müsste diese Reihenfolge auch bei der Variablenselektion rauskommen. Schauen wir mal, was wir erhalten.$$y \sim \beta_0 + 3 * sand + 2 * temp + 1.5 * rained - 1.2 * forest + 1.1 * no3 $$Viele Beispiele laufen immer unter der Annahme der Normalverteilung. Deshalb als zweites Beispiel nochmal die Daten von den infizierten Ferkeln mit einem binomialverteilten Outcome $y$ mit `infected`. Auch hier können wir uns den Auszug der Daten in @tbl-pigs-var anschauen.```{r}#| message: false#| warning: falsepig_tbl <-read_excel("data/infected_pigs.xlsx") ```Das schöne an diesem Datensatz ist jetzt, dass wir mit $n = 412$ Beobachtungen sehr viele Daten vorliegen haben. Daher können wir auch alle Methoden gut verwenden und haben nicht das Problem einer zu geringen Fallzahl.```{r}#| echo: false#| message: false#| warning: false#| label: tbl-pigs-var#| tbl-cap: Auszug aus dem Daten zu den kranken Ferkeln.#| column: pagerbind(head(pig_tbl, 3),rep("...", times =ncol(pig_tbl)),tail(pig_tbl, 3)) |>kable(align ="c", "pipe")```Auch in diesem Beispiel wurden die Daten von mir mit folgenden Effekten generiert. Schauen wir mal, was die Variablenselektion bei der hohen Fallzahl mit den Variablen macht bzw. welche Sortierung am Ende rauskommt.$$y \sim \beta_0 + 2 * crp + 0.5 * sex + 0.5 * frailty + 0.2 * bloodpressure + 0.05 * creatinin + 0.01 * weight$$Damit haben wir unsere beiden Beispiel und jetzt gehen wir mal eine Auswahl an Methoden zur Variablenselektion durch. Besonders hier, haltet den statistsichen Engel nah bei euch. Es wird leider etwas ruppig für den statistischen Engel.## Methoden der VariablenselektionIn den folgenden Abschnitten wollen wir uns eine Reihe an Methoden anschauen um eine Variablenselektion durchzuführen. Dabei gehen wir von einfach nach komplex. Wobei das komplex eher die Methode und nicht die Anwendung meint. Wir nutzen R Pakete und gehen nicht sehr ins Detail *wie* der Algorithmus jetzt die Auswahl trifft. Für den Hintergrund sind dann die Verweise auf die anderen Kapitel.### Per HandManchmal ist der Anfang auch das Ende. Wir müssen ja gar keinen Algorithmus auf unsere Daten loslassen um eine Variablenselektion durchzuführen. Deshalb möchte ich gleich den ersten Abschnitt mit einem Zitat von @heinze2017five beginnen.*"Oft gibt es keinen wissenschaftlichen Grund, eine (algorithmische) Variablenauswahl durchzuführen. Insbesondere erfordern Methoden der (algorithmische) Variablenselektion einen viel größeren Stichprobenumfang als die Schätzung eines multiplen Modells mit einem festen Satz von Prädiktoren auf der Grundlage (klinischer) Erfahrung."* [Übersetzt und ergänzt nach @heinze2017five, p. 9]Fazit dieses kurzen Abschnitts. Wir können auf alles Folgende einfach verzichten und uns überlegen welche Variablen *sinnvollerweise* mit ins Modell sollen und das mit unserem Expertenwissen begründen. Gut, und was ist, wenn ich kein Experte bin? Oder wir aber *wirklich* Neuland betreten? Dann können wir eine Reihe anderer Verfahren nutzen um uns algortimisch einer Wahrheit anzunähern.### Univariate VorselektionUnd weiter geht es mit Zitaten aus @heinze2017five zu der Variablenselektion. Dazu musst du wissen, dass die univariate Vorselektion sehr beliebt war und auch noch ist. Denn die univariate Vorselektion ist einfach durchzuführen und eben auch gut darzustellen.*"Obwohl die univariable Vorfilterung nachvollziehbar und mit Standardsoftware leicht durchführbar ist, sollte man sie besser ganz vergessen, da sie für die Erstellung multivariabler Modelle weder Voraussetzung noch von Nutzen ist."* [Übersetzt nach @heinze2017five, p. 8][Ich sage immer, auch mit einem Hammer kann man Scheiben putzen. Halt nur einmal... Deshalb auch hier die univariate Variante der Vorselektion.]{.aside}Wir sehen also, eigentlich ist die univariate Variablensleketion nicht so das gelbe vom Ei, aber vielleicht musst die Variablenselektion durchführen, so dass her die Lösung in R einmal dargestellt ist. Wir nutzen einmal die gaussian lineare Regression für den Kichererbsendatensatz. Es ist eine ganze Reihe an Code, das hat aber eher damit zu tun, dass wir die Modellausgabe noch filtern und anpassen wollen. Die eigentliche Idee ist simple. Wir nehmen unseren Datensatz und pipen den Datensatz in select und entfernen unser Outcome `drymatter`. Nun iterieren wir für *jede* Variable `.x` im Datensatz mit der Funktion `map()` und rechnen in jeder Iteration eine gaussian lineare Regression. Dann entferne wir noch den Intercept und sortieren nach den $p$-Werten.```{r}chickpea_tbl |>select(-dryweight) |>map(~glm(dryweight ~ .x, data = chickpea_tbl, family = gaussian)) |>map(tidy) |>map(filter, term !="(Intercept)") |>map(select, -term, -std.error, -statistic) |>bind_rows(.id="term") |>arrange(p.value) |>mutate(p.value =pvalue(p.value),estimate =round(estimate, 2))```Würden wir nur nach dem Signifikanzniveau von 5% gehen, dann hätten wir die Variablen `sand` und `location` selektiert. Bei der selektion mit dem $p$-Wert wird aber eher eine Schwelle von 15.7% vorgeschlagen [@heinze2017five, p. 9]. Daher würden wir auch noch `no3` und `temp` mit Selektieren und in unser Modell nehmen.Es gibt ja immer zwei Wege nach Rom. Deshalb hier auch nochmal die Funktion `tbl_uvregression()` aus dem R Paket `{gtsummary}`, die es erlaubt die univariaten Regressionen über alle Variablen laufen zu lassen. Wir kriegen dann auch eine schöne @tbl-chick-gt wieder.::: column-marginDas R Paket `{gtsummary}` erlaubt es Ergebnisse der Regression in dem [Tutorial: tbl_regression](https://www.danieldsjoberg.com/gtsummary/articles/tbl_regression.html) gut darzustellen.:::```{r}#| echo: true#| message: false#| warning: false#| label: tbl-chick-gt#| tbl-cap: "Univariate Regression mit der Funktion `tbl_uvregression()`."chickpea_tbl |>tbl_uvregression(method = glm,y = dryweight,method.args =list(family = gaussian),pvalue_fun =~style_pvalue(.x, digits =2) ) |>add_global_p() |># add global p-value add_q() |># adjusts global p-values for multiple testingbold_p() |># bold p-values under a given threshold (default 0.05)bold_p(t =0.10, q =TRUE) |># now bold q-values under the threshold of 0.10bold_labels()```Nun führen wir die univariate Regression erneut auf den Ferkeldaten aus. Hier ändern wir nur die `family = binomial`, da wir hier jetzt eine logistische lineare Regression rechnen müssen. Unser Outcome `infected` ist ja $0/1$ codiert. Sonst ändert sich der Code nicht.```{r}pig_tbl |>select(-infected) |>map(~glm(infected ~ .x, data = pig_tbl, family = binomial)) |>map(tidy) |>map(filter, term !="(Intercept)") |>map(select, -term, -std.error, -statistic) |>bind_rows(.id="term") |>arrange(p.value) |>mutate(p.value =pvalue(p.value),estimate =round(estimate, 2))```In diesem Fall reicht die Schwelle von 15.7% nur für zwei Variablen [@heinze2017five, p. 9]. Wir erhalten die Variablen `crp` und `bloodpressure` für das Modell selektiert.In der @tbl-pig-gt sehen wir dann nochmal die Anwendung der Funktion `tbl_uvregression()` auf den Ferkeldatensatz. Ich musste hier die Option `pvalue_fun = ~style_pvalue(.x, digits = 2)` entfernen, da sonst die Variable `crp` keinen $p$-Wert erhält. Leider sehe ich den $p$-Wert mit $<0.001$ in meiner Ausgabe in R aber wie du siehst, wird die Tabelle auf der Webseite nicht korrekt angezeigt. Das Problem von automatischen Tabellen. Ein Fluch und Segen zugleich. Du musst immer wieder überprüfen, ob die Optionen dann auch für sich und deine Analyse passen.```{r}#| echo: true#| message: false#| warning: false#| label: tbl-pig-gt#| tbl-cap: "Univariate Regression mit der Funktion `tbl_uvregression()`."pig_tbl |>tbl_uvregression(method = glm,y = infected,method.args =list(family = binomial),exponentiate =TRUE ) |>add_global_p() |># add global p-value add_nevent() |># add number of events of the outcomeadd_q() |># adjusts global p-values for multiple testingbold_p() |># bold p-values under a given threshold (default 0.05)bold_p(t =0.10, q =TRUE) |># now bold q-values under the threshold of 0.10bold_labels()```Neben der Berechnung von univariaten logistischen Regressionen ist auch die Darstellung der Daten in einer @tbl-pigs-table1 bei Medizinern sehr beliebt. Deshalb an dieser Stelle auch die Tabelle 1 (eng. *table 1*) für die Zusammenfasung der Daten getrennt nach dem Infektionsstatus zusammen mit dem $p$-Wert. Ich nutze hier die Funktion `tbl_summary()` aus dem R Paket `{gtsummary}`.```{r}#| echo: true#| message: false#| warning: false#| label: tbl-pigs-table1#| tbl-cap: Zusammenfasung der Daten getrennt nach dem Infektionsstatus zusammen mit dem $p$-Wert.pig_tbl |>tbl_summary(by = infected) |>add_p()```Tja, auch hier ist dann die Frage, wie sortiere ich Variablen. Da es sich bei dem *table 1*-Stil um eine Übersichtstabelle handelt, ist die Tabelle nach den Variablen sortiert. Auch hier finden wir dann die Variablen `crp` und `bloodpressure` wieder. Das Problem hierbei ist natürlich, dass sich die $p$-Werte unterscheiden. Das muss ja auch so sein, denn eine logitische Regression ist nun mal kein *Wilcoxon rank sum test* oder ein *Pearson's Chi-squared test*.Fassen wir als Fazit dieses Abschnitts zusammen wie unsere Modelle nach der Variablenslektion aussehen würde. In unserem Beispiel für die Kichererbsen im sandigen Brandenburg würden wir dann folgendes Modell nehmen.$$y \sim \beta_0 + sand + location + no3 + temp$$Unsere infizierten Ferkel würden dann folgendes selektiertes Modell erhalten.$$y \sim \beta_0 + crp + bloodpressure$$Schauen wir mal, was die anderen Algorithmen noch so finden.### Sonderfall Gaussian linear RegressionFür die gaussian lineare Regression gibt es mit dem R Paket `{oslrr}` eine große Auswahl an [Variable Selection Methods](https://olsrr.rsquaredacademy.com/articles/variable_selection.html). Einfach mal die Möglichkeiten anschauen, die dort angeboten werden. Wir nutzen jetzt nicht alles was din `oslrr` möglich ist, sondern nur eien Auswahl. Zuerst müssen wir wieder unser Modell fitten. Wir nehmen alle Variablen mit rein und nutzen die Funktion `lm()` für ein lineares Modell mit einem normalverteilten Outcome $y$ mit `dryweight`.```{r}chickenpea_fit <-lm(dryweight ~ temp + rained + location + no3 + fe + sand + forest, data = chickpea_tbl)```Nun gibt es wirklich viele Lösungen in dem R Paket `{oslrr}`. Ich möchte einmal die Variante mit `ols_step_all_possible` präsentieren. In dem Fall rechnen wir alle Modelle die gehen. Und damit meine ich wirklich alle Modelle. Deshalb filtern wir noch nach dem $R^2_{adj}$ um nicht von dem Angebot erschlagen zu werden. Darüber hinaus möchte ich nur Modelle sehen, die maximal vier Variablen mit in dem Modell haben. Das ist zufällig von mir gewählt... ich will ja ein kleineres Modell haben.```{r}ols_step_all_possible(chickenpea_fit)$result |>as_tibble() |>arrange(desc(adjr)) |>filter(n <=4) |>select(predictors, adjr, aic) ```Eine Alternative ist die Funktion `ols_step_backward_aic()`, die es erlaubt die Selektion anhand dem $AIC$-Wert zu machen. Der $AIC$-Wert beschreibt die Güte eines Modells und je kleiner der $AIC$-Wert ist, desto besser ist das Modell im Vergleich zu anderen Modellen gleicher Art. Da der $AIC$-Wert von den Daten abhängt in denen der $AIC$-Wert geschätzt wurde, können verschiedene $AIC$-Werte nicht übergreifend vergleichen werden.```{r}chick_step_aic <-ols_step_backward_aic(chickenpea_fit)```In @fig-ols-diag sehen wir einmal den Verlauf der $AIC$-Wert durch die Entfernung der jeweiligen Variable. Wenn du auf die y-Achse schaust, ist der Effekt numerisch nicht sehr groß. Davon darf man sich aber nicht beeindrucken lassen., Wir erhalten ein besseres Modell, wenn wir Variablen entfernen. Darüber hinaus sehen wir auch eine Sättigung.```{r}#| echo: true#| message: false#| label: fig-ols-diag#| fig-align: center#| fig-height: 3#| fig-width: 5#| fig-cap: "Visualisierung der `ols_step_backward_aic` mit der Reduktion des AIC-Wertes."plot(chick_step_aic) ```Gut, und wie sieht nun unser finales Modell aus? Dafür müssen wir usn aus dem Objekt `chick_step_aic` das `model` raus ziehen. Dafür nutzen wir die Funktion `pluck()`. Dann noch die Ausgabe in die Funktion `model_parameters()` gepipt und schon haben wir das finale Modell nach $AIC$-Werten in einer gaussian linearen Regression.```{r}#| message: false#| warning: falsepluck(chick_step_aic, "model") |>model_parameters()```Spannenderweise ist `location` nicht mehr im finalen Modell plus die Variable `location` flog auch sehr früh raus. Das passt auch besser zu den Daten. Ich hatte die Daten so gebaut, dass der Ort eigentlich keinen Effekt haben sollte. Wir sehen, dass je nach Verfahren was anderes herauskommt. Aber Achtung, das schrittweise Verfahren ist der Auswahl nach $p$-Werten auf jeden Fall vorzuziehen!### Schrittweise mit `stepAIC`Was das R Paket `{oslrr}` für die gaussian linear Regression kann, kann das R Paket `{MASS}` mit der Funktion `stepAIC` für den Rest der möglichen Verteilungen. Da wir mit dem Fekerldatensatz ein binominales Outcome $y$ mit `infected` vorliegen haben nutzen wir un die Funktion `stepAIC()`. Wir hätten auch den Kichererbsendatensatz mit der Funktion bearbeiten können, aber im Falle der Normalverteilung stehen uns dann eben noch andere Algorithmen zu Verfügung. Wie immer müssen wir zuerst das volle Modell mit der Funktion `glm()` fitten. Wir geben noch die Verteilungsfamilie mit `family = binomial` noch mit an und definieren so eine logistische lineare Regression.```{r}#| echo: true#| message: false#| eval: true#| warning: falsefit <-glm(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_tbl, family = binomial)```Nachdem wir das Modell gefittet haben, können wir das Modell direkt in die Funktion `stepAIC` stecken. Wir nutzen noch die Option `direction = "backward"` um eine Rückwärtsselektion durchzuführen.```{r}fit_step <-stepAIC(fit, direction ="backward")```Jetzt ist die Selektion durchgelaufen und wir sehen in jeden Schritt welche Variable jeweils entfernt wurde und wie sich dann der $AIC$-Wert ändert. Wir starten mit einem $AIC = 425.65$ und enden bei einem $AIC=415.01$. Schauen wir uns nochmal das finale Modell an.```{r}#| message: false#| warning: falsefit_step |>model_parameters()```Hier erscheint jetzt noch die Variable `sex` mit in der Auswahl. Das hat natürlich auch weitreichende Auswirkungen! Es macht schon einen gewaltigen Unterschied, ob wir annehmen das, dass Geschelcht der Ferkel keinen Einfluss auf die Infektion hat oder eben doch. Wir sehen auch hier, dass wir Aufpassen müssen wenn wir eine Variablenselektion durchführen. Aber Achtung, das schrittweise Verfahren ist der Auswahl nach $p$-Werten auf jeden Fall vorzuziehen!### Feature Selektion mit `ranger`In diesem Abschnitt wollen wir die Variablenselektion mit einem maschinellen Lernverfahren durchführen. Im Bereich des maschinellen Lernens heist die Variablenselektion dann aber *Feature Selektion*. Wir versuchen jetzt die Selektion auf den Orginaldaten durchzuführen. Eigentlich wird empfohlen die Daten vorher zu normalisieren und dann mit den maschinellen Lernverfahren zu nutzen.::: callout-caution## Standardisieren oder Normalisieren von DatenEine Herausforderung für maschinelle Lernverfahren sind nicht normalisierte Daten. Das heist, dass wir Variablen haben, die kategorial oder kontinuierlich sein können oder aber sehr unterschiedlich von den Einheiten sind. Deshalb wird empfohlen die Daten vorher zu Standardisieren oder zu Normalisieren. In dem @sec-eda-transform kannst du mehr über das Transformieren von Daten nachlesen.:::Wir nutzen als erstes einen Random Forest Algorithmus wie er in @sec-class-tree beschrieben ist. Es bietet sich hier die Implementation im R Paket `{ranger}` an. Bevor wir aber einen Random Forest auf unsere Daten laufen lassen, Standardisieren wir unsere Daten nochmal. Überall wo wir einen numerischen Wert als Variableneintrag haben rechnen wir eine $z$-Transformation. Wir erhalten dann die standardisierten Daten zurück.```{r}pig_norm_tbl <- pig_tbl |>mutate(across(where(is.character), as_factor),across(where(is.numeric), dlookr::transform, "zscore"))pig_norm_tbl```Den Random Forest rechnen wir mit der Funktion `ranger()`. Dafür müssen wir wieder unser vollständiges Modell definieren und können dann die Funktion starten. Damit wir eine Variablenwichtigkeit (eng. *variable importance*) wiederbekommen, müssen wir noch die Option `importance = "permutation"` verwenden.```{r}#| echo: true#| message: false#| warning: falsefit_raw <-ranger(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_tbl, ntree =1000, importance ="permutation")pluck(fit_raw, "variable.importance") |>sort(decreasing =TRUE) |>round(3)```Wir sehen, dass wir als wichtigste Variable wiederum `crp` zurückbekommen. Danach wird es schon etwas schwieriger, da die Werte sehr schnell kleiner werden und auch ein Art Plateau bilden. Daher würde man hier nur annehmen, dass `crp` bedeutend für das Modell ist. Es kann aber auch sein, dass hier eine kontinuierliche Variable sehr vom Algorithmus bevorzugt wurde. Daher schauen wir uns die Sachlage einmal mit den standardisierten Daten an.```{r}#| echo: true#| message: false#| warning: falsefit_norm <-ranger(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_norm_tbl, ntree =1000, importance ="permutation")pluck(fit_norm , "variable.importance") |>sort(decreasing =TRUE) |>round(3)```Auch hier erhalten wir ein ähnliches Bild. Audf jeden Fall ist `crp` bedeutend für den Infektionsstatus. Danach werden die Werte etwas zufällig. Wir können Werte für die *variable importance* nicht unter Datensätzen vergleichen. Jeder Datensatz hat seine eigene *variable importance*, die von den Werten in dem Datensatz abhängt.Wir ziehen als Fazit, dass wir nur `crp` als bedeutenden Wert für die Klassifikation des Infektionsstatus ansehen würden. Hier stehen wir wirklich etwas wie das Schwein vor dem Uhrwerk, denn was nun richtiger ist, `stepAIC` oder `ranger` lässt sich so einfach nicht bewerten. Zum einen wollen wir ja eigentlich mit Random Forest eine Klassifikation durchführen und mit linearen Regressionsmodellen eher kausale Modelle schätzen. Am Ende musst du selber abschätzen, was in das finale Modell soll. Ich kann ja auch den Threshold für den Variablenausschluss selber wählen. Wähle ich einen Threshold von $0.008$, dann hätte ich `crp`, `weight`, `bloodpressure` und `sex` mit in dem finalen Modell.### Feature Selektion mit `boruta`Mit dem Boruta Algorithmus steht uns noch eine andere Implemnetierung des Random Forest Algorithmus zu verfügung um Feature Selektion zu betreiben. Wir nutzen wieder den Boruta Algorithmus in seine einfachen Form und gehen nicht tiefer auf alle Optionen ein. Wir nehmen wieder als Beispiel den Datensatz zu den infizierten Ferkeln und nutzen in diesem Fall auch nur die rohen Daten. Über eine Standardisierung könnte man wiederum nachdenken.::: column-marginIch empfehle noch das Tutorium [Feature Selection in R with the Boruta R Package](https://www.datacamp.com/tutorial/feature-selection-R-boruta). Wir gehen hier nicht tiefer auf die Funktionalität von Boruta ein.:::::: callout-caution## Standardisieren oder Normalisieren von DatenEine Herausforderung für maschinelle Lernverfahren sind nicht normalisierte Daten. Das heist, dass wir Variablen haben, die kategorial oder kontinuierlich sein können oder aber sehr unterschiedlich von den Einheiten sind. Deshalb wird empfohlen die Daten vorher zu Standardisieren oder zu Normalisieren. In dem @sec-eda-transform kannst du mehr über das Transformieren von Daten nachlesen.:::Um die Funktion `Boruta()` zu nutzen brauchen wir wieder das Modell und den Datensatz. Sonst ist erstmal nichts weiter anzugeben. Die Funktion läuft dann durch und gibt auch gleich den gewollten Informationshappen. Wichtig ist hierbei, dass wir natürlich noch andere Optionen mit angeben können. Wir können die Anzahl an Iterationen erhöhen und andere Tuning Parameter eingeben. Hier muss man immer schauen was am besten passt. Da würde ich auch immer rumprobieren und auf der Hilfeseite der Funktion `?Boruta` einmal nachlesen.```{r}set.seed(20221031)boruta_output <-Boruta(infected ~ age + sex + location + activity + crp + frailty + bloodpressure + weight + creatinin, data = pig_tbl) boruta_output```Manchmal ist es super praktisch, wenn eine Funktion einem die Antwort auf die Frage welche Variable bedeutend ist, gleich liefert. Wir erhalten die Information, dass die Variable `crp` als bedeutsam angesehen wird. Wir können uns den Zusammenhang auch in der @fig-buruta auch einmal anschauen. Die grünen Variablen sind die bedeutenden Variablen.```{r}#| echo: true#| message: false#| label: fig-buruta#| fig-align: center#| fig-height: 5#| fig-width: 5#| fig-cap: "Visualisierung der Boruta Ausgabe."plot(boruta_output, cex.axis=.7, las=2, xlab="", main="Variable Importance") ```Am Ende finden wir auch hier die Variable `crp` als einziges als bedeutend wieder. Wenn wir noch Variablen haben die verdächtig oder vorläufig bedeutend sind, angezeigt durch `2 tentative attributes left: bloodpressure, sex`, dann können wir noch die Funktion `TentativeRoughFix()` nutzen. Die Funktion `TentativeRoughFix()` rechnet die Variablen nochmal nach und versucht alle Variablen in bedeutend oder nicht bedeutend zu klassifizieren. Wir haben ja zwei *tentative* Variablen in unseren Fall vorliegen, also nutzen wir noch kurz die Funktion um uns auch hier Klarheit zu schaffen.```{r}TentativeRoughFix(boruta_output)```Am Ende ist die klare Aussage einer Funktion auch immer ein zweischneidiges Schwert. Wir verlieren jetzt noch die beiden *tentative* Variablen. Wo wir bei `ranger` die Qual der Wahl haben, werden wir bei `Boruta` eher vor vollendete Tatsachen gestellt. Meistens neigt man nach einer `Boruta`-Analyse nicht dazu noch eine zusätzliche Variable mit ins Modell zu nehmen. Dafür ist dann die Ausgabe zu bestimmt, obwohl die Entscheidung am Ende auch genau so unsicher ist wie von `ranger` und den anderen Modellen.## Referenzen {.unnumbered}