47  Gaussian Regression

Letzte Änderung am 09. April 2024 um 09:55:04

“Regression analysis is the hydrogen bomb of the statistics arsenal.” — Charles Wheelan, Naked Statistics: Stripping the Dread from the Data

Selten nutzen wir eine multiple gaussian lineare Regression alleine. Häufig haben wir dann ein normalverteiltes Outcome, weshalb wir die Gaussian Regression überhaupt rechnen, und dann noch mehrere Faktoren über die wir eine Aussagen treffen wollen. In der folgenden Abbildung 47.1 siehst du nochmal den Zusammenhang zwischen einem normalverteilten Outcome, wie Gewicht oder Größe, im Bezug zu einem kontinuierlichen oder kategoriellen \(x\) als Einflussgröße. Wir sind dann eher im Bereich der Posthoc-Tests mit kategoriellen \(x\) als in wirklich einer Interpretation einer Gaussian Regression mit einem kontinuierlichen \(x\). Dennoch müssen wir wissen, ob die Gaussian Regression gut funktioniert hat. Die Überprüfung der Modellierung können wir dann in diesem Kapitel einmal durchgehen.

Abbildung 47.1— Visueller Zusammenhang eines kontinuierlichen Outcomes (\(y\)) aus einer Normalverteilung (Gaussian) im Verhätnis zu verschiedenen Skalen der Einflussvariable (\(x\)). Ein Punkt stellt eine Beobachtung dar. (A) \(x\) ist kontinuierlich. (B) \(x\) ist kategoriell mit zwei oder mehr Gruppen. (C) \(x\) ist kategoriell mit zwei Gruppen. [Zum Vergrößern anklicken]

Häufig wollen wir dann die Ergebnisse aus einer Gaussian Regression dann im Falle eines kategoriellen \(x\) dann als Barplots wie in der Abbildung 47.2 darstellen. Hier musst du dann einmal weiter unten in den Abschnitt zu den Gruppenvergleichen springen.

Abbildung 47.2— Visueller Zusammenhang eines gemittelten Outcomes (\(y\)) aus einer Normalverteilung im Verhältnis zu der Einflussvariable (\(x\)) mit zwei oder mehr Kategorien anhand von Barplots. Hauptsächlich unterscheiden sich die Barplots durch die unterschiedlichen Einheiten auf der \(y\)-Achse. Die Fehlerbalken stellen den Standardfehler (SE) dar. [Zum Vergrößern anklicken]

Im diesem Kapitel zu der multiplen Gaussian linearen Regression gehen wir davon aus, dass die Daten in der vorliegenden Form ideal sind. Das heißt wir haben weder fehlende Werte vorliegen, noch haben wir mögliche Ausreißer in den Daten. Auch wollen wir keine Variablen selektieren. Wir nehmen alles was wir haben mit ins Modell. Sollte eine oder mehre Bedingungen nicht zutreffen, dann schaue dir einfach die folgenden Kapitel an.

Wir gehen jetzt einmal die lineare Regression mit einem normalverteilten Outcome \(y\) einmal durch. Wie schon oben erwähnt, meistens nutzten wir die Gaussian Regression als vorangestelltes Modell für eine ANOVA oder dann eben einen Posthoc-Test in {emmeans}.

47.1 Genutzte R Pakete

Wir wollen folgende R Pakete in diesem Kapitel nutzen.

pacman::p_load(tidyverse, magrittr, broom,
               see, performance, scales, parameters,
               olsrr, readxl, car, gtsummary, emmeans,
               multcomp, conflicted)
conflicts_prefer(dplyr::select)
cbbPalette <- c("#000000", "#E69F00", "#56B4E9", "#009E73", 
                "#F0E442", "#0072B2", "#D55E00", "#CC79A7")

An der Seite des Kapitels findest du den Link Quellcode anzeigen, über den du Zugang zum gesamten R-Code dieses Kapitels erhältst.

