Na początek kilka ważnych faktów o analizie wariancji.
Model strukturalny leżący u podstaw analizy wariancji ma następującą postać:
\[X_{ij} = \mu + \tau_j + \epsilon_{ij}\]
Przy analizie wariancji mamy do czynienia z następującym układem hipotez:
\[ H_0: \mu_1 = \mu_2 = ... = \mu_k \]
W tym wypadku \(\mu_1\) do \(\mu_k\) odnoszą się do średnich w populacji dla poszczególnych poziomów czynnika grupującego. Hipoteza zerowa stwierdza, że średnie te są sobie równe.
\[ H_A: \neg H_0 \]
Hipoteza alternatywna (ta, którą chcemy uzasadnić obalając hipotezę zerową!) głosi, że \(H_0\) jest fałszem. Warto zwrócić uwagę, że \(H_0\) jest sformułowana w bardzo specyficzny sposób i może okazać się fałszywa na wiele sposobów. Wszystkie średnie mogą być od siebie różne albo tylko niektóre. Sam test jednak powie nam jedynie tyle, że powinniśmy odrzucić hipotezę zerową. Czy możemy mimo wszystko dowiedzieć się czegoś na temat tego, w jaki sposób \(H_0\) okazała się fałszywa? Tak! Żeby to zrobić, musimy przeprowadzić analizę post-hoc. O tym jednak powiemy sobie więcej na koniec tej części kursu (Uwaga! Możemy to zrobić wyłącznie wówczas, gdy ANOVA dała istotny statystycznie wynik).
Logika analizy wariancji polega na porównywaniu wariancji międzygrupowej i wewnątrzgrupowej. Korzystamy z faktu, że – przy założeniu hipotezy zerowej – na podstawie obu możemy skonstruować estymator wariancji populacji. Jeśli hipoteza zerowa jest zaś fałszywa, to estymator ,,działa”’ tylko w jednym przypadku. Mechanizm ten będzie jaśniejszy, jeśli prześledzimy analizę wariancji na przykładzie.
Stwórzmy ramkę danych, w której mamy poziomów czynnika (jakiegokolwiek, tutaj użyhemy niewiele znaczacych poziomów ,,bardzo mało’‘, ,,mało’‘, ,,średnio’‘, ,,dużo’’ oraz ,,bardzo dużo’’), ale wszystkie wartości pochodzą z populacji o tych samych parametrach (takiej samej średniej oraz odchyleniu standardowym). Łatwo zauważyć, że jest to sytuacja, w której hipoteza zerowa jest prawdziwa.
# Nasz czynnik grupujący ma 5 poziomów, po 200 obserwacji każdy
faktor <- rep(c('bardzo mało', 'mało', 'średnio', 'dużo', 'bardzo dużo'), 200)
# Losujemy 1000 obserwacji z rozkładu o mu = 10 i sd = 5; po 200 na każdy poziom czynnika
wartosci <- rnorm(1000, mean = 10, sd = 5)
# Tworzymy z naszych wylosowanych obserwacji ramkę danych
df <- data.frame(condition = faktor, value = wartosci)
# Wyświetlamy pierwsze 6 wierszy
head(df)
## condition value
## 1 bardzo mało 4.126729
## 2 mało -2.742236
## 3 średnio 13.585048
## 4 dużo 8.888261
## 5 bardzo dużo 7.021355
## 6 bardzo mało 12.580226
Obliczmy wariancje w każdej z grup za pomocą funkcji
tapply
.
Krótkie przypomnienie jak działa tapply
. Funkcja ta
bierze wartości przekazane jako pierwszy argument (tutaj:
df$value
) i dzieli je według czynnika przekazanego jako
drugi argument (tutaj: df$condition
). Następnie do każdego
elementu takiego podziału stosuje funkcję przekazaną jako trzeci
argument – tutaj jest to var
obliczające wariancję. Funkcja
ta zwróci więc tyle wartości wariancji z próby (var
) ile
mieliśmy poziomów czynnika grupującego (df$condition
) – w
naszym wypadku 5.
tapply(df$value, df$condition, var)
## bardzo dużo bardzo mało dużo mało średnio
## 25.63169 24.64649 27.38787 25.30736 26.61042
W następnym kroku obliczymy średnią z obliczonych właśnie wariancji. W ten sposób skonstruowaliśmy pierwszy z dwóch estymator wariancji populacji – obliczyliśmy wariancję w każdej z grup z osobna i wyciągnęliśmy z nich średnią.
mse <- mean(tapply(df$value, df$condition, var))
mse
## [1] 25.91676
Częściej spotkają się Państwo ze sposobem obliczania, w którym najpierw obliczamy sumę kwadratów a następnie średni kwadrat według następujących wzorów:
\[ SSE = \sum_{i=1}^{k} \sum_{j=1}^{n_i}(X_{ij} - \bar{X_i})^2 \]
\[ MSE = \frac{SSE}{n-k} \] Sposób ten jest równoważny temu, co zrobiliśmy wcześniej, co można prześledzić wykonując umieszczony poniżej kod. W moim przekonaniu w tym sposobie nieco gorzej widać,,o co chodzi’’.
# Dzielimy wyjściową ramkę danych według czynnika z kolumny `condition`
df_s <- split(df, df$condition)
# Obliczamy sumę kwadratów odchyleń
sse <- sum(sapply(df_s, function(x){sum((x$value - mean(x$value))^2)}))
sse
## [1] 25787.18
# Obliczamy średni kwadrat
mse <- sse/(1000-5)
mse
## [1] 25.91676
Nasz drugi estymator wariancji w populacji skonstruujemy, korzystajac z nabytej wcześniej wiedzy dotyczącej odchylenia standardowego średnich z próby. Innymi słowy - kolejny raz skorzystamy z Centralnego Twierdzenia Granicznego!
Wiemy z CTG, że:
\[ \frac{\sigma^2}{n} = s^2_{\bar{X}} \]
Ten wzór możemy przekształcić w taki sposób, że uzyskamy:
\[ \sigma^2 = ns^2_{\bar{X}} \] Widzimy więc, że wariancja w populacji (\(\sigma^2\)) to po prostu wariancja średnich z próby (\(s^2_{\bar{X}}\)) pomnożona przez liczebność próby.
W takim razie, żeby wyestymować wariancję w populacji, powinniśmy obliczyć wariancję średnich z próby:
# Za pomocą `tapply` obliczamy średnią dla każdego poziomu `df$condition`
# Takich średnich w naszym wypadku otrzymujemy 5
# `var` oblicza wariancje dla tych 5 średnich
var(tapply(df$value, df$condition, mean))
## [1] 0.1256466
# Żeby otrzymać estymator wariancji w populacji, musimy przemnożyć nasz wynik przez n = 200
# Bo tyle obserwacji mamy na każdy poziom czynnika grupującego
mstreat <- var(tapply(df$value, df$condition, mean))*200
mstreat
## [1] 25.12931
Częściej spotkają się Państwo ze sposobem obliczania, w którym najpierw obliczamy sumę kwadratów a następnie średni kwadrat według następujących wzorów:
\[ SSA = \sum_{i=1}^{k} (\bar{X_i} - \bar{X})^2 n_i \]
\[ MSA = \frac{SSA}{k-1} \]
ssa <- (sum((tapply(df$value, df$condition, mean) - mean(df$value))^2))*200
ssa
## [1] 100.5172
msa <- ssa/(5-1)
msa
## [1] 25.12931
Zwróćmy uwagę, że obliczona przez nas wartość będzie dobrym estymatorem wariancji w populacji wyłącznie wtedy, gdy próby pochodzą z tej samej populacji, ponieważ tylko takiej sytuacji dotyczy przywołane sformułowanie Centralnego Twierdzenia Granicznego! W innym wypadku tak obliczony ,,estymator’’ nie będzie działać i ten fakt wykorzystamy do skonstruowania testu statystycznego!
Statystyka testowa \(F\) będzie proporcją między dwiema obliczonymi własnie wartościami. W przypadku, kiedy grupa nie ma znaczenia (czyli, biorac pod uwagę nasz model teoretyczny, \(\theta = 0\)), spodziewamy się, że statystyka ta będzie wynosić 1 (taka jest jej wartość oczekiwana). Dlaczego? Ponieważ obie wartości są estymatorami tego samego parametru!
\[ F = \frac{MSA}{MSE} \]
f_stat <- mstreat/mse
f_stat
## [1] 0.9696161
Czy wielkośc statystyki testowej, którą otrzymaliśmy, jest duża czy mała? Możemy zobaczyć, jaki rozkład ma interesująca nas statystyka testowa, przeprowadzając odpowiednią symulację.
Możemy również posłużyc się rozkładem teoretycznym \(F\). Ma on dwa parametry - liczbę stopni swobody licznika (czyli \(k-1\), gdzie \(k\) to liczba czynników) oraz liczbę stopni swobody licznika (\(n-k\), gdzie \(k\) to liczba czynników a \(n\) to całkowita liczebność próby).
Na początek symulacja. Sprawdzamy, jaki rozkład ma statystyka testowa \(F\) przy założeniu prawdziwości hipotezy zerowej. W tym celu powtórzymy nasz eksperyment 10 000 razy i obliczamy dla każdego powtórzenia statystykę \(F\). Następnie nakładamy na histogram statystyk \(F\) uzyskanych w symulacji odpowiednią krzywą z teoretycznego rozkładu \(F\). Finalnie możemy zobaczyć, że histogram z symulowanymi wartościami \(F\) oraz krzywa rozkładu teoretycznego świetnie do siebie pasują.
f_val <- replicate(10000,
{
# Najpierw symulujemy naszą próbę
faktor = rep(c('bardzo mało', 'mało', 'średnio', 'dużo', 'bardzo dużo'),
200)
wartosci <- rnorm(1000, mean = 10, sd = 5)
df <- data.frame(condition = faktor, value = wartosci)
# Obliczamy MSE i MS_Treatment
mse <- mean(tapply(df$value, df$condition, var))
mstreat <- var(tapply(df$value, df$condition, mean)) * 200
# Obliczamy statystykę testową F czyli iloraz tych dwóch wartości
mstreat / mse
})
# Wyniki symulacji możemy przedstawić na histogramie
hist(f_val, freq = FALSE)
curve(df(x, df1 = 5-1, df2 = 995), add = TRUE)
Skoro obliczyliśmy już statystykę testową \(F\), to musimy jeszcze sprawdzić, czy
uzyskanie takiej lub bardziej skrajnej wartości jest mniej prawdopodobne
niż \(5\%\) (przyjmijmy taki
konwencjonalny poziom \(\alpha\)). Żeby
to zrobić, musimy posłużyć się dystrybuantą rozkładu \(F\), czyli funkcją pf
. Rozkład
\(F\) ma dwa parametry (dwie liczby
stopni swobody). df1
będzie tutaj wynosiła \(k-1\) gdzie \(k\) jest liczbą poziomów czynnika
grupującego, a df2
będzie wynosiło \(n - k\) gdzie \(n\) to (całkowita) liczebność próby.
pf(f_stat, df1 = 5-1, df2 = 995, lower.tail = FALSE)
## [1] 0.4232132
Obliczyliśmy właśnie p-wartość i tym samym przeprowadziliśmy naszą
pierwszą analizę wariancji. Oczywiście przeprowadzając analizę wariancji
nie musimy wykonywać ręcznie obliczeń. Jak zwykle możemy się posłużyć
wbudowaną funkcją R, którą w tym wypadku jest aov
.
summary(aov(value ~ condition, df))
## Df Sum Sq Mean Sq F value Pr(>F)
## condition 4 101 25.13 0.97 0.423
## Residuals 995 25787 25.92
Prześledźmy analizę wariancji na przykładzie. W bibliotece
standardowej R znajduje się zbiór danych PlantGrowth
,
dotyczący wpływu pewnych czynników na obfitość plonów. W jednej kolumnie
mamy wagę roślin, w drugiej to, do jakiej grupy przynależy dana
obserwercja. Grupy były trzy - dwie eksperymentalne (trt1
oraz trt2
) i oraz grupa kontrolna (ctrl
). Za
pomocą analizy wariancji możemy zbadać, czy warunki eksperymentalne
miały wpływ na wagę roślin.
Nasza hipoteza zerowa będzie stwierdzać, że średnia waga jest taka sama we wszystkich grupach. Innymi słowy, że obserwacje dla wszystkich trzech poziomów czynnika grupującego pochodzą z populacji o tym samym parametrze średniej. Hipoteza alterantywna będzie zaś temu zaprzeczać. Oto zbiór danych (proszę wybaczyc, że wydrukuje go w całości, ale jest dość krótki):
PlantGrowth
## weight group
## 1 4.17 ctrl
## 2 5.58 ctrl
## 3 5.18 ctrl
## 4 6.11 ctrl
## 5 4.50 ctrl
## 6 4.61 ctrl
## 7 5.17 ctrl
## 8 4.53 ctrl
## 9 5.33 ctrl
## 10 5.14 ctrl
## 11 4.81 trt1
## 12 4.17 trt1
## 13 4.41 trt1
## 14 3.59 trt1
## 15 5.87 trt1
## 16 3.83 trt1
## 17 6.03 trt1
## 18 4.89 trt1
## 19 4.32 trt1
## 20 4.69 trt1
## 21 6.31 trt2
## 22 5.12 trt2
## 23 5.54 trt2
## 24 5.50 trt2
## 25 5.37 trt2
## 26 5.29 trt2
## 27 4.92 trt2
## 28 6.15 trt2
## 29 5.80 trt2
## 30 5.26 trt2
Na początek zawsze warto stworzyć wizualizację danych - między innymi po to, aby skontrolować to, czy nasze dane spełniają założenia analizy wariancji. Zasadniczo założenia są, podobnie jak przy regresji, dwa - równa wariancja oraz normalność w grupach. Na podstawie wykresu pudełkowego możemy “na oko” ocenić, czy mamy prawo zakładać, że założenia są spełnione. Te dane nie wyglądają bardzo źle – nie mamy powodów by powiedzieć, że w sposób oczywisty dane nie spełniają założeń.
library(ggplot2)
qplot(x = group, y = weight, geom = 'boxplot', data = PlantGrowth)
Normalność w grupach możemy “na oko” ocenić za pomocą wykresu kwantyl-kwantyl dla każdej grupy z osobna. Przy małych próbach może to okazać się to bardziej pomocne niż formalne testy na normalność rozkładu. Nie chcemy, żeby w sposób wyraźny i podpadający pod jakiś wzór odbiegały od przekątnej.
p <- ggplot(PlantGrowth, aes(sample = weight))
# scales = "free" sprawia, że skale na każdym wykresie są osobne.
p +
stat_qq() +
facet_grid(group~., scales = 'free')
Następnie możemy “ręcznie” przeprowadzić analizę wariancji w R. Najpierw musimy policzyć sumę kwadratów między grupami i wewnatrz grup, następnie obliczyć średni kwadrat w obu przypadkach. Po obliczeniu tych dwóch rzeczy możemy obliczyć statystykę testową F, dzieląc jedną przez drugą, i sprawdzić, korzystając z dystrybuanty rozkładu F, czy odrzucamy hipotezę zerową.
df_s <- split(PlantGrowth, PlantGrowth$group)
sse <- sum(sapply(df_s, function(x){sum((x$weight - mean(x$weight))^2)}))
print('SSE')
## [1] "SSE"
sse
## [1] 10.49209
mse <- sse/(nrow(PlantGrowth) - (length(levels(PlantGrowth$group))))
print('MSE')
## [1] "MSE"
mse
## [1] 0.3885959
ssa <-(sum((tapply(PlantGrowth$weight, PlantGrowth$group, mean) -
mean(PlantGrowth$weight))^2))*(nrow(PlantGrowth)/3)
print('SSA')
## [1] "SSA"
ssa
## [1] 3.76634
msa <- ssa/(length(levels(PlantGrowth$group))-1)
print('MSA')
## [1] "MSA"
msa
## [1] 1.88317
f_stat <- msa/mse
print('Statystyka testowa F')
## [1] "Statystyka testowa F"
f_stat
## [1] 4.846088
pval <- pf(f_stat, (length(levels(PlantGrowth$group))-1), (nrow(PlantGrowth) - (length(levels(PlantGrowth$group)))),
lower.tail = FALSE)
print('Wartość p')
## [1] "Wartość p"
pval
## [1] 0.01590996
To samo możemy wykonać za pomoca wbudowanej funkcji aov
.
Jak widzimy, uzyskane w eksperymencie rezultaty uprawniają nas do
odrzucenia hipotezy zerowej. Możemy więc stiwerdzić, że proby nie
pochodzą z populacji o takich samych średnich.
fit <- aov(weight ~ group, data = PlantGrowth)
summary(fit)
## Df Sum Sq Mean Sq F value Pr(>F)
## group 2 3.766 1.8832 4.846 0.0159 *
## Residuals 27 10.492 0.3886
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Często nie tylko interesuje nas fakt, że odrzuciliśmy hipotezę,
zgodnie z którą wszystkie próby pochodzą z populacji o tej samej
średniej, ale chcielibyśmy móc ustalić, między którymi grupami występują
statytycznie istotne różnice. Tutaj przydatne okazują się tzw. testy
post-hoc. Możemy ich użyć tylko jeśli nasza główna metoda
(tutaj – ANOVA) dała statystycznie istotny wynik. Zaletą testów
post-hoc jest to, że uwzględniają fakt, że są post-hoc
i podają bardziej wiarygodne wyniki niż np. seria testów t.
Testów post-hoc, które możemy zastosować przeprowadziwszy
analizę wariancji jest kilka, najbardziej popularnym jest chyba test HSD
(honest significant difference) Tukeya. Możemy taki test łatwo
przeprowadzić za pomocą wbudowanej w R funkcji TukeyHSD
. Na
poniższym wydruku widzimy, że statystycznie istotna jest różnica
pomiędzy dwoma grupami eksperymentalnymi (ale już nie między każdą z
nich a grupą kontrolną!).
TukeyHSD(fit)
## Tukey multiple comparisons of means
## 95% family-wise confidence level
##
## Fit: aov(formula = weight ~ group, data = PlantGrowth)
##
## $group
## diff lwr upr p adj
## trt1-ctrl -0.371 -1.0622161 0.3202161 0.3908711
## trt2-ctrl 0.494 -0.1972161 1.1852161 0.1979960
## trt2-trt1 0.865 0.1737839 1.5562161 0.0120064
Możemy wyniki naszej analizy przedstawić w formie graficznej za
pomocą funkcji plot
. Na osi Y mamy zestawienia ze sobą
poziomów czynnika (tutaj - group
), na osi X zaś 95%
przedziały ufności różnicy średnich między nimi. Istotne statystycznie
są te różnice, które nie obejmują zera (proszę przypomnieć sobie
testowanie hipotez za pomocą przedziałów ufności).
plot(TukeyHSD(fit))
W przypadku analizy wariancji standardowo używa się miary \(\eta^2\). Oblicza się ją dzięląc międzygrupową sumę kwadratów przez całkowitą sumę kwadratów.
# Całkowita suma kwadratów
sst <- sum((PlantGrowth$weight - mean(PlantGrowth$weight)) ** 2)
eta_kw = ssa/sst
eta_kw
## [1] 0.2641483
Interpretacja tej miary jest analogiczna jak w przypadku znanego już
Państu \(R^2\) i jest to stosunek
zmienności wyjaśnianej przez nasz predyktor do całkowitej zmienności
zmiennej zależnej. Jeżeli mamy taką ochotę, to mozemy skorzystać z
funkcji obliczającą tę miarę w jednej z wielu bibliotek, które taką
funkcję oferują. Ja skorzystałem tutaj z DescTools
i
funkcji EtaSq
(ale jest ich wiele!).
DescTools::EtaSq(fit)
## eta.sq eta.sq.part
## group 0.2641483 0.2641483
Istnieją bardzo podobne podobieństwa między analizą wariancji i modelem liniowym. Można nawet stwierdzić, że analiza wariancji to po prostu szczególny przypadek modelu liniowego. Fakt, że omawiane są oddzielnie podyktowane jest w znacznej mierze zaszłościami historycznymi. W artykule dostępnym w sekcji dla zalogowanych “Multiple Regression As A General Data-Analytic System” autorstwa Jacoba Cohena znajduje się dobre wyjaśnienie wielu zawiłości z tym związanych.
Zobaczmy sobie podsumowanie jednoczynnikowej analizy wariancji oraz modelu liniowego z jednym kategorialnym predyktorem.
Analiza wariancji:
fit_aov <- aov(weight ~ group, data = PlantGrowth)
summary(fit_aov)
## Df Sum Sq Mean Sq F value Pr(>F)
## group 2 3.766 1.8832 4.846 0.0159 *
## Residuals 27 10.492 0.3886
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
DescTools::EtaSq(fit_aov)
## eta.sq eta.sq.part
## group 0.2641483 0.2641483
Model liniowy:
fit_lm <- lm(weight ~ group, data = PlantGrowth)
summary(fit_lm)
##
## Call:
## lm(formula = weight ~ group, data = PlantGrowth)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.0710 -0.4180 -0.0060 0.2627 1.3690
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.0320 0.1971 25.527 <2e-16 ***
## grouptrt1 -0.3710 0.2788 -1.331 0.1944
## grouptrt2 0.4940 0.2788 1.772 0.0877 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.6234 on 27 degrees of freedom
## Multiple R-squared: 0.2641, Adjusted R-squared: 0.2096
## F-statistic: 4.846 on 2 and 27 DF, p-value: 0.01591
Zwróćmy uwagę na kilka rzeczy. Dla analizy wariancji hipoteza zerowa wygląda tak:
\[ H_0: \mu_1 = \mu_2 = \mu_3 \]
Dla modelu liniowego hipotezą zerową było:
\[ H_0: R^2 = 0 \]
Hipotezy te, chociaż pozornie brzmiące różnie, w rzeczywistości są sobie matematycznie równoważne i można je przetestować tym samym testem (na co wskazuje nam w wydruku R taka sama wartość statystyki testowej \(F\)). Możemy to zilustrować takim oto rozumowaniem.
Jeżeli hipoteza zerowa analizy wariancji jest prawdziwa (czyli średnie w grupach są w populacji identyczne), to znajomość tego, do której grupy dana obserwacja należy, nie pozwala nam przewidywać wartości tej obserwacji lepiej niż po prostu znajomość ogólnej średniej. W takiej sytuacji nasz model nie wyjaśnia żadnej części wariancji zmiennej zależnej czyli \(R^2\) jest równe zeru. Warto również zwrócić uwagę na to, że \(R^2\) jest równy obliczonemu przez nas \(\eta^2\), co nie powinno dziwić biorać pod uwagę sposób w jaki oblicza się obie te wartości.
Na kolejnych zajęciach, gdy będziemy zajmować się nieco bardziej skomplikowanymi wariantami analizy wariancji (w których uwzględniamy więcej czynników), zobaczymy w jaki sposób tę paralelę między modelem liniowym a analizą wariancji można pociągnąć dalej.