ANOVA à 2 facteurs : quand les hypothèses ne sont pas satisfaites

Cet article fait suite aux trois précédents articles consacrés à l’ ANOVA à 2 facteurs (two ways anova, en anglais) :  Nous allons voir ici, les solutions qui peuvent être envisagées lorsque l’hypothèse de normalité et/ ou l’hypothèse d’homogénéité des résidus ne sont pas satisfaites.

Rappels concernant les hypothèses de validité de l'ANOVA à 2 facteurs

Comme décrit dans le tutoriel il est nécessaire que l’ANOVA à 2 facteurs (comme tous les modèles linéaires d’ailleurs) satisfasse trois conditions, pour que ses résultats soient valides (c’est à dire pour qu’on puisse avoir confiance dans ces résultats). On parle alors d’hypothèses de validité. Celles-ci se vérifient sur les résidus de l’ANOVA.

Ces hypothèses de validité sont :

  1. L‘indépendance des résidus,
  2. La normalité des résidus , autrement dit les résidus sont distribués selon une loi normale de moyenne 0,
  3. L’homogénéité des résidus, autrement dit la dispersion des résidus (pour chaque condition correspondant aux croisements des modalités des deux facteurs) est similaire.

Pour plus de détails sur la vérification de ces hypothèses, je vous renvoie vers le tutoriel.

Si l’hypothèse d’indépendance des résidus n’est pas satisfaite, c’est généralement par ce que des observations sont réalisées plusieurs fois sur la même unité expérimentale. Par exemple, si un des facteurs est un traitement (A ou B) et le second facteur du temps (Jour1, jour2, jour 3) et que les observations sont réalisées pour chaque temps sur le même sujet. Dans ce cas, les données d’un même sujet sont corrélées, et il s’agit alors d’utiliser un modèle mixte pour prendre en compte que les données d’un même sujet se ressemblent plus que les données de deux sujets différents. Cette situation de non-indépendance des données nécessite de changer d’approche statistique.

En revanche, lorsque les résidus ne satisfont pas l’hypothèse de normalité et/ou l’hypothèse d’homogénéité cela est plus problématique, car il n’existe pas, du moins à ma connaissance, d’approche non paramétrique de l’ANOVA à 2 facteurs ( comme cela est le cas pour l’ANOVA à un facteur avec le test de Kruskal Wallis).

Il existe néanmoins une solution qui peut facilement être mise en place. Il s’agit d’appliquer une transformation sur la variable réponse. C’est ce que nous allons explorer dans cet article.

Une solution simple pour l'ANOVA à 2 facteurs : la transformation

Lorsque, dans une ANOVA à 2 facteurs,  l’hypothèse de normalité des résidus, et / ou l’hypothèse d’homogénéité des résidus ne sont pas satisfaites, une solution simple à envisager est celle de l’utilisation d’une transformation log, ou d’une transformation de type BoxCox (c’est une généralisation de la transformation log) de la variable réponse. L’application de ces transformations a pour conséquence d’améliorer conjointement la normalité et l’homogénéité des résidus. C’est pour cela qu’on peut avoir recours à la transformation de la variable réponse en cas de défaut de normalité des résidus et/ ou en cas de défaut d’homogénéité.

Tutoriel

Prenons pour exemple les données mydata, simulées pour l’occasion. On pourrait imaginer qu’il s’agit de mesurer la fatigue musculaire des quadriceps en fonction de trois types d’exercices (course à pied / vélo / vélo elliptique) et en fonction de deux types d’hydratation (eau, boisson glucidique).

Vous pouvez copier-coller les lignes suivantes dans voter console R pour créer le jeu de données utilisé ici :