47.2 Daten

Im Folgenden schauen wir uns die Daten eines Pilotprojektes zum Anbau von Kichererbsen in Brandenburg an. Wir haben an verschiedenen anonymisierten Bauernhöfen Kichererbsen angebaut und das Trockengewicht als Endpunkt bestimmt. Darüber hinaus haben wir noch andere Umweltparameter erhoben und wollen schauen, welche dieser Parameter einen Einfluss auf das Trockengewicht hat. In Kapitel 7.3 findest du nochmal mehr Informationen zu den Daten.

chickpea_tbl <- read_excel("data/chickpeas.xlsx") 

In der Tabelle 47.1 ist der Datensatz chickenpea_tbl nochmal als Ausschnitt dargestellt. Insgesamt haben wir \(n = 95\) Messungen durchgeführt. Wir sehen, dass wir verschiedene Variablen gemessen haben. Unter anderem, ob es geregent hat oder an welcher Stelle in Brandenburg die Messungen stattgefunden haben. Ebenso haben wir geschaut, ob ein Wald in der Nähe der Messung war oder nicht. Wir nehmen als Outcome \(y\) das normalverteilte Trockengewicht dryweight.

Tabelle 47.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 255.4
21.4 high northeast 9.15 2.58 51.17 <1000m 217.55
27.84 high northeast 5.57 2.19 55.57 <1000m 232.52
24.59 low north 7.97 1.47 62.49 >1000m 252.06
25.47 low north 6.92 3.18 64.55 >1000m 266.68
29.04 low north 5.64 2.87 53.27 >1000m 228.58
24.11 high northeast 4.31 3.66 63 >1000m 251.75
28.88 low northeast 7.92 2 65.75 <1000m 274.46

Im Folgenden werden wir die Daten nur für das Fitten eines Modells verwenden. In den anderen oben genannten Kapiteln nutzen wir die Daten dann anders.

47.3 Fit des Modells

Wir rechnen jetzt den Fit für das vollständige Modell mit allen Variablen in dem Datensatz. Wir sortieren dafür einmal das \(y\) mit dryweight auf die linke Seite und dann die anderen Variablen auf die rechte Seite des ~. Wir haben damit unser Modell chickenpea_fit wie folgt vorliegen.

chickenpea_fit <- lm(dryweight ~ temp + rained + location + no3 + fe + sand + forest, 
                   data = chickpea_tbl)

Soweit so gut. Wir können uns zwar das Modell mit der Funktion summary() anschauen, aber es gibt schönere Funktionen, die uns erlauben einmal die Performance des Modells abzuschätzen. Also zu klären, ob soweit alles geklappt hat und wir mit dem Modell weitermachen können.

47.4 Performance des Modells

Da ich die Daten selber gebaut habe, ist mir bekannt, dass das Outcome dryweight normalverteilt ist. Immerhin habe ich die Daten aus einer Normalverteilung gezogen. Manchmal will man dann doch Testen, ob das Outcome \(y\) einer Normalverteilung folgt. Das R Paket {oslrr} bietet hier eine Funktion ols_test_normality(), die es erlaubt mit allen bekannten statistischen Tests auf Normalverteilung zu testen. Wenn der \(p\)-Wert kleiner ist als das Signifikanzniveau \(\alpha\), dann können wir die Nullhypothese, dass unsere Daten gleich einer Normalverteilung wären, ablehnen. Darüber hinaus bietet das R Paket {olsrr} eine weitreichende Diagnostik auf einem normalverteilten Outcome \(y\).

ols_test_normality(chickenpea_fit) 
-----------------------------------------------
       Test             Statistic       pvalue  
-----------------------------------------------
Shapiro-Wilk              0.9899         0.6886 
Kolmogorov-Smirnov        0.0665         0.7699 
Cramer-von Mises          6.9798         0.0000 
Anderson-Darling          0.2994         0.5773 
-----------------------------------------------

