Alternative all'ANOVA a senso unico per i dati eteroschedastici


36

Ho dati da 3 gruppi di biomassa di alghe ( UN , B , C ) che contengono campioni di dimensioni diverse ( nUN=15 , nB=13 , nC=12 ) e vorrei fare un confronto se questi gruppi appartengono alla stessa popolazione .

L'ANOVA a senso unico sarebbe sicuramente la strada da percorrere, tuttavia dopo aver condotto test di normalità sui miei dati, l'eteroschedascità sembra essere il problema principale. I miei dati grezzi, senza alcuna trasformazione, hanno prodotto un rapporto di varianze ( Fmax=19.1 ) che è molto più alto del valore critico ( ) e quindi non posso eseguire ANOVA a senso unico.Fcriot=4.16

Ho anche provato la trasformazione per normalizzare i miei dati. Anche dopo le prove di varie trasformazioni (log, radice quadrata, square), la più bassa prodotta dopo la trasformazione con una trasformazione era , che era ancora più alta rispetto a . log 10 7.16 F c r i tFmaxlog107.16Fcrit

Qualcuno qui può consigliarmi su dove andare da qui? Non riesco a pensare ad altri metodi di trasformazione per normalizzare i dati. Esistono alternative a un ANOVA a senso unico?

PS: i miei dati non elaborati sono di seguito:

A: 0.178 0.195 0.225 0.294 0.315 0.341 0.36  0.363 0.371 0.398 0.407 0.409 0.432 
   0.494 0.719
B: 0.11  0.111 0.204 0.416 0.417 0.441 0.492 0.965 1.113 1.19  1.233 1.505 1.897
C: 0.106 0.114 0.143 0.435 0.448 0.51  0.576 0.588 0.608 0.64  0.658 0.788 0.958

2
Il test F mostra chiaramente che i gruppi non appartengono alla stessa popolazione. Il prossimo passo sarebbe interpretare questo risultato, iniziando con visualizzazioni chiare e descrizione quantitativa dei dati suddivisi per gruppo.
whuber

Ci sono anche i test di permutazione nel pacchetto di monete. Per ANOVA sarebbe la funzione oneway_test. Esempio Quick-R: statmethods.net/stats/resampling.html
user16263

Risposte:


73

Esistono diverse opzioni disponibili quando si trattano dati eteroscedastici. Sfortunatamente, nessuno di loro è garantito per funzionare sempre. Ecco alcune opzioni con cui ho familiarità:

  • trasformazioni
  • Welch ANOVA
  • minimi quadrati ponderati
  • regressione robusta
  • eteroscedasticità coerenti errori standard
  • bootstrap
  • Test di Kruskal-Wallis
  • regressione logistica ordinale

Aggiornamento: Ecco una dimostrazione R di alcuni modi per adattare un modello lineare (ad esempio, un ANOVA o una regressione) quando si ha eteroscedasticità / eterogeneità della varianza.

Cominciamo dando un'occhiata ai tuoi dati. Per comodità, li ho caricati in due frame di dati chiamati my.data(che è strutturato come sopra con una colonna per gruppo) e stacked.data(che ha due colonne: valuescon i numeri e indcon l'indicatore del gruppo).

Possiamo formalmente testare l' eteroscedasticità con il test di Levene:

library(car)
leveneTest(values~ind, stacked.data)
# Levene's Test for Homogeneity of Variance (center = median)
#       Df F value   Pr(>F)   
# group  2  8.1269 0.001153 **
#       38                    

Abbastanza sicuro, hai l'eteroscedasticità. Controlleremo per vedere quali sono le variazioni dei gruppi. Una regola empirica è che i modelli lineari sono abbastanza robusti rispetto all'eterogeneità della varianza purché la varianza massima non sia superiore a maggiore della varianza minima, quindi troveremo anche quel rapporto: 4×

apply(my.data, 2, function(x){ var(x, na.rm=T) })
#          A          B          C 
# 0.01734578 0.33182844 0.06673060 
var(my.data$B, na.rm=T) / var(my.data$A, na.rm=T)
# [1] 19.13021

Le tue variazioni differiscono sostanzialmente, con il più grande B, essendo il più piccolo,. Questo è un livello problematico di eteroscedsaticità. 19×A

parallel.universe.data2.7B.7Cper mostrare come funzionerebbe:

