TP1 – Graphiques Quantile-Quantile, Test de Normalité et Test du χ²

Auteur·rice

HAMLIL Mohamed

Date de publication

30 avril 2026

Introduction

Dans ce TP, nous allons utiliser les outils de la normalité et de l’indépendance dans deux cadres :

  • Sur données ordonnées (regroupées par modalités) : nous reconstruirons tous les outils à la main.
  • Sur données brutes : nous utiliserons largement les fonctions de R.

Objectifs pédagogiques :

  • Comprendre et tracer un QQ-plot
  • Réaliser un test du χ² d’adéquation (normalité)
  • Réaliser un test du χ² d’indépendance
  • Utiliser les fonctions R pour tester la normalité (Shapiro-Wilk, qqnorm)

1. Le QQ-Plot (graphique quantile-quantile)

1.1 Rappels théoriques

Le QQ-plot (Quantile-Quantile plot) est un outil graphique qui permet de visualiser rapidement si une série de données suit une distribution théorique donnée (gaussienne, Poisson, etc.).

Principe :

Soit \(x_1, \ldots, x_n\) une série statistique et \(F_0\) la fonction de répartition de la loi de probabilité de référence. On trace :

  • En ordonnées : les fractiles correspondant à la distribution observée (\(q_i\))
  • En abscisses : les fractiles correspondant à la distribution théorique (\(q_i^*\))

Interprétation : Plus les points sont alignés sur la diagonale, plus l’adéquation de la loi théorique avec la loi observée est forte.

Pour des données regroupées par modalités \(m_1, \ldots, m_J\) avec des fréquences cumulées \(F_1, \ldots, F_J\), les quantiles théoriques d’une loi normale sont : \(q_J^* = F_0^{-1}(F_J)\), accessibles via qnorm(F_J, m, σ).

1.2 Application : tensions de pile

Contexte : Xantane a relevé 500 fois les tensions (\(x_i\)) de sa pile. Les données sont regroupées dans le tableau suivant :

\(x_i\) 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
\(n_i\) 1 4 9 28 23 37 51 63 65 60 48 38 32 23 12 3 1 2

Étape 1 : Saisie des données

Code
qi = 92:109
ni = c(1, 4, 9, 28, 23, 37, 51, 63, 65, 60, 48, 38, 32, 23, 12, 3, 1, 2)

Étape 2 : Calcul de la moyenne pondérée

La moyenne pondérée se calcule comme : \(m = \frac{\sum q_i \cdot n_i}{\sum n_i}\)

Code
m = sum(qi * ni) / sum(ni)
cat("Moyenne pondérée m =", m)
Moyenne pondérée m = 100.068

Étape 3 : Calcul de l’écart-type pondéré

\(\sigma = \sqrt{\frac{\sum n_i (q_i - m)^2}{\sum n_i - 1}}\)

Code
sigma = sqrt(sum(ni * (qi - m)^2) / (sum(ni) - 1))
cat("Écart-type pondéré sigma =", round(sigma, 2))
Écart-type pondéré sigma = 3.08

Étape 4 : Fréquences cumulées et quantiles théoriques

Code
Fi = cumsum(ni) / sum(ni)
qietoile = qnorm(Fi, m, sigma)

Étape 5 : Tracé du QQ-plot

Code
plot(qi, qietoile,
     main = "QQ-Plot : Tensions de pile",
     xlab = "Quantiles observés (qi)",
     ylab = "Quantiles théoriques (qi*)",
     pch = 19, col = "blue")
lines(c(90, 110), c(90, 110), col = "red", lwd = 2)
legend("topleft", legend = "Diagonale y = x", col = "red", lwd = 2)

Conclusion : Les points sont bien alignés sur la diagonale. On observe une bonne adéquation avec une loi Normale \(\mathcal{N}(m, \sigma)\).


2. Test du χ² pour la normalité

Puisque nous ne disposons pas des données brutes, nous ne pouvons pas faire un test de Shapiro-Wilk. Nous allons donc utiliser le test du χ² pour vérifier l’adéquation à la loi normale.