Wir sehen, testen wir viel, dann kommt immer was signifikantes raus. Um jetzt kurz einen statistischen Engel anzufahren, wir nutzen wenn überhaupt den Shapiro-Wilk-Test oder den Kolmogorov-Smirnov-Test. Für die anderen beiden steigen wir jetzt hier nicht in die Theorie ab.

Nachdem wir die Normalverteilung nochmal überprüft haben wenden wir uns nun dem Wichtigen zu. Wir schauen jetzt auf die Varianz des Modells. Um zu überprüfen, ob das Modell funktioniert können wir uns den Anteil der erklärten Varianz anschauen. Wie viel erklären unsere \(x\) von der Varianz des Outcomes \(y\)? Wir betrachten dafür das Bestimmtheitsmaß \(R^2\). Da wir mehr als ein \(x\) vorliegen haben, nutzen wir das adjustierte \(R^2\). Das \(R^2\) hat die Eigenschaft immer größer und damit besser zu werden je mehr Variablen in das Modell genommen werden. Wir können dagegen Adjustieren und daher das \(R^2_{adj}\) nehmen.

r2(chickenpea_fit)
# R2 for Linear Regression
       R2: 0.884
  adj. R2: 0.873

Wir erhalten ein \(R^2_{adj}\) von \(0.87\) und damit erklärt unser Modell ca 87% der Varianz von \(y\) also unserem Trockengewicht. Das ist ein sehr gutes Modell. Je nach Anwendung sind 60% bis 70% erklärte Varianz schon sehr viel.

Im nächsten Schritt wollen wir nochmal überprüfen, ob die Varianzen der Residuen auch homogen sind. Das ist eine weitere Annahme an ein gutes Modell. Im Prinzip überprüfen wir hier, ob unser Ourtcome auch wirklcih normalveteilt ist bzw. der Annahme der Normalverteilung genügt. Wir nutzen dafür die Funktion check_heteroscedasticity() aus dem R Paket {performance}.

check_heteroscedasticity(chickenpea_fit)
OK: Error variance appears to be homoscedastic (p = 0.344).

Auch können wir uns einmal numerisch die VIF-Werte anschauen um zu sehen, ob Variablen mit anderen Variablen ungünstig stark korrelieren. Wir wollen ja nur die Korrelation des Modells, also die \(x\), mit dem Outcome \(y\) modellieren. Untereinander sollen die Variablen \(x\) alle unabhängig sein. Für können uns die VIF-Werte für alle kontinuierlichen Variablen berechnen lassen.

vif(chickenpea_fit)
             GVIF Df GVIF^(1/(2*Df))
temp     1.074291  1        1.036480
rained   1.093377  1        1.045647
location 1.161615  2        1.038163
no3      1.060697  1        1.029902
fe       1.097535  1        1.047633
sand     1.073129  1        1.035919
forest   1.132836  1        1.064347

Alle VIF-Werte sind unter dem Threshold von 5 und damit haben wir hier keine Auffälligkeiten vorliegen.

Damit haben wir auch überprüft, ob unsere Varianzen homogen sind. Also unsere Residuen annähend normalverteilt sind. Da unsere Daten groß genug sind, können wir das auch ohne weiteres Anwenden. Wenn wir einen kleineren Datensatz hätten, dann wäre die Überprüfung schon fraglicher. bei kleinen Fallzahlen funktioniert der Test auf Varianzheterogenität nicht mehr so zuverlässig.

In Abbildung 47.3 sehen wir nochmal die Visualisierung verschiedener Modellgütekriterien. Wir sehen, dass unsere beobachte Verteilung des Trockengewichts mit der vorhergesagten übereinstimmt. Ebenso ist der Residualplot gleichmäßig und ohne Struktur. Wir haben auch keine Ausreißer, da alle unsere Beobachtungen in dem gestrichelten, blauen Trichter bleiben. Ebenso zeigt der QQ-Plot auch eine approximative Normalverteilung der Residuen. Wir haben zwar leichte Abweichungen, aber die sind nicht so schlimm. Der Großteil der Punkte liegt auf der Diagonalen. Ebenso gibt es auch keine Variable, die einen hohen VIF-Wert hat und somit ungünstig mit anderen Variablen korreliert.

