Ce rapport présente les réponses au TD 1 de l’ECUE STI812 (Statistique et analyse de données) du Master S8 GC-BTP de l’Institut International d’Ingénierie de l’Eau et de l’Environnement (2iE). Le TD poursuit quatre objectifs :
ggplot2 ;Ces objectifs suivent la logique habituelle d’une analyse de données : d’abord comprendre les données (structure, valeurs manquantes), ensuite les décrire (une variable à la fois), puis étudier les relations entre variables. Chaque étape prépare la suivante : on ne peut pas interpréter une corrélation sans avoir regardé la forme des variables, ni faire une ANOVA sans avoir vérifié ses conditions.
Comment lire ce rapport. Chaque question est traitée selon la même trame :
Ozone (ppb, moyenne entre 13 h et 15 h),
Solar.R (rayonnement solaire, en Langley),
Wind (vent moyen, en mph), Temp (température
maximale, en °F), Month et Day. Ce jeu est
proche des problématiques environnementales de 2iE : l’ozone est un
polluant secondaire qui se forme sous l’effet du soleil et de la
chaleur.Tous les tests sont menés au risque α = 5 %. Cela signifie que l’on accepte de se tromper, dans 5 % des cas, en concluant à un effet qui n’existe pas.
Pourquoi commencer par là ? Avant tout calcul, il faut savoir ce que contient le jeu de données : combien de lignes, quelles variables, de quel type, et s’il manque des valeurs. Un statisticien qui saute cette étape risque d’appliquer un test inadapté (par exemple traiter un code de catégorie comme une quantité) ou de tirer des conclusions faussées par des données absentes.
Énoncé : Combien d’individus et de variables ? Quels sont les types de chaque variable ?
data(airquality) # charge le jeu fourni avec R
glimpse(airquality) # structure : dimensions, type et premières valeurs de chaque variable## Rows: 153
## Columns: 6
## $ Ozone <int> 41, 36, 12, 18, NA, 28, 23, 19, 8, NA, 7, 16, 11, 14, 18, 14, …
## $ Solar.R <int> 190, 118, 149, 313, NA, NA, 299, 99, 19, 194, NA, 256, 290, 27…
## $ Wind <dbl> 7.4, 8.0, 12.6, 11.5, 14.3, 14.9, 8.6, 13.8, 20.1, 8.6, 6.9, 9…
## $ Temp <int> 67, 72, 74, 62, 56, 66, 65, 59, 61, 69, 74, 69, 66, 68, 58, 64…
## $ Month <int> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,…
## $ Day <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,…
## Ozone Solar.R Wind Temp
## Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00
## 1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00
## Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
## Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88
## 3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
## Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
## NAs :37 NAs :7
## Month Day
## Min. :5.000 Min. : 1.0
## 1st Qu.:6.000 1st Qu.: 8.0
## Median :7.000 Median :16.0
## Mean :6.993 Mean :15.8
## 3rd Qu.:8.000 3rd Qu.:23.0
## Max. :9.000 Max. :31.0
##
Comment lire le résultat.
glimpse() donne les dimensions (lignes
× colonnes) et, pour chaque variable, son type :
<int> pour un entier, <dbl> pour
un nombre décimal.summary() résume chaque variable par cinq nombres
(minimum, 1er quartile, médiane, 3e quartile, maximum) et par la
moyenne. La ligne NA's compte les valeurs manquantes.Réponse. Le jeu contient 153
individus (ici, un individu correspond à un jour) et 6
variables, toutes numériques : Wind est décimale,
les cinq autres sont entières. Month et Day ne
sont pas des mesures : ce sont de simples repères de calendrier.
À retenir. Connaître le type d’une variable
détermine ce que l’on a le droit de calculer. Une moyenne de
Day n’a aucun sens physique, et Month, bien
que numérique, sera recodée en facteur (variable
qualitative) à l’exercice 5, car les mois sont des catégories et non des
quantités.
Énoncé : Combien de valeurs manquantes par variable ?
Pourquoi compter les valeurs manquantes ? Dans R,
une valeur manquante (NA) « contamine » les calculs :
mean(x) renvoie NA dès qu’un seul élément
l’est. Il faut donc savoir où elles se trouvent, combien elles sont, et
décider comment les traiter. Leur nombre indique aussi combien
d’observations on perd réellement dans chaque analyse.
# is.na() renvoie VRAI pour chaque case manquante ; colSums() additionne par colonne
na <- colSums(is.na(airquality))
na## Ozone Solar.R Wind Temp Month Day
## 37 7 0 0 0 0
Réponse. Il y a 44 valeurs
manquantes au total, concentrées sur Ozone
(37, soit 24,2 % des jours) et Solar.R
(7). Seuls 111 jours sont complets sur
les six variables.
Conséquence pratique. Pour chaque calcul, nous
utilisons l’option na.rm = TRUE (ignorer les
NA) ou use = "complete.obs" (n’utiliser que
les lignes complètes). L’effectif utile est alors plus
petit que 153 : par exemple 116 mesures d’ozone. Il faut aussi rester
prudent : si les mesures manquent pour une raison liée au phénomène
étudié (un capteur en panne par temps extrême, par exemple), les
résultats peuvent être biaisés. Ici nous supposons que les données
manquent de façon sans lien avec les valeurs mesurées.
Pourquoi étudier une variable seule ? Avant de relier l’ozone à d’autres variables, il faut connaître son comportement propre : autour de quelles valeurs se situe-t-elle (position), à quel point varie-t-elle (dispersion), et sa distribution est-elle équilibrée ou déformée (forme) ? Ces réponses conditionnent le choix des outils suivants : une distribution très asymétrique invite à utiliser des indicateurs robustes.
Énoncé : Calculer moyenne, médiane, écart-type, quartiles et coefficient de variation.
oz <- airquality$Ozone
oz_v <- oz[!is.na(oz)] # ozone sans les valeurs manquantes
moy <- mean(oz, na.rm = TRUE) # moyenne : somme des valeurs / effectif
med <- median(oz, na.rm = TRUE) # médiane : valeur qui coupe l'échantillon en deux moitiés
et <- sd(oz, na.rm = TRUE) # écart-type : écart typique à la moyenne
cv <- et / moy * 100 # coefficient de variation, en %
q <- quantile(oz, na.rm = TRUE) # minimum, Q1, médiane, Q3, maximum
c(moyenne = moy, mediane = med, ecart_type = et, CV_pct = cv)## moyenne mediane ecart_type CV_pct
## 42.12931 31.50000 32.98788 78.30151
## 0% 25% 50% 75% 100%
## 1.00 18.00 31.50 63.25 168.00
| Indicateur | Valeur |
|---|---|
| Effectif valide | 116 |
| Minimum | 1 |
| Q1 (25 %) | 18 |
| Médiane | 31.5 |
| Moyenne | 42,13 |
| Q3 (75 %) | 63.25 |
| Maximum | 168 |
| Écart-type | 32,99 |
| IQR (Q3 − Q1) | 45.25 |
| Coefficient de variation | 78,3 % |
Comment lire chaque indicateur, et à quoi il sert.
Interprétation. Le CV vaut 32,99 / 42,13 = 78,3 % : la variable est très dispersée. La moitié des jours ont un ozone inférieur à 31,5 ppb, et la moitié centrale des jours se situe entre 18 et 63.25 ppb, soit un IQR de 45.25 ppb.
Énoncé : La distribution est-elle symétrique ? Comparer moyenne et médiane.
Pourquoi comparer moyenne et médiane ? Dans une distribution parfaitement symétrique, les deux coïncident. Quand elles s’écartent, la distribution est déformée : si la moyenne dépasse la médiane, quelques valeurs très élevées « tirent » la moyenne vers la droite (asymétrie à droite) ; si c’est l’inverse, l’asymétrie est à gauche. Cet écart guide le choix des indicateurs à présenter.
Réponse. Non, la distribution n’est pas symétrique. La moyenne (42,13) dépasse la médiane (31,5) de 10,6 ppb. La distribution est dissymétrique à droite : la plupart des jours ont un ozone modéré, mais quelques jours de forte pollution (maximum 168 ppb) allongent la queue à droite et tirent la moyenne vers le haut sans modifier la médiane.
À retenir. Sur une variable dissymétrique comme l’ozone, la médiane et l’IQR décrivent mieux le jour « habituel » que la moyenne et l’écart-type. C’est exactement le rappel donné dans l’énoncé du TD. En revanche, pour l’ingénieur qui s’intéresse aux pics de pollution, ce sont justement les valeurs élevées qui comptent : il faut alors les regarder pour elles-mêmes.
Énoncé : Tracer l’histogramme et la boîte à moustaches. Repérer les valeurs atypiques.
Pourquoi des graphiques en plus des chiffres ? Les indicateurs résument la distribution en quelques nombres, mais des distributions très différentes peuvent avoir les mêmes moyenne et écart-type. Le graphique montre la forme réelle : asymétrie, plusieurs pics, valeurs isolées. L’histogramme découpe la variable en classes (ici 15) et compte les jours dans chaque classe ; la boîte à moustaches résume les cinq nombres et signale automatiquement les valeurs atypiques.
ggplot(airquality, aes(x = Ozone)) +
geom_histogram(bins = 15, fill = "#1E7A3D", colour = "white") + # 15 classes
geom_vline(xintercept = c(moy, med), linetype = c("dashed", "solid"),
colour = c("#C00000", "#1F4E79")) + # repères moyenne / médiane
labs(title = "Histogramme de l'ozone",
subtitle = "Pointillé rouge : moyenne ; trait bleu : médiane",
x = "Ozone (ppb)", y = "Effectif") +
theme_minimal()Distribution de l’ozone (153 jours, 37 valeurs manquantes ignorées).
ggplot(airquality, aes(y = Ozone)) +
geom_boxplot(fill = "#DEEAF1") +
labs(title = "Boîte à moustaches de l'ozone", y = "Ozone (ppb)") +
theme_minimal()Boîte à moustaches de l’ozone : les points isolés sont les valeurs atypiques (règle de Tukey).
# Seuil de la règle de Tukey : au-delà de Q3 + 1,5 x IQR, une valeur est dite atypique
seuil <- q[[4]] + 1.5 * (q[[4]] - q[[2]])
seuil## [1] 131.125
## [1] 135 168
Comment lire les graphiques.
Interprétation. Le seuil vaut 63.25 + 1,5 × 45.25 = 131,1 ppb. Les jours qui le dépassent sont : 135 et 168 ppb. Une valeur « atypique » n’est pas forcément une erreur : ici, ce sont des épisodes réels de forte pollution. On les conserve, mais on les signale, car ils influencent la moyenne, l’écart-type et la corrélation de Pearson.
Pourquoi mesurer une corrélation ? Dans un projet d’ingénieur, on cherche souvent à savoir si une grandeur varie avec une autre (l’ozone avec la température, la résistance du béton avec le rapport eau/ciment). La corrélation répond à deux questions : la liaison existe-t-elle, et quelle est son sens et son intensité ? Elle ne dit pas pourquoi les variables sont liées.
Énoncé : Tracer le nuage de points Temp (abscisse) vs Ozone (ordonnée).
Pourquoi tracer le nuage avant de calculer ? Un
coefficient de corrélation résume tout en un seul nombre et peut cacher
une relation courbe, deux sous-groupes ou une valeur aberrante. Le nuage
de points permet de vérifier que la liaison est plausible et de choisir
le bon coefficient. On place la variable explicative (Temp)
en abscisse et la variable à expliquer (Ozone) en
ordonnée.
ggplot(airquality, aes(Temp, Ozone)) +
geom_point(colour = "#1F4E79") + # un point = un jour
geom_smooth(method = "lm", colour = "#C00000") + # droite des moindres carrés + intervalle de confiance
labs(x = "Température (°F)", y = "Ozone (ppb)",
title = "Ozone en fonction de la température") +
theme_minimal()Nuage de points Ozone vs Température, avec droite de régression et intervalle de confiance à 95 %.
Comment lire le graphique. Chaque point est un jour.
La droite rouge est la droite de régression
(method = "lm" : modèle linéaire) : c’est la droite qui
passe au plus près des points au sens des moindres carrés. La
bande grisée autour d’elle est un intervalle de
confiance à 95 % de cette droite. Le nuage monte de gauche à droite :
quand la température augmente, l’ozone augmente en moyenne.
Interprétation. La relation est positive. On voit aussi que la dispersion des points augmente avec la température (les jours chauds peuvent avoir un ozone faible ou très élevé) et que la relation est légèrement courbée aux fortes valeurs : la droite décrit donc bien la tendance générale, sans être parfaite.
Énoncé : Calculer le coefficient de corrélation de Pearson, puis de Spearman. Commenter l’écart.
Pourquoi deux coefficients ? Ils ne mesurent pas la même chose :
Comparer les deux est un test de diagnostic rapide : s’ils sont proches, la relation est bien linéaire ; s’ils diffèrent, la forme de la relation ou des valeurs extrêmes jouent un rôle.
# use = "complete.obs" : on ne garde que les jours où Temp ET Ozone sont renseignés
r_p <- cor(airquality$Temp, airquality$Ozone, use = "complete.obs") # Pearson
r_s <- cor(airquality$Temp, airquality$Ozone, use = "complete.obs", method = "spearman") # Spearman
c(Pearson = r_p, Spearman = r_s)## Pearson Spearman
## 0.6983603 0.7740430
# Test de significativité de la corrélation de Pearson (H0 : la corrélation vraie est nulle)
ct <- cor.test(airquality$Temp, airquality$Ozone)
ct$estimate; ct$conf.int; ct$p.value## cor
## 0.6983603
## [1] 0.5913340 0.7812111
## attr(,"conf.level")
## [1] 0.95
## [1] 2.931897e-18
## [1] 116
Comment lire les coefficients.
cor.test() ajoute un intervalle de confiance à
95 % pour r et une p-valeur. Si la p-valeur est inférieure à
0,05, la corrélation est significativement différente de zéro.Résultats. Sur 116 jours avec les deux mesures, Pearson vaut 0,698 et Spearman 0,774. L’intervalle de confiance à 95 % de r est [0,59 ; 0,78] et la p-valeur est < 0,001 : la corrélation est significative.
Commentaire de l’écart. Spearman (0,77) est supérieur à Pearson (0,70). Cela s’explique par deux phénomènes vus sur le nuage : la relation est monotone mais pas parfaitement linéaire (l’ozone croît plus vite aux fortes températures), et les valeurs extrêmes d’ozone affaiblissent Pearson alors que Spearman, calculé sur les rangs, n’y est pas sensible. La température seule est associée à environ 49 % de la variance de l’ozone (r² = 0,49) : c’est beaucoup, mais il reste la moitié à expliquer par d’autres facteurs (vent, ensoleillement, etc.).
À retenir. Une forte corrélation ne prouve pas que la température cause l’ozone. Ici l’explication physique existe (la chaleur et le soleil favorisent les réactions photochimiques), mais elle vient de la connaissance du domaine, pas du coefficient.
Énoncé : Construire la matrice de corrélation des variables quantitatives et l’interpréter.
Pourquoi une matrice ? Elle donne d’un seul coup d’œil toutes les corrélations deux à deux. C’est un premier balayage pour repérer les variables les plus liées à la variable à expliquer, et aussi les variables liées entre elles, ce qui compte plus tard en régression (problème de colinéarité, TD4).
mat <- cor(airquality[, 1:4], use = "complete.obs") # Ozone, Solar.R, Wind, Temp
kable(round(mat, 3), caption = "Matrice de corrélation de Pearson (jours complets)")| Ozone | Solar.R | Wind | Temp | |
|---|---|---|---|---|
| Ozone | 1.000 | 0.348 | -0.612 | 0.699 |
| Solar.R | 0.348 | 1.000 | -0.127 | 0.294 |
| Wind | -0.612 | -0.127 | 1.000 | -0.497 |
| Temp | 0.699 | 0.294 | -0.497 | 1.000 |
## [1] 111
Comment lire la matrice. Chaque case est la
corrélation entre la variable de la ligne et celle de la colonne. La
diagonale vaut toujours 1 (une variable est
parfaitement corrélée avec elle-même) et la matrice est
symétrique. Ici, use = "complete.obs"
retire toute ligne contenant au moins un NA : la matrice
repose donc sur 111 jours, ce qui explique une
différence minime avec la valeur de la question 2 (calculée sur 116
jours).
Interprétation.
Pourquoi un test du khi-deux ? La corrélation ne s’applique qu’à des variables quantitatives. Pour deux variables qualitatives (couleur des cheveux et couleur des yeux), on compare le tableau des effectifs observés à celui que l’on obtiendrait si les deux variables n’avaient aucun lien. Plus l’écart est grand, plus il est difficile de soutenir qu’elles sont indépendantes.
Énoncé : Construire le tableau de contingence cheveux × yeux.
# HairEyeColor a 3 dimensions (cheveux, yeux, sexe) ; on somme sur le sexe
# pour ne garder que le croisement cheveux x yeux
tab <- margin.table(HairEyeColor, c(1, 2))
tab## Eye
## Hair Brown Blue Hazel Green
## Black 68 20 15 5
## Brown 119 84 54 29
## Red 26 17 14 14
## Blond 7 94 10 16
| Brown | Blue | Hazel | Green | Sum | |
|---|---|---|---|---|---|
| Black | 68 | 20 | 15 | 5 | 108 |
| Brown | 119 | 84 | 54 | 29 | 286 |
| Red | 26 | 17 | 14 | 14 | 71 |
| Blond | 7 | 94 | 10 | 16 | 127 |
| Sum | 220 | 215 | 93 | 64 | 592 |
Comment lire le tableau. Chaque case donne le nombre d’étudiants ayant à la fois une couleur de cheveux (ligne) et une couleur d’yeux (colonne). Les marges (totaux) donnent la répartition de chaque variable prise seule : par exemple 127 étudiants blonds sur 592. Ce sont elles qui servent à calculer ce que l’on attendrait sous l’indépendance.
Énoncé : Appliquer le test du khi-deux. Lire la statistique, les degrés de liberté et la p-valeur.
Comment fonctionne le test.
##
## Pearson's Chi-squared test
##
## data: tab
## X-squared = 138.29, df = 9, p-value < 2.2e-16
## Eye
## Hair Brown Blue Hazel Green
## Black 40.14 39.22 16.97 11.68
## Brown 106.28 103.87 44.93 30.92
## Red 26.39 25.79 11.15 7.68
## Blond 47.20 46.12 19.95 13.73
## Eye
## Hair Brown Blue Hazel Green
## Black 6.1 -4.3 -0.6 -2.3
## Brown 2.2 -3.4 2.1 -0.5
## Red -0.1 -2.3 1.0 2.6
## Blond -8.3 10.0 -2.7 0.7
# V de Cramér : intensité de la liaison, entre 0 (aucune) et 1 (parfaite)
V <- sqrt(chi$statistic / (sum(tab) * (min(dim(tab)) - 1)))
V## X-squared
## 0.2790446
Comment lire la sortie.
< 2.2e-16 quand elle est trop
petite pour être affichée.Lecture des résultats. χ² = 138,29, ddl = 9 [(4 − 1) × (4 − 1)] et p-valeur < 0,001. Les résidus standardisés les plus forts concernent les blonds : 10,0 pour « blond / yeux bleus » (excès) et -8,3 pour « blond / yeux marron » (déficit).
Énoncé : Conclure sur l’indépendance au risque de 5 %. La condition de validité est-elle respectée ?
Conclusion. La p-valeur est très inférieure à 0,05 : on rejette H₀. Au risque de 5 %, la couleur des cheveux et celle des yeux sont liées. La dépendance vient surtout des blonds : 94 blonds aux yeux bleus observés contre 46,1 attendus, et seulement 7 blonds aux yeux marron contre 47,2 attendus. Le V de Cramér (0,28) indique une liaison d’intensité modérée : le lien est réel, mais la couleur des cheveux ne détermine pas entièrement celle des yeux.
Pourquoi vérifier une condition de validité ? La loi du χ² n’est qu’une approximation, correcte seulement si les effectifs attendus ne sont pas trop petits. La règle usuelle exige que tous les effectifs théoriques soient supérieurs ou égaux à 5. Si ce n’est pas le cas, la p-valeur n’est pas fiable, et il faut regrouper des modalités ou utiliser un test exact (Fisher).
Vérification. Le plus petit effectif théorique vaut 7,68 (case « cheveux roux / yeux verts »), donc supérieur à 5 : la condition est respectée et la conclusion est fiable.
À retenir. Le test du khi-deux répond à « y a-t-il un lien ? » ; le V de Cramér répond à « le lien est-il fort ? » ; les résidus standardisés répondent à « où est le lien ? ». Un rapport complet donne les trois.
Pourquoi une ANOVA ? On veut savoir si la température moyenne diffère d’un mois à l’autre. Avec deux groupes, un test t suffirait. Avec cinq mois, il faudrait faire 10 comparaisons deux à deux, et chaque test comporte un risque d’erreur de 5 % : le risque d’obtenir au moins une fausse conclusion monte alors à environ 40 %. L’ANOVA teste d’un seul coup l’hypothèse que les cinq moyennes sont égales, en gardant le risque global à 5 %.
Énoncé : Transformer Month en facteur. Tracer des boîtes à moustaches de Temp par mois.
Pourquoi transformer Month en facteur ?
Pour R, Month est un nombre (5 à 9). Traité ainsi, un
modèle chercherait un effet proportionnel au numéro du mois
(une droite). Or on veut comparer cinq groupes
indépendamment les uns des autres : il faut donc déclarer
Month comme variable qualitative (facteur). Ici, on lui
donne aussi les noms des mois pour des graphiques lisibles.
airquality$Month <- factor(airquality$Month, levels = 5:9,
labels = c("Mai", "Juin", "Juillet", "Août", "Sept."))
ggplot(airquality, aes(Month, Temp)) +
geom_boxplot(fill = "#E2EFDA", colour = "#1E7A3D") +
labs(x = "Mois (1973)", y = "Température (°F)",
title = "Température maximale journalière par mois") +
theme_minimal()Température maximale journalière selon le mois (mai à septembre 1973).
| Mois | Effectif | Moyenne | Écart-type | Médiane |
|---|---|---|---|---|
| Mai | 31 | 65.5 | 6.9 | 66 |
| Juin | 30 | 79.1 | 6.6 | 78 |
| Juillet | 31 | 83.9 | 4.3 | 84 |
| Août | 31 | 84.0 | 6.6 | 82 |
| Sept. | 30 | 76.9 | 8.4 | 76 |
Interprétation. Les médianes augmentent de mai à juillet-août, puis diminuent en septembre. Mai est nettement le mois le plus frais ; juillet et août sont les plus chauds. Les boîtes de mai et de juillet-août ne se chevauchent presque pas, ce qui annonce une différence significative. Les dispersions sont comparables, sauf en septembre (écart-type le plus élevé) et en juillet (le plus faible), ce que l’on vérifiera avec le test de Bartlett.
Énoncé : Réaliser l’ANOVA à un facteur. Lire la statistique de Fisher et la p-valeur.
Le principe de l’ANOVA. La variabilité totale des températures se décompose en deux parties :
La statistique de Fisher est le rapport des deux, chacune divisée par ses degrés de liberté : F = carré moyen inter / carré moyen intra. Si les mois n’avaient aucun effet, ce rapport serait proche de 1. Plus F est grand, plus les différences entre mois dominent celles à l’intérieur des mois.
mod <- aov(Temp ~ Month, data = airquality) # modèle : la température dépend du mois
res <- summary(mod)
res## Df Sum Sq Mean Sq F value Pr(>F)
## Month 4 7061 1765.3 39.85 <2e-16 ***
## Residuals 148 6557 44.3
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Extraction des éléments de la table d'ANOVA
ddl_a <- res[[1]][["Df"]]
ss <- res[[1]][["Sum Sq"]]
Fobs <- res[[1]][["F value"]][1]
pval <- res[[1]][["Pr(>F)"]][1]
eta2 <- ss[1] / sum(ss) # part de la variance totale expliquée par le mois
c(F = Fobs, p = pval, eta2 = eta2)## F p eta2
## 3.984619e+01 1.276565e-22 5.185188e-01
Comment lire la table.
Lecture. F(4 ; 148) = 39,85, p-valeur < 0,001. Le mois explique η² = 52 % de la variance de la température : c’est un effet très important.
Pourquoi un test post-hoc ? L’ANOVA répond seulement à « au moins un mois diffère des autres ». Elle ne dit pas lesquels. Le test de Tukey compare tous les couples de mois en ajustant les p-valeurs pour tenir compte du nombre de comparaisons (ce qui évite le risque cumulé vu plus haut).
tk <- as.data.frame(TukeyHSD(mod)$Month)
tk$Décision <- ifelse(tk$`p adj` < 0.05, "Différent", "Non différent")
kable(tk, digits = 3,
col.names = c("Différence", "IC inf.", "IC sup.", "p ajustée", "Décision"),
caption = "Comparaisons multiples de Tukey (°F)")| Différence | IC inf. | IC sup. | p ajustée | Décision | |
|---|---|---|---|---|---|
| Juin-Mai | 13.552 | 8.844 | 18.259 | 0.000 | Différent |
| Juillet-Mai | 18.355 | 13.686 | 23.024 | 0.000 | Différent |
| Août-Mai | 18.419 | 13.750 | 23.088 | 0.000 | Différent |
| Sept.-Mai | 11.352 | 6.644 | 16.059 | 0.000 | Différent |
| Juillet-Juin | 4.803 | 0.095 | 9.511 | 0.043 | Différent |
| Août-Juin | 4.868 | 0.160 | 9.575 | 0.039 | Différent |
| Sept.-Juin | -2.200 | -6.946 | 2.546 | 0.704 | Non différent |
| Août-Juillet | 0.065 | -4.604 | 4.734 | 1.000 | Non différent |
| Sept.-Juillet | -7.003 | -11.711 | -2.295 | 0.001 | Différent |
| Sept.-Août | -7.068 | -11.775 | -2.360 | 0.001 | Différent |
Comment lire le tableau. Chaque ligne compare deux mois (par exemple « Juin-Mai » : moyenne de juin moins moyenne de mai). La colonne Différence donne l’écart de moyennes en °F ; l’intervalle de confiance à 95 % de cet écart est donné à côté ; la p ajustée est la p-valeur corrigée pour les 10 comparaisons. Si l’intervalle de confiance ne contient pas 0 (ou si p ajustée < 0,05), les deux mois sont significativement différents.
Interprétation. Les mois se regroupent en trois niveaux :
C’est cohérent avec la saison : la température monte de mai à l’été, puis redescend en septembre.
Pourquoi vérifier les hypothèses ? La p-valeur de l’ANOVA n’est exacte que si trois conditions sont remplies :
Pour ces tests d’hypothèses, l’hypothèse nulle est que la condition est vérifiée : une p-valeur inférieure à 0,05 signale donc un problème. On ajoute deux tests robustes, qui ne dépendent pas de ces conditions : celui de Welch (accepte des variances inégales) et celui de Kruskal-Wallis (test non paramétrique sur les rangs, sans hypothèse de normalité). Si tous conduisent à la même conclusion, celle-ci est solide.
sh <- shapiro.test(residuals(mod)) # normalité des résidus
ba <- bartlett.test(Temp ~ Month, data = airquality) # homogénéité des variances
we <- oneway.test(Temp ~ Month, data = airquality) # Welch : variances non supposées égales
kw <- kruskal.test(Temp ~ Month, data = airquality) # non paramétrique
sh; ba; we; kw##
## Shapiro-Wilk normality test
##
## data: residuals(mod)
## W = 0.98249, p-value = 0.0491
##
## Bartlett test of homogeneity of variances
##
## data: Temp by Month
## Bartlett's K-squared = 12.023, df = 4, p-value = 0.01718
##
## One-way analysis of means (not assuming equal variances)
##
## data: Temp and Month
## F = 43.3, num df = 4.00, denom df = 72.62, p-value < 2.2e-16
##
## Kruskal-Wallis rank sum test
##
## data: Temp by Month
## Kruskal-Wallis chi-squared = 73.328, df = 4, p-value = 4.496e-15
| Test | Question | Statistique | p-valeur | |
|---|---|---|---|---|
| W | Shapiro-Wilk | Résidus normaux ? | 0,982 | 0,049 |
| Bartlett’s K-squared | Bartlett | Variances égales ? | 12,02 | 0,017 |
| F | Welch | Moyennes égales (variances inégales) ? | 43,3 | < 0,001 |
| Kruskal-Wallis chi-squared | Kruskal-Wallis | Distributions identiques ? | 73,33 | < 0,001 |
Graphiques de diagnostic des résidus :
par(mfrow = c(1, 2))
# Gauche : résidus vs valeurs ajustées (la dispersion doit être à peu près constante)
plot(fitted(mod), residuals(mod),
main = "Résidus vs valeurs ajustées",
xlab = "Valeurs ajustées (°F)", ylab = "Résidus (°F)", pch = 19, col = "#1F4E79")
abline(h = 0, lty = 2, col = "#C00000")
# Droite : diagramme quantile-quantile (les points doivent suivre la droite)
qqnorm(residuals(mod),
main = "Normalité des résidus (ANOVA Temp ~ Mois)",
xlab = "Quantiles théoriques", ylab = "Quantiles des résidus (°F)", pch = 19, col = "#1F4E79")
qqline(residuals(mod), lty = 2, col = "#C00000")Diagnostics du modèle ANOVA : résidus contre valeurs ajustées (gauche) et diagramme quantile-quantile des résidus (droite).
Comment lire les diagnostics.
Remarque sur l’indépendance. Les températures de jours consécutifs sont en réalité corrélées (une journée chaude est souvent suivie d’une autre journée chaude). L’hypothèse d’indépendance n’est donc qu’approximativement respectée. Cela n’invalide pas la différence entre mois, très marquée, mais c’est une limite à mentionner.
Énoncé : Conclure : les températures moyennes diffèrent-elles selon les mois ?
Conclusion. Oui. Au risque de 5 %, la température moyenne diffère significativement selon les mois (F = 39,85 ; p < 0,001 ; η² = 52 %). Les hypothèses sont à la limite (Shapiro p = 0,049 ; Bartlett p = 0,017), mais :
La conclusion ne dépend donc pas des hypothèses de l’ANOVA classique : elle est solide.
À retenir. La démarche complète d’une ANOVA est : (1) visualiser, (2) tester l’effet global avec F, (3) localiser les différences avec un test post-hoc, (4) vérifier les hypothèses, (5) confirmer avec des tests robustes si elles sont douteuses, (6) quantifier l’effet avec η².
Pourquoi refaire le calcul à la main ? Un logiciel donne un résultat, mais seul le calcul manuel montre d’où il vient. Refaire chaque étape permet de comprendre ce que chaque nombre mesure, et de repérer une erreur si le résultat de R surprend. Ici, on reprend le test d’indépendance sur un petit tableau de 100 ménages : « accès à l’eau potable » selon la « zone » (urbaine ou rurale).
m <- matrix(c(40, 10, 20, 30), nrow = 2,
dimnames = list(Acces = c("oui", "non"), Zone = c("Urbaine", "Rurale")))
kable(addmargins(m), caption = "Effectifs observés (n = 100 ménages)")| Urbaine | Rurale | Sum | |
|---|---|---|---|
| oui | 40 | 20 | 60 |
| non | 10 | 30 | 40 |
| Sum | 50 | 50 | 100 |
Énoncé : Calculer les effectifs théoriques n̂ₖₗ = nₖ• · n•ₗ / n sous l’hypothèse d’indépendance.
Raisonnement. Si l’accès à l’eau était indépendant de la zone, la proportion de ménages avec accès serait la même partout, égale à la proportion générale : 60 / 100 = 60 %. Dans une zone de 50 ménages, on attendrait donc 60 % × 50 = 30 ménages avec accès. C’est exactement ce que donne la formule (60 × 50) / 100.
# outer() calcule tous les produits (total ligne x total colonne) ; on divise par n
E <- outer(rowSums(m), colSums(m)) / sum(m)
E## Urbaine Rurale
## oui 30 30
## non 20 20
Réponse. Les effectifs théoriques sont 30 et 30 pour « accès : oui » (urbaine et rurale) et 20 et 20 pour « accès : non ». Tous sont supérieurs à 5 : la condition de validité du test est respectée.
Énoncé : En déduire la statistique χ² = Σ (nₖₗ − n̂ₖₗ)² / n̂ₖₗ et son nombre de degrés de liberté.
## Zone
## Acces Urbaine Rurale
## oui 3.333333 3.333333
## non 5.000000 5.000000
chi2 <- sum(contrib) # somme des contributions
ddl <- (nrow(m) - 1) * (ncol(m) - 1)
c(chi2 = chi2, ddl = ddl)## chi2 ddl
## 16.66667 1.00000
Comment lire le calcul. Chaque case apporte une contribution : (observé − théorique)² / théorique. Le carré évite que les écarts positifs et négatifs s’annulent ; la division par l’effectif théorique donne un poids relatif (un écart de 10 compte davantage sur un effectif attendu de 20 que de 30). Ici : (40 − 30)² / 30 = 3,33 ; (20 − 30)² / 30 = 3,33 ; (10 − 20)² / 20 = 5 ; (30 − 20)² / 20 = 5.
Les degrés de liberté représentent le nombre de cases que l’on peut remplir librement une fois les totaux imposés : dans un tableau 2 × 2, une seule case suffit à déterminer les trois autres, donc ddl = (2 − 1) × (2 − 1) = 1.
Réponse. χ² = 3,33 + 3,33 + 5 + 5 = 16,67, avec ddl = 1.
Énoncé : Le quantile théorique à 5 % pour 1 ddl vaut 3,84. Conclure, puis vérifier avec chisq.test.
D’où vient 3,84 ? C’est la valeur critique : le seuil que le χ² à 1 ddl dépasse dans seulement 5 % des cas si H₀ est vraie. Si notre χ² calculé dépasse ce seuil, l’écart observé est trop grand pour être attribué au hasard, et on rejette l’indépendance. C’est la même décision que « p-valeur < 0,05 », vue depuis l’autre côté.
## [1] 3.841459
##
## Pearson's Chi-squared test
##
## data: m
## X-squared = 16.667, df = 1, p-value = 4.456e-05
##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: m
## X-squared = 15.042, df = 1, p-value = 0.0001052
Pourquoi deux résultats différents ? Pour un tableau
2 × 2, R applique par défaut la correction de continuité de
Yates, qui réduit légèrement le χ² pour compenser le fait
qu’une loi continue (le khi-deux) approche des effectifs discrets. Le
calcul à la main correspond à la version sans
correction (correct = FALSE). Les deux versions
mènent ici à la même décision.
Conclusion. Comme 16,67 > 3,84, on rejette l’indépendance au risque de 5 % : l’accès à l’eau potable dépend de la zone. La proportion de ménages avec accès est de 80,0 % en zone urbaine contre 40,0 % en zone rurale. Le calcul manuel est confirmé par R (χ² = 16,67, p < 0,001). Avec la correction de Yates, χ² = 15,04. L’intensité de la liaison, mesurée par le coefficient φ = √(χ² / n) = 0,41, est forte.
À retenir. Ce résultat a une portée pratique pour l’ingénieur : l’écart d’accès entre zones urbaine et rurale est trop grand pour être dû au hasard, ce qui justifie de cibler les investissements en adduction d’eau vers les zones rurales.
Ce TD a permis de parcourir une démarche complète d’analyse : comprendre les données, décrire une variable, puis mesurer des liaisons entre variables, en vérifiant à chaque fois les conditions de validité.
| Étape | Outil | Question posée | Résultat principal |
|---|---|---|---|
| Exercice 1 | glimpse, is.na |
Que contiennent les données ? | 153 jours, 6 variables, 44 valeurs manquantes |
| Exercice 2 | Moyenne, médiane, CV, boîte | Comment se distribue l’ozone ? | Asymétrique à droite, très dispersé |
| Exercice 3 | Pearson, Spearman | L’ozone dépend-il de la température ? | Liaison positive forte (r ≈ 0,70) |
| Exercice 4 | Khi-deux | Cheveux et yeux sont-ils liés ? | Oui, liaison modérée |
| Exercice 5 | ANOVA, Tukey | La température varie-t-elle selon le mois ? | Oui, 3 niveaux, η² ≈ 52 % |
| Théorie | Khi-deux manuel | L’accès à l’eau dépend-il de la zone ? | Oui, χ² = 16,67 |
Trois enseignements de méthode.
Limites. Les analyses portent sur les données disponibles (116 mesures d’ozone ; 111 jours complets pour la matrice de corrélation). Les données couvrent une seule saison (1973, New York) : les résultats décrivent cet échantillon et ne se généralisent pas sans précaution. Une corrélation ou une association ne prouve pas une relation de cause à effet. Enfin, les mesures journalières successives ne sont pas indépendantes, ce qui limite la portée stricte des tests.
Version de R et des packages utilisés pour produire ce rapport :
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 22631)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=French_France.utf8 LC_CTYPE=French_France.utf8
## [3] LC_MONETARY=French_France.utf8 LC_NUMERIC=C
## [5] LC_TIME=French_France.utf8
##
## time zone: Etc/UTC
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] knitr_1.52 lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0
## [5] dplyr_1.2.1 purrr_1.2.2 readr_2.2.0 tidyr_1.3.2
## [9] tibble_3.3.1 ggplot2_4.0.3 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] Matrix_1.7-5 gtable_0.3.6 jsonlite_2.0.0 compiler_4.6.1
## [5] tidyselect_1.2.1 jquerylib_0.1.4 splines_4.6.1 scales_1.4.0
## [9] yaml_2.3.12 fastmap_1.2.0 lattice_0.22-9 R6_2.6.1
## [13] labeling_0.4.3 generics_0.1.4 bslib_0.12.0 pillar_1.11.1
## [17] RColorBrewer_1.1-3 tzdb_0.5.0 rlang_1.3.0 stringi_1.8.9
## [21] cachem_1.1.0 xfun_0.61 sass_0.4.10 S7_0.2.2
## [25] otel_0.2.0 timechange_0.4.0 cli_3.6.6 mgcv_1.9-4
## [29] withr_3.0.3 magrittr_2.0.5 digest_0.6.39 grid_4.6.1
## [33] rstudioapi_0.19.0 hms_1.1.4 nlme_3.1-169 lifecycle_1.0.5
## [37] vctrs_0.7.3 evaluate_1.0.5 glue_1.8.1 farver_2.1.2
## [41] rmarkdown_2.32 tools_4.6.1 pkgconfig_2.0.3 htmltools_0.5.9