library(data.table) # bessere Datenmanipulation
library(nlme) # generalisiertes Modell package
library(emmeans) # adjustierte Mittelwerte
library(ggplot2) # bessere Plots
In diesem Experiment wurde in einer randomisierten vollständigen Blockanlage (RCBD) mit 5 Wiederholungen der Blattflächenindex von 4 Sorghumsorten verglichen. Allerdings wurde der Blattflächenindex mehrfach, nämlich in 5 aufeinanderfolgenden Wochen, gemessen, sodass Messwiederholungen vorliegen.
Dieses Beispiel basiert auf Example 4 des
agriTutorial
packages und der dazugehörigen Veröffentlichung
Piepho, H. P., & Edmondson, R. N. (2018). A tutorial on the statistical analysis of factorial experiments with qualitative and quantitative treatment factor levels. Journal of Agronomy and Crop Science.
Messwiederholungen werfen eine Neuerung gegenüber den vorangegangenen Beispielen auf: zum ersten Mal ist die kleinste Randomisationseinheit (=Parzelle) nicht gleichzeitig die Beobachtungseinheit, da wir mehrere Beobachtungen pro Parzelle haben. Der wichtige Punkt hier ist, dass der Faktor Woche nicht randomisiert werden kann. Statt der üblichen Annahme von unabhängigen Messwerten, sind Messwerte derselben Parzelle von aufeinanderfolgenden Wochen wahrscheinlich miteinander korreliert. Um dies zu modellieren, soll im ersten Schritt vorerst das Modell für die Analyse einer einzelnen Woche aufgestellt werden.
print(repmes, nrows=10)
## week gen rep plot y
## 1: 1 A rep1 plot1 5.00
## 2: 1 D rep1 plot2 5.86
## 3: 1 B rep1 plot3 5.82
## 4: 1 C rep1 plot4 5.65
## 5: 1 C rep2 plot5 5.39
## ---
## 96: 5 B rep4 plot16 3.99
## 97: 5 D rep5 plot17 2.96
## 98: 5 C rep5 plot18 3.00
## 99: 5 B rep5 plot19 2.95
## 100: 5 A rep5 plot20 2.16
str(repmes)
## Classes 'data.table' and 'data.frame': 100 obs. of 5 variables:
## $ week: Factor w/ 5 levels "1","2",""..
## $ gen : Factor w/ 4 levels "A","B",""..
## $ rep : Factor w/ 5 levels "rep1","r"..
## $ plot: Factor w/ 20 levels "plot1","..
## $ y : num 5 5.86 5.82 5.65 5.39 4...
## - attr(*, ".internal.selfref")=<exter..
Wenn wir jede Woche separat analysieren, umgehen wir das Problem der korrelierten Messwerte und können das übliche Modell für eine einfaktorielle Varianzanalyse in einem RCBD aufstellen ( siehe Beispiel ).
Dann müssen wir lediglich einen Teildatensatz mit den Daten von nur einer Woche erstellen und auswerten.
repmes.wk1 <- repmes[week=="1"] # Nur Daten der ersten Woche
mod.wk1 <- lm(y ~ gen + rep, data=repmes.wk1)
anova(mod.wk1)
## Analysis of Variance Table
##
## Response: y
## Df Sum Sq Mean Sq F value Pr(>F)
## gen 3 2.7604 0.92012 46.59 6.918e-07 ***
## rep 4 8.3190 2.07974 105.31 3.086e-09 ***
## Residuals 12 0.2370 0.01975
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
So erhalten wir also die Ergebnisse der Varianzanalyse für die erste Woche und könnten wie gewohnt fortfahren indem wir adjustierte Mittelwerte für den signifikanten Faktor Sorte berechnen.
Anstatt die 5 Teildatensätze der 5 Wochen separat und manuell zu erstellen, können wir eine Schleife (Loop) schreiben, die automatisch eine Woche nach der anderen analysiert. Zusätzlich können wir in R eine Liste erstellen, in der die Ergebnisse aller durch den Loop generierten Varianzanalysen gespeichert werden.
anova.liste <- list() # erstelle ein leeres Listenobjekt
for (wochen.nr in c("1", "2", "3", "4", "5")){ # Loop Anfang
repmes.wkX <- repmes[week==wochen.nr]
mod.wkX <- lm(y ~ gen + rep, data=repmes.wkX)
anova.liste[[wochen.nr]] <- anova(mod.wkX)
} # Loop Ende
anova.liste[["1"]] # Zeige ANOVA der 1. Woche
## Analysis of Variance Table
##
## Response: y
## Df Sum Sq Mean Sq F value Pr(>F)
## gen 3 2.7604 0.92012 46.59 6.918e-07 ***
## rep 4 8.3190 2.07974 105.31 3.086e-09 ***
## Residuals 12 0.2370 0.01975
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova.liste[["5"]] # Zeige ANOVA der 5. Woche
## Analysis of Variance Table
##
## Response: y
## Df Sum Sq Mean Sq F value Pr(>F)
## gen 3 5.9977 1.99924 97.196 1.099e-08 ***
## rep 4 4.3410 1.08526 52.762 1.619e-07 ***
## Residuals 12 0.2468 0.02057
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Der Nachteil der separaten Analyse einzelner Wochen wird besonders deutlich, wenn wir uns vorstellen, dass wir Messwerte von sehr viel mehr Wochen hätten, deren ANOVAs nicht immer die gleichen Signifikanzen zeigen: Es kann schwierig sein eine wochenübergreifende Antwort zu geben. Außerdem wurde durch das separate Analysieren auch immer nur ein Bruchteil der Informationen genutzt. Demnach ist es erstrebenswert den Datensatz als Ganzes mit einem geeigneten Modell auszuwerten. Dafür müssen wir Korrelationen zwischen den Fehlertermen verschiedener Wochen erlauben. Die Grafik, die aus dem oben erwähnten Artikel stammt, soll dies verdeutlichen.
Standardmäßig haben lineare Modelle unabhängige, identisch verteilte Fehler (independent and identically distributed, kurz IID). Um also nun korrelierte Fehler zu modellieren, kann man eine Kovarianzstruktur für die Fehler annehmen. Es gibt verschiedene Kovarianzstrukturen ( siehe hier ), wobei die wahrscheinlich populärste für Messwiederholungen den Namen first order autoregressive, kurz AR1 trägt.
Um lineare Modelle anzupassen, die eine andere Kovarianzstruktur als IID annehmen, wird die Verallgemeinerte Kleinste-Quadrat-Schätzung (engl. generalized least squares kurz GLS) angewandt. Das nlme
package hat dafür die Funktion gls()
. Um das hier benötigte Modell aufzustellen können wir vorerst mit dem dem Modell aus der separaten Analyse der Wochen beginnen: y ~ gen + rep
. Zur Erinnerung: Auch wenn wir es nicht explizit ins Modell geschrieben haben, so wird standardmäßig auch ein Intercept \(\mu\) angepasst. Um dieses Modell für die Analyse mehrere Wochen zu erweitern, können wir jeden Effekt (also auch \(\mu\)) zusätzlich wochenspezifisch definieren: y ~ week + gen + week*gen + rep + week*rep
. Wir erhalten nun also zusätzlich je ein Intercept, einen Genotyp-Effekt und einen Wiederholungseffekt pro Woche. Man kann die Syntax für dieses Modell wie folgt abkürzen und trotzdem dieselben Effekte anpassen lassen:
mod.iid <- gls(y ~ week * (gen + rep),
data = repmes)
Schließlich wollen wir noch dafür sorgen, dass die Fehler derselben Parzelle zwsichen den Wochen autokorreliert sind, was mit dem Argument corr = corExp(form = ~ week|plot)
funktioniert.
mod.ar1 <- gls(y ~ week * (gen + rep),
corr = corExp(form = ~ week|plot),
data = repmes)
Wir können die geschätzten Fehlervarianzen und -korrelationen einsehen:
mod.iid$sigma^2 # IID.var
## [1] 0.02317583
mod.ar1$sigma^2 # AR1.var
## [1] 0.02317583
as.numeric(exp(-1/coef(mod.ar1$modelStruct$corStruct, unconstrained=F))) # AR1.cor
## [1] 0.7776763
Die Korrelation wurde also auf 0.78 (und somit ganz und gar nicht 0) geschätzt, was darauf hindeutet, dass diese Kovarianzstruktur in der Tat für die Modellierung der Daten geeignet ist. Um in solchen Fällen zu entscheiden ob das AR1-Modell tatsächlich dem IID-Modell vorzuziehen ist, können hier die AIC (Akaike Information Criterion) Werte der beiden Modelle verglichen werden. Dabei gilt, dass das Modell mit dem kleineren AIC-Wert das bessere ist. Zu beachten ist außerdem, dass nur Modelle, die die gleichen festen Effekte haben und dieselben Daten auswerten, mittels AIC-Wert verglichen werden sollten. Da das hier der Fall ist, können wir das demnach tun.
AIC(mod.iid)
## [1] 78.26137
AIC(mod.ar1)
## [1] 38.37925
Der deutlich kleinere AIC-Wert bestätigt also die Vermutung, dass hier eine AR1 statt der üblichen IID Struktur für die Fehler angepasst werden sollte. Es muss klar sein, dass an dieser Stelle natürlich auch die AIC-Werte der Modelle mit anderen Kovarianzstrukturen verglichen werden könnten. Dies soll hier allerdings nicht getan werden um das Beispiel kurz zu halten.
Schließlich können wir wie gewohnt eine ANOVA durchführen, um den F-Test für den Behandlungsfaktor durchzuführen - nur eben für das selektierte AR1-Modell. Residuenplots kann man für gls()
Objekte z.B. via plot(mod.ar)
und qqnorm(resid(mod.ar)); qqline(resid(mod.ar))
erhalten.
anova(mod.ar1)
## Denom. DF: 60
## numDF F-value p-value
## (Intercept) 1 21284.944 <.0001
## week 4 738.198 <.0001
## gen 3 88.053 <.0001
## rep 4 94.361 <.0001
## week:gen 12 14.975 <.0001
## week:rep 16 6.077 <.0001
Alle Effekte sind laut F-Test signifikant, relevant ist dabei aber vor allem week:gen
. Analog zur Vorgehensweise bei einer signifikanten Wechselwirkung von zwei Behandlungseffekten (siehe hier) gilt auch hier, dass die adjustierten Mittelwerte für week:gen
und nicht für gen
berechnet werden sollten.
means <- emmeans(mod.ar1, pairwise ~ gen|week, adjust="tukey") # adjust="none" für t-test
means <- CLD(means$emmeans, details=TRUE, Letters=letters)
means$emmeans # Sorten-Mittelwerte pro Woche
## week = 1:
## gen emmean SE df lower.CL upper.CL .group
## A 4.242 0.06808206 18.22 4.053961 4.430039 a
## C 4.888 0.06808206 19.51 4.701289 5.074711 b
## B 5.148 0.06808206 19.51 4.961289 5.334711 c
## D 5.154 0.06808206 18.22 4.965961 5.342039 c
##
## week = 2:
## gen emmean SE df lower.CL upper.CL .group
## A 4.046 0.06808206 18.22 3.857961 4.234039 a
## D 4.944 0.06808206 18.22 4.755961 5.132039 b
## B 4.980 0.06808206 19.51 4.793289 5.166711 b
## C 5.074 0.06808206 19.51 4.887289 5.260711 b
##
## week = 3:
## gen emmean SE df lower.CL upper.CL .group
## A 3.406 0.06808206 18.22 3.217961 3.594039 a
## B 4.594 0.06808206 19.51 4.407289 4.780711 b
## D 4.706 0.06808206 18.22 4.517961 4.894039 b
## C 4.714 0.06808206 19.51 4.527289 4.900711 b
##
## week = 4:
## gen emmean SE df lower.CL upper.CL .group
## A 3.086 0.06808206 18.22 2.897961 3.274039 a
## B 4.220 0.06808206 19.51 4.033289 4.406711 b
## D 4.388 0.06808206 18.22 4.199961 4.576039 b
## C 4.476 0.06808206 19.51 4.289289 4.662711 b
##
## week = 5:
## gen emmean SE df lower.CL upper.CL .group
## A 2.700 0.06808206 18.22 2.511961 2.888039 a
## D 3.834 0.06808206 18.22 3.645961 4.022039 b
## B 4.008 0.06808206 19.51 3.821289 4.194711 b
## C 4.018 0.06808206 19.51 3.831289 4.204711 b
##
## Results are averaged over the levels of: rep
## Degrees-of-freedom method: satterthwaite
## Confidence level used: 0.95
## Conf-level adjustment: sidak method for 4 estimates
## P value adjustment: tukey method for comparing a family of 4 estimates
## significance level used: alpha = 0.05
Wie der im F-Test signifikante week:gen
Effekt schon vermuten ließ, finden wir für die Sortenmittelwerte je Woche unterschiedliche Buchstabendarstellungen oder anders gesagt: Wochen-spezifische Unterschiede zwischen den Sorten. Allerdings sehen wir beispielsweise auch, dass Sorte A in jeder Woche signifikant schlechter ist als alle anderen Sorten.
Analog zu diesem Beispiel können wir die Ergebnisse grafisch wie folgt aufbereiten:
means.plot <- as.data.table(means$emmeans) # korrektes Format für ggplot
ggplot(data=means.plot, aes(x=gen)) +
geom_bar(aes(y=emmean, fill=gen), stat="identity", width=0.8) +
guides(fill=FALSE) +
geom_errorbar(aes(ymin=lower.CL, ymax=upper.CL), width=0.4) +
geom_text(aes(y=emmean*1.1, label=.group)) +
facet_wrap(~week) + # ein Plot pro Woche
theme_bw() +
labs(main="Adj. Sortenmittelwerte pro Woche",
y="Adjustierter Ertragsmittelwert ± 95% Konfidenzintervall", x="Sorte",
caption="Getrennt für jede Woche gilt: Mittelwerte, die mit einem gemeinsamen Buchstaben versehen sind,\n sind laut Tukey-test nicht signifikant voneinander verschieden.")
oder alternativ:
ggplot() +
# Rohdaten (lemna)
geom_jitter( data=repmes, aes(x=gen, y=y, fill=gen), width=0.15, height=0, shape=21) +
# Ergebnisse (plot.means)
geom_point( data=means.plot, aes(x=as.numeric(gen)+0.4, y=emmean), col="red", shape=16, size=2) +
geom_errorbar(data=means.plot, aes(x=as.numeric(gen)+0.4, ymin=lower.CL, ymax=upper.CL), col="red", width=0.3) +
geom_text( data=means.plot, aes(x=gen, y=6.5, label =.group), col="red") +
facet_wrap(~week) + # ein Plot pro Woche
theme_bw() +
labs(main="Adj. Sortenmittelwerte pro Woche",
y="Adjustierter Ertragsmittelwert ± 95% Konfidenzintervall", x="Sorte",
caption="Getrennt für jede Woche gilt: Mittelwerte, die mit einem gemeinsamen Buchstaben versehen sind,\n sind laut Tukey-test nicht signifikant voneinander verschieden.")
Bei Fragen kannst du mir gerne schreiben!
schmidtpaul@hotmail.de