check_model(chickenpea_fit, colors = cbbPalette[6:8], 
            check = c("qq", "outliers", "pp_check", "homogeneity", "vif")) 
Abbildung 47.3— Ausgabe ausgewählter Modelgüteplots der Funktion check_model().

47.5 Interpretation des Modells

Nachdem wir nun sicher sind, dass das Modell unseren statistischen Ansprüchen genügt, können wir jetzt die Ergebnisse des Fits des Modells einmal interpretieren. Wir erhalten die Modellparameter über die Funktion model_parameters() aus dem R Paket {parameters}.

chickenpea_fit |> 
  model_parameters()
Parameter            | Coefficient |    SE |         95% CI | t(86) |      p
----------------------------------------------------------------------------
(Intercept)          |       15.80 | 10.39 | [-4.85, 36.46] |  1.52 | 0.132 
temp                 |        1.75 |  0.24 | [ 1.29,  2.22] |  7.46 | < .001
rained [low]         |        1.33 |  1.29 | [-1.24,  3.90] |  1.03 | 0.307 
location [northeast] |       -1.38 |  1.29 | [-3.95,  1.20] | -1.06 | 0.291 
location [west]      |       -2.40 |  1.78 | [-5.94,  1.15] | -1.34 | 0.182 
no3                  |        1.11 |  0.43 | [ 0.25,  1.97] |  2.56 | 0.012 
fe                   |       -0.72 |  0.56 | [-1.83,  0.40] | -1.28 | 0.205 
sand                 |        3.03 |  0.13 | [ 2.78,  3.29] | 23.93 | < .001
forest [>1000m]      |        0.67 |  1.27 | [-1.85,  3.18] |  0.53 | 0.599 

Schauen wir uns die einzelnen Zeilen aus der Ausgabe einmal in Ruhe an. Wir sind eigentlich nur an den Spalten Coefficient für das \(\beta\) als Effekt der Variablen sowie der Spalte p als \(p\)-Wert für die Variablen interessiert. Wir testen immer als Nullhypothese, ob sich der Parameter von 0 unterscheidet.

  • (Intercept) beschreibt den den \(y\)-Achsenabschnitt. Wir brauen den Intercept selten in der Interpretation. Wir nehmen hier erstmal hin, dass wir einen von 0 signifikant unterschiedlichen Intercept haben. Meist löschen wir den Intrcept auch aus der finalen Tabelle raus.
  • temp beschreibt den Effekt der Temperatur. Wenn die Temperatur um ein Grad ansteigt, dann erhalten wir \(1.75\) mehr Trockengewicht als Ertrag. Darüber hinaus ist der Effekt der Temperatur signifikant.
  • rained [low] beschreibt den Effekt des Levels low des Faktors rained im Vergleich zum Level high. Daher haben wir bei wenig Regen einen um \(1.33\) höheren Ertrag als bei viel Regen.
  • location [northeast] beschreibt den Effekt des Levels northeast zu dem Level north des Faktors location. Wir haben also einen \(-1.38\) kleineren Ertrag an Kichererbsen als im Norden von Brandenburg. Wenn du hier eine andere Sortierung willst, dann musst du mit der Funktion factor() die Level anders sortieren.
  • location [west] beschreibt den Effekt des Levels west zu dem Level north des Faktors location. Wir haben also einen \(-2.40\) kleineren Ertrag an Kichererbsen als im Norden von Brandenburg. Wenn du hier eine andere Sortierung willst, dann musst du mit der Funktion factor() die Level anders sortieren.
  • no3 beschreibt den Effekt von Nitrat im Boden. Wenn wir die Nitratkonzentration um eine Einheit erhöhen dann steigt der Ertrag um \(1.11\) an. Wir haben hier einen \(p\)-Wert von \(0.012\) vorliegen und können hier von einem signifkianten Effekt sprechen.
  • fe beschreibt den Effekt des Eisens im Boden auf den Ertrag an Kichererbsen. Wenn die Konzentration von Eisen um eine Einheit ansteigt, so sinkt der Ertrag von Kichererbsen um \(-0.72\) ab.
  • sand beschreibt den Anteil an Sand in der Bodenprobe. Wenn der Anteil an Sand um eine Einheit ansteigt, so steigt der Ertrag an Kichererbsen um \(3.03\) an. Dieser Effekt ist auch hoch signifikant. Kichererbsen scheinen sandigen Boden zu bevorzugen.
  • forest [>1000m] beschreibt die Nähe des nächsten Waldstückes als Faktor mit zwei Leveln. Daher haben wir hier einen höheren Ertrag von \(0.67\) an Kichererbsen, wenn wir weiter weg vom Wald messen >1000 als Nahe an dem Wald <1000.

