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: values
con i numeri e ind
con 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.data
2.7B
.7C
per 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:
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)
Fdf = 19.445
df = 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.5089
0.01749
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 B
ed C
essendo 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
vcovHC
ttt
Per R
car
white.adjust
p
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
chi2
Discrimination Indexes
pProduce di 0.0363
.