mydata <- structure(list(Fatigue = c(118.32, 110.02, 125.32, 110.42, 111.94, 
    138.65, 112.59, 118.7, 136.42, 113.7, 60.25, 68.22, 82.22, 80.53, 
    77.02, 72.21, 69.45, 64.92, 59.73, 69.4, 65.09, 77.01, 69.09, 
    68.76, 61.55, 72.08, 69.98, 60.41, 63.13, 69.95, 51.11, 47.21, 
    49.06, 44.45, 57.88, 54.12, 46.09, 42.28, 45.63, 41.27, 52.97, 
    52.58, 55.92, 57.84, 53.95, 60.37, 57.65, 50.17, 56.35, 60.01, 
    35.03, 35.98, 34.26, 33.12, 32.22, 34.43, 34.13, 38.16, 31.63, 
    35.97), Exercice = structure(c(3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 
    3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 2L, 2L, 2L, 2L, 
    2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
    1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
    1L, 1L, 1L, 1L), .Label = c("Course", "Vélo elliptique", "Vélo simple"
    ), class = "factor"), Hydratation = structure(c(1L, 1L, 1L, 1L, 
    1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
    1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 
    2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
    2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), .Label = c("eau", "boisson glucidique"
    ), class = "factor")), row.names = c(NA, -60L), class = "data.frame") 

Visualisation des données

Commençons par visualiser les données :

library(tidyverse)

ggplot(mydata,aes(y=Fatigue, x=Exercice, colour=Hydratation))+
     geom_point(position=position_jitterdodge(dodge.width=0.7), size=2) +
     geom_boxplot(alpha=0.5, position = position_dodge(width=0.8), fatten=NULL)+
     theme_classic() 

On peut voir que :

  • les niveaux de fatigue lors des exercices de courses et de vélo elliptique sont relativement proches, mais qu’en revanche la fatigue musculaire est plus forte pour l’exercice du vélo.
  • le niveau de fatigue est globalement moins élevé en cas d’hydratation avec une boisson glucidique, et que le profil des fatigues est plutôt parallèle.
  • les profils de fatigue en fonction du type d’hydratation semblent un peu différents (forte augmentation de la fatigue pour le coupe Vélo simple et hydratation avec de l’eau). Ceci laisse à penser qu’une interaction entre les facteurs “Hydration” et “Exercice” pourrait être présente.

Mise en évidence des défauts de normalité et d'homogénéité

Réalisation de l'ANOVA à 2 facteurs

Pour rappel, les contrastes sont modifiés pour obtenir des carrés de type 3 (vous trouverez plus d’infos dans ce tutoriel).

mod <- lm(Fatigue~  Exercice*Hydratation, 
             contrasts=list(Exercice=contr.sum, Hydratation=contr.sum),
             data=mydata) 

Evaluation de l'hypothèse de normalité des résidus

plot(mod,2) 

Le QQplot nous montre qu’il existe un défaut de normalité assez prononcé puisque de nombreux points ne sont pas bien alignés selon la droite.

Le test de Shapiro Wilk va dans le même sens puisque sa p-value est < 0.05 ; il rejette donc l’hypothèse de normalité.

shapiro.test(residuals(mod))

    ## 
    ##  Shapiro-Wilk normality test
    ## 
    ## data:  residuals(mod)
    ## W = 0.95253, p-value = 0.02051 

Evaluation de l'hypothèse d'homogénéité des résidus

plot(mod,3) 

Le plot des résidus standardisés en fonction des valeurs prédites (les moyennes des croisements des exercices et des types d’hydratation) nous montre qu’il existe un défaut d’homogénéité des résidus. En effet, on peut voir que la variabilité des résidus a tendance à augmenter lorsque la fatigue augmente (fitted values).

On peut également réaliser un test de Bartlett, en créant une variable condition qui est le croisement des modalités des facteurs “Exercice” et “Hydratation” :

mydata$condition <- interaction(mydata$Exercice, mydata$Hydratation, sep="_")

tail(mydata)

    ##    Fatigue Exercice        Hydratation                 condition
    ## 55   32.22   Course boisson glucidique Course_boisson glucidique
    ## 56   34.43   Course boisson glucidique Course_boisson glucidique
    ## 57   34.13   Course boisson glucidique Course_boisson glucidique
    ## 58   38.16   Course boisson glucidique Course_boisson glucidique
    ## 59   31.63   Course boisson glucidique Course_boisson glucidique
    ## 60   35.97   Course boisson glucidique Course_boisson glucidique 