parallel.universe.data = with(my.data, data.frame(A=A, B=B+2.7, C=C+.7))
apply(parallel.universe.data,       2, function(x){ var(x, na.rm=T) })
#          A          B          C 
# 0.01734578 0.33182844 0.06673060 
apply(log(parallel.universe.data),  2, function(x){ var(x, na.rm=T) })
#          A          B          C 
# 0.12750634 0.02631383 0.05240742 
apply(sqrt(parallel.universe.data), 2, function(x){ var(x, na.rm=T) })
#          A          B          C 
# 0.01120956 0.02325107 0.01461479 
var(sqrt(parallel.universe.data$B), na.rm=T) / 
var(sqrt(parallel.universe.data$A), na.rm=T)
# [1] 2.074217

L'uso della trasformazione con radice quadrata stabilizza abbastanza bene questi dati. Puoi vedere il miglioramento per i dati dell'universo parallelo qui:

inserisci qui la descrizione dell'immagine

Invece di provare diverse trasformazioni, un approccio più sistematico consiste nell'ottimizzare il parametro Box-Cox λλ=.5λ=0

boxcox(values~ind, data=stacked.data,    na.action=na.omit)
boxcox(values~ind, data=stacked.pu.data, na.action=na.omit) 

inserisci qui la descrizione dell'immagine

Fdf = 19.445df = 38

oneway.test(values~ind, data=stacked.data, na.action=na.omit, var.equal=FALSE)
#  One-way analysis of means (not assuming equal variances)
# 
# data:  values and ind
# F = 4.1769, num df = 2.000, denom df = 19.445, p-value = 0.03097

Un approccio più generale consiste nell'utilizzare i minimi quadrati ponderati . Poiché alcuni gruppi ( B) si estendono di più, i dati in tali gruppi forniscono meno informazioni sulla posizione della media rispetto ai dati di altri gruppi. Possiamo consentire al modello di incorporarlo fornendo un peso per ciascun punto dati. Un sistema comune è usare il reciproco della varianza di gruppo come peso:

wl = 1 / apply(my.data, 2, function(x){ var(x, na.rm=T) })
stacked.data$w = with(stacked.data, ifelse(ind=="A", wl[1], 
                                           ifelse(ind=="B", wl[2], wl[3])))
w.mod = lm(values~ind, stacked.data, na.action=na.omit, weights=w)
anova(w.mod)
# Response: values
#           Df Sum Sq Mean Sq F value  Pr(>F)  
# ind        2   8.64  4.3201  4.3201 0.02039 *
# Residuals 38  38.00  1.0000                  

Fp4.50890.01749

inserisci qui la descrizione dell'immagine

zt50100N

1 / apply(my.data,       2, function(x){ var(x, na.rm=T) })
#         A         B         C 
# 57.650907  3.013606 14.985628 
1 / apply(my.data,       2, function(x){ IQR(x, na.rm=T) })
#        A        B        C 
# 9.661836 1.291990 4.878049 

rw = 1 / apply(my.data, 2, function(x){ IQR(x, na.rm=T) })
stacked.data$rw = with(stacked.data, ifelse(ind=="A", rw[1], 
                                            ifelse(ind=="B", rw[2], rw[3])))
library(robustbase)
w.r.mod = lmrob(values~ind, stacked.data, na.action=na.omit, weights=rw)
anova(w.r.mod, lmrob(values~1,stacked.data,na.action=na.omit,weights=rw), test="Wald")
# Robust Wald Test Table
# 
# Model 1: values ~ ind
# Model 2: values ~ 1
# Largest model fitted by lmrob(), i.e. SM
# 
#   pseudoDf Test.Stat Df Pr(>chisq)  
# 1       38                          
# 2       40    6.6016  2    0.03685 *

I pesi qui non sono così estremi. I mezzi di gruppo previsti divergenti ( A: WLS 0.36673, robusto 0.35722, B: WLS 0.77646, robusto 0.70433, C: WLS 0.50554, robusto 0.51845), con i mezzi Bed Cessendo meno tirati dai valori estremi.

In econometria l' errore standard Huber-White ("sandwich") è molto popolare. Come per la correzione di Welch, ciò non richiede di conoscere le variazioni a priori e non richiede di stimare pesi dai dati e / o dipendere da un modello che potrebbe non essere corretto. D'altra parte, non so come incorporarlo con un ANOVA, il che significa che li ottieni solo per i test dei singoli codici fittizi, il che mi sembra meno utile in questo caso, ma lo dimostrerò comunque:

