Alternatives à l'ANOVA unidirectionnelle pour les données hétéroscédastiques

36

J'ai des données de 3 groupes de biomasse d'algues ( , , ) qui contiennent des tailles d'échantillon inégales ( , , ) et j'aimerais comparer si ces groupes appartiennent à la même population.B C n A = 15 n B = 13 n C = 12ABCnA=15nB=13nC=12

Une ANOVA à un facteur serait définitivement la solution, cependant, lors de tests de normalité de mes données, l'hétéroskédascité semble être le problème principal. Mes données brutes, sans aucune transformation, ont produit un rapport de variances ( ) très supérieur à la valeur critique ( F _ {\ rm crit} = 4.16 ) et ne permettaient donc pas d'effectuer une ANOVA à un facteur. .Fmax=19.1Fcrit=4.16

J'ai aussi essayé la transformation pour normaliser mes données. Même après des essais de diverses transformations (log, racine carrée, carré), le F _ {\ max} le plus bas Fmaxproduit après transformation avec une transformation log10 était de 7.16 , ce qui était encore plus élevé par rapport à Fcrit .

Est-ce que quelqu'un ici peut me conseiller sur où aller d'ici? Je ne peux pas penser à d'autres méthodes de transformation pour normaliser par les données. Existe-t-il des alternatives à l'ANOVA à sens unique?

PS: mes données brutes sont ci-dessous:

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
Rick L.
la source
2
Le test F montre clairement que les groupes ne font pas partie de la même population. La prochaine étape consisterait à interpréter ce résultat, en commençant par des visualisations claires et une description quantitative des données ventilées par groupe.
whuber
Il y a aussi les tests de permutation dans le paquet de pièces. Pour ANOVA, ce serait la fonction oneway_test. Exemple Quick-R: statmethods.net/stats/resampling.html
user16263

Réponses:

73

Un certain nombre d'options sont disponibles pour traiter des données hétéroscédastiques. Malheureusement, aucun d’entre eux n’est garanti de toujours fonctionner. Voici quelques options que je connais bien:

  • transformations
  • ANOVA DE WELCH
  • moindres carrés pondérés
  • régression robuste
  • hétéroscedasticité erreurs-types cohérentes
  • bootstrap
  • Test de Kruskal-Wallis
  • régression logistique ordinale

Mise à jour: Voici une démonstration R de quelques manières d’ajuster un modèle linéaire (c’est-à-dire une ANOVA ou une régression) lorsque vous avez une hétéroscédasticité / hétérogénéité de la variance.

Commençons par examiner vos données. Pour plus de commodité, je les ai chargés dans deux trames de données appelées my.data(qui sont structurées comme ci-dessus avec une colonne par groupe) et stacked.dataqui ont deux colonnes: valuesavec les nombres et indavec l'indicateur de groupe).

Nous pouvons formellement tester l' hétéroscédasticité avec le test de 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                    

Effectivement, vous avez l'hétéroscédasticité. Nous allons vérifier quels sont les écarts des groupes. En règle générale, les modèles linéaires sont assez robustes face à l'hétérogénéité de la variance tant que la variance maximale n'est pas supérieure à supérieure à la variance minimale. Nous allons donc trouver ce rapport également: 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

Vos écarts diffèrent considérablement, le plus grand Bétant le plus petit . C'est un niveau problématique d'hétéroscedsaticité. 19×A

Vous aviez pensé à utiliser des transformations telles que le log ou la racine carrée pour stabiliser la variance. Cela fonctionnera dans certains cas, mais les transformations de type Box-Cox stabilisent la variance en comprimant les données de manière asymétrique, soit en les comprimant vers le bas, les données les plus élevées étant comprimées au maximum, soit en les resserrant vers le haut avec les données les plus basses. Ainsi, vous avez besoin que la variance de vos données change avec la moyenne pour que cela fonctionne de manière optimale. Vos données présentent une différence de variance énorme, mais une différence relativement faible entre les moyennes et les médianes, c’est-à-dire que les distributions se chevauchent généralement. En tant qu’exercice d’enseignement, nous pouvons en créer parallel.universe.dataen ajoutant à toutes les valeurs et à.72.7B.7Cest de montrer comment cela fonctionnerait:

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'utilisation de la transformation de racine carrée stabilise assez bien ces données. Vous pouvez voir l'amélioration des données de l'univers parallèle ici:

entrez la description de l'image ici

Plutôt que d'essayer différentes transformations, une approche plus systématique consiste à optimiser le paramètre Box-Cox (bien qu'il soit généralement recommandé de l'arrondir à la transformation interprétable la plus proche). Dans votre cas, la racine carrée, , ou le journal, , sont acceptables, mais aucun ne fonctionne réellement. Pour les données de l'univers parallèle, la racine carrée est la meilleure: λλ = 0λ=.5λ=0

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

entrez la description de l'image ici

Puisque ce cas est une ANOVA (c'est-à-dire aucune variable continue), une solution pour traiter l'hétérogénéité consiste à utiliser la correction de Welch au degré de liberté du dénominateur dans le test (nb , une valeur fractionnelle plutôt que ): 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

Une approche plus générale consiste à utiliser les moindres carrés pondérés . Certains groupes ( B) s'étalant davantage, les données de ces groupes fournissent moins d'informations sur l'emplacement de la moyenne que les données des autres groupes. Nous pouvons laisser le modèle incorporer cela en fournissant un poids avec chaque point de données. Un système commun consiste à utiliser l'inverse de la variance de groupe comme pondération:

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                  

Cela donne des valeurs et légèrement différentes de celles de l'ANOVA non pondérée ( , ), mais cela a bien résolu l'hétérogénéité: pFp4.50890.01749

entrez la description de l'image ici

Les moindres carrés pondérés ne constituent toutefois pas une panacée. Un fait inconfortable est que ce n'est que juste si les poids sont juste, ce qui signifie, entre autres choses, qu'ils sont connus a priori. Il ne traite pas non plus de la non-normalité (telle que l'asymétrie) ni des valeurs aberrantes. L’utilisation de pondérations estimées à partir de vos données fonctionne généralement bien, en particulier si vous disposez de suffisamment de données pour estimer la variance avec une précision raisonnable (cette méthode est analogue à l’idée d’utiliser une table au lieu d’une table lorsque vous avez ou plus).t 50 100 Nzt50100degrés de liberté), vos données sont suffisamment normales et vous ne semblez pas avoir de données aberrantes. Malheureusement, vous avez relativement peu de données (13 ou 15 par groupe), quelques biais et peut-être des valeurs aberrantes. Je ne suis pas sûr que ce soit assez grave pour en faire une grosse affaire, mais vous pouvez mélanger les moindres carrés pondérés à des méthodes robustes . Au lieu d'utiliser la variance comme mesure de la dispersion (sensible aux valeurs aberrantes, en particulier avec un faible ), vous pouvez utiliser l'inverse de la plage inter-quartile (non affectée par des valeurs aberrantes pouvant aller jusqu'à 50% dans chaque groupe). Ces poids pourraient ensuite être combinés avec une régression robuste en utilisant une fonction de perte différente comme celle de Tukey's bisquare: N

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 *

Les poids ici ne sont pas aussi extrêmes. Les moyennes des groupes prédites diffèrent légèrement ( A: WLS 0.36673, robuste 0.35722; B: WLS 0.77646, robuste 0.70433; C: WLS 0.50554, solide 0.51845), avec les moyens de Bet Cétant moins tirés par des valeurs extrêmes.

En économétrie, l' erreur type de Huber-White ("sandwich") est très populaire. À l'instar de la correction de Welch, cela ne vous oblige pas à connaître les variances a priori ni à estimer les poids à partir de vos données et / ou à condition d'utiliser un modèle qui pourrait ne pas être correct. D'autre part, je ne sais pas comment incorporer cela dans une ANOVA, ce qui signifie que vous ne les obtenez que pour les tests de codes factices individuels, ce qui me semble moins utile dans ce cas, mais je vais vous en faire la démonstration:

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 

La fonction vcovHCcalcule une matrice de variance-covariance cohérente hétéroscedastique pour vos betas (codes factices), ce que signifient les lettres de l'appel de fonction. Pour obtenir des erreurs types, vous extrayez la diagonale principale et prenez les racines carrées. Pour obtenir des tests pour vos bêta, vous divisez vos estimations de coefficient par les SE et comparez les résultats à la distribution appropriée (à savoir, la distribution avec vos degrés de liberté restants). t tttt

Pour les Rutilisateurs en particulier, @TomWenseleers note dans les commentaires ci-dessous que la fonction ? Anova du carpackage peut accepter un white.adjustargument pour obtenir une valeur pour le facteur en utilisant des erreurs cohérentes d'hétéroscédasticité. 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

Vous pouvez essayer d'obtenir une estimation empirique de la répartition réelle de votre statistique de test en procédant par bootstrap . Tout d'abord, vous créez un vrai null en faisant en sorte que tous les groupes soient exactement égaux. Ensuite, vous rééchantillonnez avec remplacement et calculez votre statistique de test ( ) sur chaque échantillon pour obtenir une estimation empirique de la distribution d'échantillonnage de sous le zéro avec vos données, quel que soit leur statut en termes de normalité ou d'homogénéité. La proportion de cette distribution d'échantillonnage aussi extrême ou plus extrême que votre statistique de test observée correspond à la valeur : F pFFp

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

À certains égards, l’amorçage est l’approche ultime en matière d’hypothèses réduites pour analyser les paramètres (par exemple, les moyennes), mais cela suppose que vos données représentent bien la population, ce qui signifie que vous disposez d’une taille d’échantillon raisonnable. Étant donné que vos sont petits, il est peut-être moins fiable. La protection ultime contre la non-normalité et l'hétérogénéité consiste probablement à utiliser un test non paramétrique. La version non paramétrique de base d'une ANOVA est le test de Kruskal-Wallis : 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

Bien que le test de Kruskal-Wallis soit sans aucun doute la meilleure protection contre les erreurs de type I, il ne peut être utilisé qu’avec une seule variable catégorique (c’est-à-dire qu’il n’ya pas de prédicteurs continus ni de conceptions factorielles) et qu’il a le moins de pouvoir de toutes les stratégies examinées. Une autre approche non paramétrique consiste à utiliser la régression logistique ordinale . Cela semble étrange à beaucoup de gens, mais vous devez seulement supposer que vos données de réponse contiennent des informations ordinales légitimes, ce qu'ils font sûrement ou bien toute autre stratégie ci-dessus est également invalide:

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 Indexesp0.0363

gung - Rétablir Monica
la source
1
Aimez vos réponses! Maintenant, il y a une lueur chez moi pour traiter mes données! :) Parmi toutes ces approches, laquelle recommanderiez-vous fortement pour mon type de données, en particulier pour ce qui est de fournir une puissance statistique suffisante? Je ne suis pas très enthousiaste à l'idée d'utiliser une approche non paramétrique, c'est-à-dire le test de Kruskal-Wallis, car cela signifierait que je risque de commettre une erreur de type I? Corrige moi si je me trompe.
Rick L.
2
Je vais travailler un peu plus ici si j'ai une chance, mais non, le test de Kruskal-Wallis fournit probablement votre meilleure protection contre les erreurs de type I. OTOH, ce n'est peut-être pas l'option la plus puissante que vous avez.
gung - Réintégrer Monica
4
Notez que dans la bibliothèque, caril existe également une option permettant white.adjust=Tde régler l’hétéroskédacité à l’aide d’erreurs standard corrigées en fonction de l’hétéroscédasticité ajustée en fonction du blanc
Tom Wenseleers
3
Oui , il ne mentionne que pour lm« s, mais il semble aussi travailler pour aovl » (options pour white.adjustsont white.adjust=c(FALSE, TRUE, "hc3", "hc0", "hc1", "hc2", "hc4")- pour plus d' informations voir ?hccm)
Tom Wenseleers
1
@Nakx, non, pas que je sache. C'est une règle de base; il est peu probable que ce soit dans un article de la littérature statistique. C'est probablement dans certains manuels d'introduction.
gung - Rétablir Monica