2.1 Fusion des effectifs faibles

Pour que le test du χ² soit valide, il faut que les effectifs théoriques soient supérieurs à 5. On fusionne donc les 2 premiers et les 3 derniers effectifs :

Code
ni2 = c(ni[1] + ni[2], ni[3:15], ni[16] + ni[17] + ni[18])
cat("Effectifs regroupés :", ni2)
Effectifs regroupés : 5 9 28 23 37 51 63 65 60 48 38 32 23 12 6

2.2 Calcul des probabilités théoriques

On calcule les probabilités théoriques \(p_i\) en faisant la correction de continuité (bornes à ±0.5) :

Code
k = c(91, 93:106) + 0.5
K = c(93:106.5, 109) + 0.5
pi_theo = pnorm(K, 100, 3.1) - pnorm(k, 100, 3.1)
ti = 500 * pi_theo

2.3 Calcul de la statistique du χ²

Code
dchi = sum((ni2 - ti)^2 / ti)
cat("Statistique du chi-deux :", round(dchi, 4))
Statistique du chi-deux : 12.0494

On peut aussi utiliser la fonction chisq.test :

Code
chisq.test(ni2, p = pi_theo, rescale.p = TRUE)

    Chi-squared test for given probabilities

data:  ni2
X-squared = 11.991, df = 14, p-value = 0.607

2.4 Degrés de liberté et conclusion

Attention ! Les degrés de liberté valent \(15 - 1 - 2 = 12\) (car nous avons estimé 2 paramètres : \(m\) et \(\sigma\)). Or chisq.test() n’utilise pas les bons d.d.l. et donne une p-value erronée.

Calculons la p-value correcte :

Code
cat("Quantile théorique χ²(0.95, 12) =", qchisq(0.95, 12))
cat("\nStatistique observée =", round(dchi, 4))
cat("\np-value correcte =", 1 - pchisq(dchi, 12))
Quantile théorique χ²(0.95, 12) = 21.02607
Statistique observée = 12.0494
p-value correcte = 0.4417218

Conclusion : La p-value est supérieure à 0.05 → on ne rejette pas l’hypothèse de normalité au seuil 5%.


3. Test du χ² d’indépendance

3.1 Importation des données du Titanic

Le fichier titanic.csv contient le registre des passagers du Titanic. L’objectif est d’étudier la relation entre survie et classe à l’aide du test d’indépendance de Chi-deux.

Code
titanic = read.csv("titanic.csv")
head(titanic)
A data.frame: 6 × 12
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
<int> <int> <int> <chr> <chr> <dbl> <int> <int> <chr> <dbl> <chr> <chr>
1 1 0 3 Braund. Mr. Owen Harris male 22 1 0 A/5 21171 7.2500 S
2 2 1 1 Cumings. Mrs. John Bradley (Florence Briggs Thayer) female 38 1 0 PC 17599 71.2833 C85 C
3 3 1 3 Heikkinen. Miss. Laina female 26 0 0 STON/O2. 3101282 7.9250 S
4 4 1 1 Futrelle. Mrs. Jacques Heath (Lily May Peel) female 35 1 0 113803 53.1000 C123 S
5 5 0 3 Allen. Mr. William Henry male 35 0 0 373450 8.0500 S
6 6 0 3 Moran. Mr. James male NA 0 0 330877 8.4583 Q

Stockons les variables d’intérêt :

Code
survived = titanic$Survived
class = titanic$Pclass
head(survived)
head(class)
  1. 0
  2. 1
  3. 1
  4. 1
  5. 0
  6. 0
  1. 3
  2. 1
  3. 3
  4. 1
  5. 3
  6. 3

3.2 Test d’indépendance : Survie vs Classe

Construisons le tableau de contingence :

Code
contingence1 = table(survived, class)
contingence1
        class
survived   1   2   3
       0  80  97 372
       1 136  87 119