Le test de Bartlett va dans le même sens puisque sa p-value est < 0.05 ; il rejette donc l’hypothèse d’égalité des variances des résidus.

bartlett.test(residuals(mod)~mydata$condition)

    ## 
    ##  Bartlett test of homogeneity of variances
    ## 
    ## data:  residuals(mod) by mydata$condition
    ## Bartlett's K-squared = 26.022, df = 5, p-value = 8.837e-05 

Utilisation d'une transformation log de la réponse

Réalisation de l'ANOVA à 2 facteurs avec le log de la réponse

Pour cela, il suffit seulement d’ajuster à nouveau le modèle ANOVA à 2 facteurs, en utilisant `log(Fatique)` comme variable réponse :

mod_log <- lm(log(Fatigue)~  Exercice*Hydratation, 
               contrasts=list(Exercice=contr.sum, Hydratation=contr.sum),
               data=mydata) 

Remarque : la transformation `log` peut être utilisée si les valeurs de la variable réponse sont strictement positives. Si certaines valeurs sont nulles, on peut ajouter + 1 au log : `log(Fatique+1)`.

Evaluation de l'hypothèse de normalité

Quand on réalise à nouveau le QQplot, on peut voir que la normalité des résidus s’est améliorée :

plot(mod_log,2) 
shapiro.test(residuals(mod_log))

    ## 
    ##  Shapiro-Wilk normality test
    ## 
    ## data:  residuals(mod_log)
    ## W = 0.98347, p-value = 0.5911
 

De même, le test de Shapiro Wilk ne rejette plus l’hypothèse de normalité des résidus puisque sa pvalue est > 0.05.

Evaluation de l'hypothèse d'homogénéité

Le plot des résidus standardisés en fonction des valeurs prédites ne met plus en évidence d’augmentation systématique de la variabilité des résidus avec l’augmentation de la fatigue, et globalement les variabilités des résidus semblent similaires.

plot(mod_log,3) 

Et le test de Bartlett ne rejette plus l’hypothèse homogénéité des variances ; sa p-value est > 0.05.

bartlett.test(residuals(mod_log)~mydata$condition)

    ## 
    ##  Bartlett test of homogeneity of variances
    ## 
    ## data:  residuals(mod_log) by mydata$condition
    ## Bartlett's K-squared = 6.7056, df = 5, p-value = 0.2435 

Remarque : le test de Levene peut également être utilisé pour évaluer la robustesse du résultat :

library(car)
leveneTest(residuals(mod_log)~mydata$condition)

    ## Levene's Test for Homogeneity of Variance (center = median)
    ##       Df F value Pr(>F)
    ## group  5  1.0705  0.387
    ##       54 

Résultats

La table ANOVA est accessible via la fonction Anova du package car.L’interaction Hydratation * Exercice apparaît significative.

Pour plus d’informations sur l’interprétation et les suites à donner à l’analyse, consulter le tutoriel sur l’ANOVA à 2 facteurs.

Utilisation d'une transformation BoxCox de la réponse

La transformation BoxCox est définie par :

Quand lambda est différent de zéro, la transformation BoxCox est très proche d’une transformation puissance puisqu’elle retranche 1 et divise par lambda, qui est une constante.

Deux éléments importants sont à prendre en considération :

1. Cette transformation BoxCox s’applique elle aussi uniquement lorsque les données sont strictement positives, car en présence de valeurs négatives et positives, l’ordre des données, peut, ne pas être préservé. Dans ce as, on peut ajouter une valeur (appelé start) aux réponses pour les rendre toutes positives.

  1. La transformation est efficace seulement si les données sont relativement distendues (ratio min max > 1)

Il existe plusieurs fonctions dans R pour appliquer une transformation BoxCox. Ma préférence va à la fonction `powerTransform()` du package `car`, pour sa simplicité d’utilisation et les informations fournies en sortie. La fonction `powerTransform()`s’applique directement sur le modèle :

library(car)
summary(p1 <- powerTransform(mod))
 

Les sorties de la fonction sont constituées de trois éléments. Dans la première partie, on retrouve l’estimation du coefficient lambda (l’estimation est faite par maximum de vraisemblance).