Das war eine Wand an Text für die Interpretation des Modells. Was können wir zusammenfassend mitnehmen? Wir haben drei signifikante Einflussfaktoren auf den Ertrag an Kichererbsen gefunden. Zum einen ist weniger Regen signifikant besser als viel Regen. Wir brauchen mehr Nitrat im Boden. Im Weiteren ist ein sandiger Boden besser als ein fetter Boden. Am Ende müssen wir noch schauen, was die nicht signifikanten Ergebnisse uns für Hinweise geben. Der Ort der Messung ist relativ unbedeutend. Es scheint aber so zu sein, dass im Norden mehr Ertrag zu erhalten ist. Hier müsste man einmal schauen, welche Betriebe hier vorliegen und wie die Bodenbeschaffenheit dort war. Im Weiteren sehen wir, dass anscheinend ein Abstand zum Wald vorteilhaft für den Ertrag ist. Hier könnte Wildfraß ein Problem gewesen sein oder aber zu viel Schatten. Auch hier muss man nochmal auf das Feld und schauen, was das konkrete Problem sein könnte. Hier endet die Statistik dann.

47.6 Gruppenvergleich

Häufig ist es ja so, dass wir das Modell für die Gaussian Regression nur schätzen um dann einen Gruppenvergleich zu rechnen. Das heißt, dass es uns interessiert, ob es einen Unterschied zwischen den Leveln eines Faktors gegeben dem Outcome \(y\) gibt. Wir nehmen hier einen ausgedachten Datensatz zu Katzen-, Hunde- und Fuchsflöhen. Dabei erstellen wir uns einen Datensatz mit mittleren Gewicht an Flöhen pro Tierart. Ich habe jetzt ein mittleres Gewicht von \(20mg\) bei den Katzenflöhen, eine mittleres Gewicht von \(30mg\) bei den Hundeflöhen und ein mittleres Gewicht von \(10mg\) bei den Fuchsflöhen gewählt. Wir generieren uns jeweils \(20\) Beobachtungen je Tierart. Damit haben wir dann einen Datensatz zusammen, den wir nutzen können um einmal die Ergebnisse eines Gruppenvergleiches zu verstehen zu können.

set.seed(20231202)
n_rep <- 20
flea_weight_tbl <- tibble(animal = gl(3, n_rep, labels = c("cat", "dog", "fox")),
                         weight = c(rnorm(n_rep, 20, 1), 
                                         rnorm(n_rep, 30, 1), 
                                         rnorm(n_rep, 10, 1)))

Wenn du gerade hierher gesprungen bist, nochmal das simple Modell für unseren Gruppenvergleich unter einer Gaussian Regression. Wir haben hier nur einen Faktor animal mit in dem Modell. Am Ende des Abschnitts findest du dann noch ein Beispiel mit drei Faktoren zu Gewicht von Brokkoli.