Effectuons le test du χ² d’indépendance :

Code
chisq.test(contingence1)

    Pearson's Chi-squared test

data:  contingence1
X-squared = 102.89, df = 2, p-value < 2.2e-16

Interprétation : La p-value est inférieure à 0.05 (pratiquement égale à 0) → on rejette significativement l’hypothèse d’indépendance. La classe et la survie sont dépendantes. Malheureusement, on le savait…

3.3 Autres tests d’indépendance

Survie vs Sexe

Code
contingence_sex = table(survived, titanic$Sex)
contingence_sex
chisq.test(contingence_sex)
        
survived female male
       0     81  468
       1    233  109

    Pearson's Chi-squared test with Yates' continuity correction

data:  contingence_sex
X-squared = 260.72, df = 1, p-value < 2.2e-16

Classe vs Port d’embarquement

Code
contingence2 = table(class, titanic$Embarked)
contingence2
     
class       C   Q   S
    1   2  85   2 127
    2   0  17   3 164
    3   0  66  72 353

On remarque une colonne vide (port inconnu). Supprimons-la :

Code
contingence2 = contingence2[, -1]
contingence2
chisq.test(contingence2)
     
class   C   Q   S
    1  85   2 127
    2  17   3 164
    3  66  72 353

    Pearson's Chi-squared test

data:  contingence2
X-squared = 123.75, df = 4, p-value < 2.2e-16

Survie vs Port d’embarquement

Code
contingence3 = table(survived, titanic$Embarked)
contingence3 = contingence3[, -1]
chisq.test(contingence3)

    Pearson's Chi-squared test

data:  contingence3
X-squared = 26.489, df = 2, p-value = 1.77e-06

4. Normalité et fonctions de R

4.1 Acquisition de la variable age

Code
age = titanic$Age
head(age)
  1. 22
  2. 38
  3. 26
  4. 35
  5. 35
  6. <NA>

Certains âges sont manquants (NA). Gardons seulement les âges connus :

Code
age = age[!is.na(age)]
class2 = class[!is.na(titanic$Age)]
cat("Nombre d'âges valides :", length(age))
Nombre d'âges valides : 714

4.2 Normalité de age

Densité estimée

Code
plot(density(age),
     main = "Densité de la variable Age",
     xlab = "Âge", ylab = "Densité",
     col = "blue", lwd = 2)

QQ-plot

Code
qqnorm(age, main = "QQ-plot de Age")
qqline(age, col = "red")

Test de Shapiro-Wilk

Code
shapiro.test(age)

    Shapiro-Wilk normality test

data:  age
W = 0.98146, p-value = 7.337e-08

Conclusion : La densité n’a pas l’allure d’une gaussienne, le QQ-plot dévie, et le test de Shapiro-Wilk rejette la normalité (p-value < 0.05). La variable age n’est pas normale.

4.3 Normalité de age par classe

La fonction tapply permet d’appliquer une fonction à une variable divisée par sous-groupes :

Code
tapply(age, class2, shapiro.test)
$`1`

    Shapiro-Wilk normality test

data:  X[[i]]
W = 0.99169, p-value = 0.3643


$`2`

    Shapiro-Wilk normality test

data:  X[[i]]
W = 0.97695, p-value = 0.005648


$`3`

    Shapiro-Wilk normality test

data:  X[[i]]
W = 0.97344, p-value = 4.186e-06

Conclusion : Seul en première classe, l’âge peut être considéré comme normal. Reste à savoir si cette information est vraiment intéressante…


Résumé

Outil Utilisation Fonction R
QQ-plot Visualiser l’adéquation à une loi qqnorm(), plot()
Test χ² d’adéquation Tester si les données suivent une loi chisq.test()
Test χ² d’indépendance Tester l’indépendance de 2 variables chisq.test(table())
Test de Shapiro-Wilk Tester la normalité (données brutes) shapiro.test()
tapply Appliquer une fonction par sous-groupe tapply(var, groupe, fun)