Dans la seconde partie, un test statistique est réalisé pour évaluer si lambda peut être fixé à 0, c’est à dire pour évaluer si une simple transformation log est suffisante.

Dans la troisième partie un test statistique est réalisé pour évaluer si la transformation BoxCox (avec lambda =1 ou lambda différent de 1) est réellement nécessaire.

Ici, on peut voir que :

  1. lambda est estimé à -0.45 avec un intervalle de confiance à 95% =[- 0.992 ; 0.091].
  2. la transformation log serait suffisante. On s’en doutait déjà puisque l’intervalle de confiance de lambda contient 0.
  3. il est nécessaire d’appliquer une transformation (log ou autre)

A présent, il est nécessaire d’ajouter la réponse transformée au jeu de données, pour ensuite ajuster à nouveau le modèle ANOVA à 2 facteurs avec cette nouvelle réponse :

mydata_bc <- transform(mydata, Fatigue_bc=bcPower(Fatigue,coef(p1)))
head(mydata_bc)

    ##   Fatigue    Exercice Hydratation       condition Fatigue_bc
    ## 1  118.32 Vélo simple         eau Vélo simple_eau   2.747563
    ## 2  110.02 Vélo simple         eau Vélo simple_eau   2.726083
    ## 3  125.32 Vélo simple         eau Vélo simple_eau   2.764256
    ## 4  110.42 Vélo simple         eau Vélo simple_eau   2.727164
    ## 5  111.94 Vélo simple         eau Vélo simple_eau   2.731229
    ## 6  138.65 Vélo simple         eau Vélo simple_eau   2.793021

mod_bc <- lm(Fatigue_bc~  Exercice*Hydratation, 
             contrasts=list(Exercice=contr.sum, Hydratation=contr.sum),
             data=mydata_bc) 

Evaluation de l'hypothèse de normalité des résidus

Lorsqu’on réalise le QQplot, on peut alors voir que la normalité des résidus a été améliorée :

plot(mod_bc,2) 

Ceci est confirmé par le test de Shapiro-Wilk :

shapiro.test(residuals(mod_bc))

    ## 
    ##  Shapiro-Wilk normality test
    ## 
    ## data:  residuals(mod_bc)
    ## W = 0.98638, p-value = 0.7416 

Evaluation de l'hypothèse d'homogénéité des résidus

plot(mod_bc,3) 

La variabilité des résidus semble plutôt homogène.

 

Ceci est confirmé par le test de Bartlett:
mydata_bc$condition <- interaction(mydata_bc$Exercice, mydata_bc$Hydratation, sep="_")

bartlett.test(residuals(mod_bc)~mydata_bc$condition)

    ## 
    ##  Bartlett test of homogeneity of variances
    ## 
    ## data:  residuals(mod_bc) by mydata_bc$condition
    ## Bartlett's K-squared = 6.1115, df = 5, p-value = 0.2955 

Résultats

Anova(mod_bc,type=3)

    ## Anova Table (Type III tests)
    ## 
    ## Response: Fatigue_bc
    ##                      Sum Sq Df    F value    Pr(>F)    
    ## (Intercept)          383.89  1 448427.887 < 2.2e-16 ***
    ## Exercice               0.65  2    380.712 < 2.2e-16 ***
    ## Hydratation            0.37  1    433.678 < 2.2e-16 ***
    ## Exercice:Hydratation   0.01  2      5.336  0.007679 ** 
    ## Residuals              0.05 54                         
    ## ---
    ## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Ici encore l’interaction Hydratation * Exercice apparaît significative. Cela était déjà  cas avant l’application des transformations , mais avec une statistique F sans doute largement surestimée ( de l’ordre de 6 et 5 avec les transformations contre 34 sans !) :

Anova(mod,type=3)

    ## Anova Table (Type III tests)
    ## 
    ## Response: Fatigue
    ##                      Sum Sq Df  F value    Pr(>F)    
    ## (Intercept)          261217  1 6529.414 < 2.2e-16 ***
    ## Exercice              26869  2  335.814 < 2.2e-16 ***
    ## Hydratation           13589  1  339.671 < 2.2e-16 ***
    ## Exercice:Hydratation   2746  2   34.317 2.411e-10 ***
    ## Residuals              2160 54                       
    ## ---
    ## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Conclusion