lm_fit <- lm(weight ~ animal, data = flea_weight_tbl) 

Eigentlich ist es recht einfach, wie wir anfangen. Wir rechnen jetzt als erstes die ANOVA. Hier müssen wir dann einmal den Test angeben, der gerechnet werden soll um die p-Werte zu erhalten. Dann nutze ich noch die Funktion model_parameters() um eine schönere Ausgabe zu erhalten.

lm_fit |> 
  anova() |> 
  model_parameters()
Parameter | Sum_Squares | df | Mean_Square |       F |      p
-------------------------------------------------------------
animal    |     3960.78 |  2 |     1980.39 | 2525.37 | < .001
Residuals |       44.70 | 57 |        0.78 |         |       

Anova Table (Type 1 tests)

Im Folgenden nutzen wir das R Paket {emmeans} um die mittleren Gewichte der Flöhe zu berechnen.

emm_obj <- lm_fit |> 
  emmeans(~ animal)
emm_obj
 animal emmean    SE df lower.CL upper.CL
 cat      20.2 0.198 57    19.77     20.6
 dog      30.1 0.198 57    29.66     30.5
 fox      10.2 0.198 57     9.76     10.6

Confidence level used: 0.95 

Wir rechnen jetzt den paarweisen Vergleich für alle Tierarten und schauen uns dann an, was wir erhalten haben. Wie du gleich siehst, erhalten wir die Differenzen der Mittelwerte der Flohgewichte für die verschiedenen Tierarten. Hier also einmal die paarweisen Vergleiche, darunter dann gleich das compact letter display.

emm_obj |> 
  pairs(adjust = "bonferroni")
 contrast  estimate   SE df t.ratio p.value
 cat - dog     -9.9 0.28 57 -35.336  <.0001
 cat - fox     10.0 0.28 57  35.732  <.0001
 dog - fox     19.9 0.28 57  71.068  <.0001

P value adjustment: bonferroni method for 3 tests 

Und fast am Ende können wir uns auch das compact letter display erstellen. Auch hier nutzen wir wieder die Funktion cld() aus dem R Paket {multcomp}. Hier erhälst du dann die Information über die mittleren Flohgewichte der jeweiligen Tierarten und ob sich die mittleren Flohgewichte unterscheidet. Ich nutze dann die Ausgabe von emmeans() um mir dann direkt das Säulendiagramm mit den Fehlerbalken und dem compact letter display zu erstellen. Mehr dazu dann im Kasten weiter unten zu dem Beispiel zu den Gewichten des Brokkoli.

emm_obj |>
  cld(Letters = letters, adjust = "none")
 animal emmean    SE df lower.CL upper.CL .group
 fox      10.2 0.198 57     9.76     10.6  a    
 cat      20.2 0.198 57    19.77     20.6   b   
 dog      30.1 0.198 57    29.66     30.5    c  

Confidence level used: 0.95 
significance level used: alpha = 0.05 
NOTE: If two or more means share the same grouping symbol,
      then we cannot show them to be different.
      But we also did not show them to be the same. 

Auch hier sehen wir, dass sich alle drei Gruppen signifikant unterschieden, keine der Tierarten teilt sich einen Buchstaben, so dass wir hier von einem Unterschied zwischen den mittleren Flohgewichten der drei Tierarten ausgehen können.

Damit sind wir einmal mit unserem Gruppenvergleich für die Gaussian Regression auf normalverteilte Daten durch. In dem Kapitel zu den Multiple Vergleichen oder Post-hoc Tests findest du dann noch mehr Inspirationen für die Nutzung von {emmeans}. Hier war es dann die Anwendung auf normalverteilte Dateb zusammen mit einem Faktor. Wenn du dir das Ganze nochmal an einem Beispiel für zwei Faktoren anschauen möchtest, dann findest du im folgenden Kasten ein Beispiel für die Auswertung von Brokkoli nach Gabe verschiedener Dosen eines Düngers und Zeitpunkten.