library(sandwich)
mod = lm(values~ind, stacked.data, na.action=na.omit)
sqrt(diag(vcovHC(mod)))
# (Intercept)        indB        indC 
#  0.03519921  0.16997457  0.08246131 
2*(1-pt(coef(mod) / sqrt(diag(vcovHC(mod))), df=38))
#  (Intercept)         indB         indC 
# 1.078249e-12 2.087484e-02 1.005212e-01 

vcovHCttt

Per Rcarwhite.adjustp

Anova(mod, white.adjust=TRUE)
# Analysis of Deviance Table (Type II tests)
# 
# Response: values
#           Df      F  Pr(>F)  
# ind        2 3.9946 0.02663 *
# Residuals 38                 
# ---
# Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

FFp

mod    = lm(values~ind, stacked.data, na.action=na.omit)
F.stat = anova(mod)[1,4]
  # create null version of the data
nullA  = my.data$A - mean(my.data$A)
nullB  = my.data$B - mean(my.data$B, na.rm=T)
nullC  = my.data$C - mean(my.data$C, na.rm=T)
set.seed(1)
F.vect = vector(length=10000)
for(i in 1:10000){
  A = sample(na.omit(nullA), 15, replace=T)
  B = sample(na.omit(nullB), 13, replace=T)
  C = sample(na.omit(nullC), 13, replace=T)
  boot.dat  = stack(list(A=A, B=B, C=C))
  boot.mod  = lm(values~ind, boot.dat)
  F.vect[i] = anova(boot.mod)[1,4]
}
1-mean(F.stat>F.vect)
# [1] 0.0485

n

kruskal.test(values~ind, stacked.data, na.action=na.omit)
#  Kruskal-Wallis rank sum test
# 
# data:  values by ind
# Kruskal-Wallis chi-squared = 5.7705, df = 2, p-value = 0.05584

Sebbene il test Kruskal-Wallis sia sicuramente la migliore protezione contro gli errori di tipo I, può essere utilizzato solo con una singola variabile categoriale (ovvero senza predittori continui o progetti fattoriali) e ha il minor potere di tutte le strategie discusse. Un altro approccio non parametrico consiste nell'utilizzare la regressione logistica ordinale . Questo sembra strano per molte persone, ma devi solo supporre che i tuoi dati di risposta contengano informazioni ordinali legittime, cosa che sicuramente fanno, altrimenti anche ogni altra strategia di cui sopra non è valida:

library(rms)
olr.mod = orm(values~ind, stacked.data)
olr.mod
# Model Likelihood          Discrimination          Rank Discrim.    
# Ratio Test                 Indexes                Indexes       
# Obs            41    LR chi2      6.63    R2                  0.149    rho     0.365
# Unique Y       41    d.f.            2    g                   0.829
# Median Y    0.432    Pr(> chi2) 0.0363    gr                  2.292
# max |deriv| 2e-04    Score chi2   6.48    |Pr(Y>=median)-0.5| 0.179
#                      Pr(> chi2) 0.0391

chi2Discrimination IndexespProduce di 0.0363.


1
Adoro le tue risposte! Ora c'è un barlume di casa per gestire i miei dati! :) Di tutti questi approcci, che consiglieresti vivamente per il mio tipo di dati, in particolare per quanto riguarda la fornitura di sufficiente potere statistico? Non sono troppo entusiasta di usare un approccio non parametrico, ad esempio il test di Kruskal-Wallis poiché ciò significherebbe che probabilmente commetterò un errore di tipo I? Correggimi se sbaglio.
Rick L.

2
Elaborerò altro materiale qui quando ne avrò la possibilità, ma no, il test Kruskal-Wallis probabilmente fornisce la migliore protezione dagli errori di tipo I. OTOH, potrebbe non essere l'opzione più potente che hai.
gung - Ripristina Monica

4
Si noti che in libreria carc'è anche la possibilità di impostare la gestione white.adjust=Tdell'eteroschedacità utilizzando errori standard corretti per l'
eteroscedasticità

3
Sì, lo menziona solo per lm, ma sembra funzionare anche per aov(le opzioni white.adjustsono white.adjust=c(FALSE, TRUE, "hc3", "hc0", "hc1", "hc2", "hc4")- per maggiori informazioni, vedi ?hccm)
Tom Wenseleers,

1
@Nakx, no, non che io sappia. È una regola empirica; è improbabile che si trovi in ​​un articolo nella letteratura statistica. Probabilmente è in alcuni libri di testo introduttivi.
gung - Ripristina Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.