J’espère que cet article répondra aux attentes des nombreux messages de que j’ai reçu depuis la publication du tutoriel sur l’ANOVA à 2 facteurs. Il est à noter que ces transformations log et BoxCox ne sont pas réservées à l’ANOVA à deux facteurs, elles peuvent également être utilisées , par exemple, dans le cadre de la régression linéaire multiple, qui n’a pas non plus d’équivalent non paramétrique. Il faut aussi garder en tête que ces transformations ne sont pas toujours efficaces, parfois les améliorations de la normalité et / ou de l’homogénéité des résidus restent insuffisante.

D’autres approches peuvent être employées si seule l’hypothèse d’homogénéité des résidus est rejetée, comme l’utilisation des estimateurs sandwich ou encore en modélisant la variance. J’essaierai de présenter ces deux approches dans un prochain article.

Si cet article vous a plu, ou vous a été utile, et si vous le souhaitez, vous pouvez soutenir ce blog en faisant un don sur sa page Tipeee 🙏

Crédits photo : Composita

11 Responses

    1. Bonjour Mathilde,

      j’imagine que malgré les solutions l’hypothèse de normalité des résidus et/ou celles d’homogénéité ne sont pas satisfaites.
      Si c’est la normalité, et que la forme des résidus ne ressemble pas à une banane, on peut croiser les doigts en disant que le modèle linéaire est robuste par rapport à la non normalité. Si c’est l’hypothèse d’homogénéité, on peut modéliser la variance, ou utiliser un estimateur sandwich. Vous trouverez des infos sur la modélisation de la variance dans le livre de zuur : https://www.amazon.fr/Mixed-Effects-Models-Extensions-Ecology/dp/0387874577/ref=sr_1_1?__mk_fr_FR=ÅMÅŽÕÑ&dchild=1&keywords=alain zuur mixed models&qid=1592772346&sr=8-1
      Et pour les estimateurs sandwich, il me semble qu’il ya un exemple dans cet ouvrage : https://www.amazon.fr/R-Companion-Applied-Regression/dp/141297514X/ref=sr_1_4?__mk_fr_FR=ÅMÅŽÕÑ&dchild=1&keywords=companion for regression analysis&qid=1592772402&sr=8-4
      Bonne continuation.

  1. Bonjour Claire,
    Merci pour cet article qui me permet de mieux comprendre la(les) démarche(s).

    J’ai réalisé la transformation log puis la transformation BoxCox à la réponse. L’intéraction est toujours significative avec une statistique F de l’ordre de 8. Cependant avant la transformation elle était de 6. Que cela signifie-t-il ?

    Ayant une intéraction significative, j’ai suivi votre démarche (2.3.4) en réalisant une anova à 1 facteur avec comparaison 2 à 2. Cependant dois-je utilisé les données transformées (« Fatigue_bc », mydata_bc$condition dans votre exemple) ou les données initiales (« Fatigue », mydata$condition dans votre exemple) ?

    1. Bonjour Clélia,

      l’augmentation de F vient sans doute de la diminution de l’erreur standard, a priori cela n’a pas trop d’importante. Pour les comparaisons multiples, il faut utiliser les variables avec la transformation boxcox.
      Bonne continuation.

  2. Merci infiniment pour cette série d’articles sur l’anova. C’était incomparablement plus clair et complet que tout ce que j’ai pu voir avant, et ça m’a permis d’enfin comprendre cette méthode et de pouvoir l’appliquer sur mes données.

    1. Bonjour Samuel,

      merci pour votre commentaire. Je suis ravie que les articles vous aient permis de comprendre la méthode et de l’appliquer. C’est très chouette !
      Bonne continuation

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Aide mémoire off'R ;)

Enregistrez vous pour recevoir gratuitement mes fiches « aide mémoire » (ou cheat sheets) qui vous permettront de réaliser facilement les principales analyses biostatistiques avec le logiciel R et pour être informés des mises à jour du site.