Anwendungsbeispiel: Dreifaktorieller Vergleich für das Erntegewicht

Im folgenden Beispiel schauen wir uns nochmal ein praktische Auswertung von einem agrarwissenschaftlichen Beispiel mit Brokkoli an. Wir haben uns in diesem Experiment verschiedene Dosen fert_amount von einem Dünger aufgebracht sowie verschiedene Zeitpunkte der Düngung fert_time berücksichtigt. Auch hier haben wir einige Besonderheiten in den Daten, da nicht jede Faktorkombination vorliegt. Wir ignorieren aber diese Probleme und rechnen einfach stumpf unseren Gruppenvergleich.

broc_tbl <- read_excel("data/broccoli_weight.xlsx") |> 
  mutate(fert_time = factor(fert_time, levels = c("none", "early", "late")),
         fert_amount = as_factor(fert_amount),
         block = as_factor(block)) |>
  select(-stem_hollowness) 

Dann können wir auch schon die Gaussian Regression mit lm() rechnen.

lm_fit <- lm(weight ~ fert_time + fert_amount + fert_time:fert_amount + block, 
             data = broc_tbl) 

Jetzt rechnen wir in den beiden folgenden Tabs einmal die ANOVA und dann auch den multiplen Gruppenvergleich mit {emmeans}. Da wir hier normalveteilte Daten haben, können wir dann einfach die Standardverfahren nehmen. Eventuell müssten wir bei dem Gruppenvergleich mit emmeans() nochmal für Varianzheterogenität adjustieren, aber da erfährst du dann mehr in dem Kapitel zu den Multiple Vergleichen oder Post-hoc Tests.

Wir rechnen hier einmal die ANOVA und nutzen den \(\mathcal{X}^2\)-Test für die Ermittelung der p-Werte. Wir müssen hier einen Test auswählen, da per Standardeinstellung kein Test gerechnet wird. Wir machen dann die Ausgabe nochmal schöner und fertig sind wir.

lm_fit |> 
  anova() |> 
  model_parameters()
Parameter             | Sum_Squares |   df | Mean_Square |     F |      p
-------------------------------------------------------------------------
fert_time             |    2.44e+06 |    2 |    1.22e+06 | 44.12 | < .001
fert_amount           |    2.11e+06 |    2 |    1.05e+06 | 38.06 | < .001
block                 |    7.21e+06 |    3 |    2.40e+06 | 86.73 | < .001
fert_time:fert_amount |    26973.84 |    2 |    13486.92 |  0.49 | 0.615 
Residuals             |    4.28e+07 | 1544 |    27692.44 |       |       

Anova Table (Type 1 tests)

Wir sehen, dass der Effekt der Düngerzeit und die Menge des Düngers signifikant ist, jedoch wir keinen signifikanten Einfluss durch die Interaktion haben. Wir haben aber also keine Interaktion vorliegen. Leider ist auch der Block signifikant, so dass wir eigentlich nicht über den Block mitteln sollten. Wir rechnen trotzdem die Analyse gemittelt über die Blöcke. Wenn du hier mehr erfahren möchtest, dann schaue dir das Beispiel hier nochmal im Kapitel zu dem linearen gemischten Modellen an.

Im Folgenden rechnen wir einmal über alle Faktorkombinationen von fert_time und fert_amount einen Gruppenvergleich. Dafür nutzen wir die Option fert_time * fert_amount. Wenn du die Analyse getrennt für die Menge und den Zeitpunkt durchführen willst, dann nutze die Option fert_time | fert_amount. Dann adjustieren wir noch nach Bonferroni und sind fertig.

emm_obj <- lm_fit |> 
  emmeans(~ fert_time * fert_amount) |>
  cld(Letters = letters, adjust = "bonferroni")
emm_obj
 fert_time fert_amount emmean    SE   df lower.CL upper.CL .group
 none      0              169 33.97 1544     77.1      260  a    
 late      150            400 11.25 1544    369.8      430   b   
 early     150            432 11.05 1544    401.8      461   bc  
 late      225            467 11.20 1544    436.9      497    cd 
 early     225            497  7.97 1544    475.2      518     de
 late      300            506 11.40 1544    475.1      537     de
 early     300            517 11.38 1544    486.8      548      e
 early     0           nonEst    NA   NA       NA       NA       
 late      0           nonEst    NA   NA       NA       NA       
 none      150         nonEst    NA   NA       NA       NA       
 none      225         nonEst    NA   NA       NA       NA       
 none      300         nonEst    NA   NA       NA       NA       

Results are averaged over the levels of: block 
Confidence level used: 0.95 
Conf-level adjustment: bonferroni method for 7 estimates 
P value adjustment: bonferroni method for 21 tests 
significance level used: alpha = 0.05 
NOTE: If two or more means share the same grouping symbol,
      then we cannot show them to be different.
      But we also did not show them to be the same. 

Das emm_obj Objekt werden wir dann gleich einmal in {ggplot} visualisieren. Die emmean stellt den mittleren Gewicht des Brokkoli je Faktorkombination dar gemittelt über alle Blöcke. Das Mitteln über die Blöcke ist eher fragwürdig, da wir ja einen Effekt der Blöcke in der ANOVA gefunden hatten. Hier schauen wir dann nochmal auf das Beispiel im Kapitel zu den linearen gemischten Modellen. Dann können wir zum Abschluss auch das compact letter display anhand der Abbildung interpretieren.

In der Abbildung 52.17 siehst du das Ergebnis der Auswertung in einem Säulendiagramm. Wir sehen einen klaren Effekt der Düngezeitpunkte sowie der Düngermenge auf das Gewicht von Brokkoli. Wenn wir ein mittleres Gewicht von \(500g\) für den Handel erreichen wollen, dann erhalten wir das Zielgewicht nur bei einer Düngemenge von \(300mg/l\). Hier stellt sich dann die Frage, ob wir bei \(225mg/l\) und einem frühen Zeitpunkt der Düngung nicht auch genug Brokkoli erhalten. Das Ziel ist es ja eigentlich in einen Zielbereich zu kommen. Die Köpfe sollen ja nicht zu schwer und auch nicht zu leicht sein. Aber diese Frage und andere Fragen der biologischen Anwendung lassen wir dann hier einmal offen, denn das Beispiel soll ja nur ein Beispiel sein.

emm_obj |> 
  as_tibble() |> 
  ggplot(aes(x = fert_time, y = emmean, fill = fert_amount)) +
  theme_minimal() + 
  labs(y = "Mittleres Gewicht [g] des Brokkoli", x = "Düngezeitpunkt",
       fill = "Düngemenge [mg/l]") +
  scale_y_continuous(breaks = seq(0, 500, by = 100)) +
  geom_hline(yintercept = 500, size = 0.75, linetype = 2) +
  geom_bar(stat = "identity", 
           position = position_dodge(width = 0.9, preserve = "single")) +
  geom_text(aes(label = .group, y = emmean + SE + 0.01),  
            position = position_dodge(width = 0.9), vjust = -0.25) +
  geom_errorbar(aes(ymin = emmean-SE, ymax = emmean+SE),
                width = 0.2,  
                position = position_dodge(width = 0.9, preserve = "single")) +
  scale_fill_okabeito()
Abbildung 47.4— Säulendigramm der mittleren Brokkoligewichte aus einer Gaussian Regression. Das lm()-Modell berechnet das mittler Gewicht des Brokkoli in jeder Faktorkombination. Das compact letter display wird dann in {emmeans} generiert. Wir nutzen hier den Standardfehler, da die Standardabweichung mit der großen Fallzahl rießig wäre. Wir haben noch ein Mindestgewicht von 500g ergänzt.