Chapitre 1117 minIntermédiaire
Statistiques avec un tableur et en Python
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- calculer les indicateurs et la droite d'ajustement dans Excel ou Calc ;
- afficher une courbe de tendance avec son équation et R² ;
- utiliser le module statistics de Python sur une série ou un CSV ;
- calculer moyenne et écart type en SQL dans une base.
3 exercices corrigés
Un tableur (Excel ou LibreOffice Calc) et le module statistics de Python calculent tous les indicateurs de ce cours sur des milliers de valeurs, tracent le nuage et sa droite, et refont le calcul dès qu'une donnée change. Ce chapitre donne les fonctions utiles dans les deux tableurs, la manière d'afficher une courbe de tendance, le module statistics (sans bibliothèque externe), la lecture d'un export CSV et le calcul des indicateurs directement en SQL. Le programme les rend indispensables : « l'utilisation de logiciels, notamment d'un tableur, et des calculatrices est nécessaire ».
Les fonctions du tableur
Dans un tableur en français, les arguments se séparent par un point-virgule et les décimales s'écrivent avec une virgule. Excel et Calc ont presque les mêmes noms de fonctions ; quelques-uns diffèrent.
| Indicateur | Excel | LibreOffice Calc |
|---|---|---|
| Effectif (cellules numériques) | NB | NB |
| Moyenne | MOYENNE | MOYENNE |
| Médiane | MEDIANE | MEDIANE |
| Mode | MODE.SIMPLE | MODE.SIMPLE |
| Quartile | QUARTILE.INCLURE | QUARTILE.INCLURE |
| Centile | CENTILE.INCLURE | CENTILE.INCLURE |
| Minimum, maximum | MIN, MAX | MIN, MAX |
| Variance (÷ N) | VAR.P | VAR.P |
| Écart type σ (÷ N) | ECARTYPE.PEARSON | ECARTYPE.P |
| Écart type s (÷ N − 1) | ECARTYPE.STANDARD | ECARTYPE.S |
| Covariance (÷ N) | COVARIANCE.PEARSON | COVARIANCE.PEARSON |
| Coefficient directeur a | PENTE | PENTE |
| Ordonnée à l'origine b | ORDONNEE.ORIGINE | ORDONNEE.ORIGINE |
| Coefficient de corrélation r | COEFFICIENT.CORRELATION | COEFFICIENT.CORRELATION |
| Coefficient de détermination r² | COEFFICIENT.DETERMINATION | COEFFICIENT.DETERMINATION |
| Prévision par la droite | PREVISION.LINEAIRE | PREVISION.LINEAIRE |
| Logarithme népérien | LN | LN |
Pour les sessions et la charge processeur du chapitre 6, saisies en A2:A11 (x) et B2:B11 (y) :
=MOYENNE(A2:A11) → 22,3
=MOYENNE(B2:B11) → 33,4
=PENTE(B2:B11;A2:A11) → 1,2357…
=ORDONNEE.ORIGINE(B2:B11;A2:A11) → 5,8435…
=COEFFICIENT.CORRELATION(A2:A11;B2:B11) → 0,9987…
=PREVISION.LINEAIRE(30;B2:B11;A2:A11) → 42,9…
Pour un ajustement exponentiel, ajoute une colonne =LN(B2) recopiée vers le bas, puis applique PENTE et ORDONNEE.ORIGINE à cette colonne : tu obtiens les coefficients de z = ax + b (chapitre 9).
Le nuage et la courbe de tendance
- Sélectionne les deux colonnes de données.
- Insère un graphique de type Nuage de points (Excel) ou XY (dispersion) (Calc), sans lignes entre les points.
- Clic droit sur un point du nuage : Ajouter une courbe de tendance (Excel) ou Insérer une courbe de tendance (Calc).
- Choisis le type Linéaire (ou Exponentielle pour un modèle y = k × eᵃˣ), puis coche l'affichage de l'équation et du coefficient de détermination R² sur le graphique.
Le R² affiché est le carré du coefficient de corrélation de l'ajustement (chapitre 8). Il ne dit pas si r est positif ou négatif : regarde le signe de la pente. Pour la courbe exponentielle, le tableur ajuste ln y en x, comme au chapitre 9, et affiche le modèle sous la forme y = k × eᵃˣ.
Le module statistics de Python
Le module statistics de la bibliothèque standard calcule les indicateurs sans rien installer. correlation, covariance et linear_regression demandent Python 3.10 ou plus récent ; la version stable actuelle est Python 3.15.
import statistics as st
# Durées des 17 interventions de la base parc (en minutes)
durees = [45, 20, 30, 40, 10, 60, 90, 120, 15, 15, 240, 180, 150, 55, 35, 90, 60]
print("Effectif :", len(durees))
print("Moyenne :", round(st.mean(durees), 2))
print("Médiane :", st.median(durees))
print("Modes :", st.multimode(durees))
print("Écart type σ :", round(st.pstdev(durees), 2)) # divise par N
print("Écart type s :", round(st.stdev(durees), 2)) # divise par N - 1
print("Quartiles :", st.quantiles(durees, n=4, method="inclusive"))
print("Étendue :", max(durees) - min(durees))
Effectif : 17
Moyenne : 73.82
Médiane : 55
Modes : [60, 90, 15]
Écart type σ : 62.95
Écart type s : 64.89
Quartiles : [30.0, 55.0, 90.0]
Étendue : 230
| Fonction | Ce qu'elle calcule | Attention |
|---|---|---|
mean, median | Moyenne, médiane | — |
mode, multimode | Le premier mode rencontré, la liste des modes | mode ne signale pas qu'il y en a plusieurs |
pstdev, pvariance | σ et σ² (division par N) | Ce sont ceux de la statistique descriptive |
stdev, variance | s et s² (division par N − 1) | Pour un échantillon |
quantiles(data, n=4) | Les trois quartiles (n=10 : les déciles) | Méthode exclusive par défaut ; method="inclusive" donne ici les quartiles du cours, pas toujours |
covariance(x, y) | Covariance | Elle divise par N − 1 : multiplie par (N − 1) ÷ N pour retrouver celle du cours |
correlation(x, y) | Coefficient r | Identique quelle que soit la division, N ou N − 1 |
linear_regression(x, y) | Pente et ordonnée à l'origine de la droite de y en x | Renvoie un couple (slope, intercept) |
Un ajustement en Python
La droite des moindres carrés et le coefficient de corrélation des sessions et de la charge :
import statistics as st
sessions = [4, 8, 12, 15, 20, 24, 28, 33, 37, 42]
cpu = [11, 15, 22, 24, 31, 34, 41, 46, 52, 58]
pente, ordonnee = st.linear_regression(sessions, cpu)
r = st.correlation(sessions, cpu)
print(f"y = {pente:.2f}x + {ordonnee:.2f}")
print(f"r = {r:.4f} r² = {r * r:.4f}")
print("Point moyen :", st.mean(sessions), st.mean(cpu))
print(f"Prévision pour 30 sessions : {pente * 30 + ordonnee:.1f} %")
y = 1.24x + 5.84
r = 0.9987 r² = 0.9974
Point moyen : 22.3 33.4
Prévision pour 30 sessions : 42.9 %
La prévision vaut 42,9 % avec les coefficients exacts, 43,0 % avec les coefficients arrondis à 10⁻² : sur une copie, utilise ceux que tu as écrits.
L'observation de la somme minimale demandée par le programme (chapitre 7) tient en une fonction :
import statistics as st
def somme_carres(x, y, a, b):
"""Somme des carrés des écarts entre les y observés et la droite y = ax + b."""
return sum((yi - (a * xi + b)) ** 2 for xi, yi in zip(x, y))
volume = [20, 40, 60, 80, 100] # Go
duree = [14, 22, 33, 41, 50] # min
a, b = st.linear_regression(volume, duree)
print(f"Droite des moindres carrés : y = {a:.3f}x + {b:.1f}")
for pente, ordonnee in [(a, b), (0.45, 5), (0.46, 4.4), (0.5, 2)]:
print(f"a = {pente:.3f}, b = {ordonnee:.1f} -> S = {somme_carres(volume, duree, pente, ordonnee):.2f}")
Droite des moindres carrés : y = 0.455x + 4.7
a = 0.455, b = 4.7 -> S = 1.90
a = 0.450, b = 5.0 -> S = 2.00
a = 0.460, b = 4.4 -> S = 2.00
a = 0.500, b = 2.0 -> S = 10.00
Et l'ajustement exponentiel du volume des index Wazuh, avec le changement de variable z = ln y :
import math
import statistics as st
mois = [1, 2, 3, 4, 5, 6, 7, 8]
volume = [10.0, 11.3, 12.5, 14.1, 15.8, 17.6, 19.8, 22.2] # Go
z = [math.log(v) for v in volume] # changement de variable z = ln y
a, b = st.linear_regression(mois, z) # ajustement affine de z en x
k, q = math.exp(b), math.exp(a) # retour à y = k × q^x
print(f"z = {a:.3f}x + {b:.3f}")
print(f"y = {k:.1f} × {q:.2f}^x")
print(f"r(x, y) = {st.correlation(mois, volume):.4f}")
print(f"r(x, z) = {st.correlation(mois, z):.4f}")
seuil = (math.log(48) - b) / a
print(f"48 Go atteints pendant le mois n° {math.ceil(seuil)}")
z = 0.113x + 2.191
y = 8.9 × 1.12^x
r(x, y) = 0.9933
r(x, z) = 0.9999
48 Go atteints pendant le mois n° 15
En Python, math.log est le logarithme népérien (ln) ; le logarithme décimal s'écrit math.log10.
Lire un export CSV
Zabbix et GLPI exportent leurs données en CSV. Le module csv de la bibliothèque standard les lit ; ici, l'export est placé dans une chaîne pour que l'exemple tourne tel quel. Avec un vrai fichier, remplace io.StringIO(export) par open("export.csv", encoding="utf-8").
import csv
import io
import statistics as st
# Extrait d'un export CSV de Zabbix : horodatage et temps de réponse (en secondes)
export = """clock;value
2026-11-02 09:00;0.212
2026-11-02 09:05;0.198
2026-11-02 09:10;0.231
2026-11-02 09:15;0.187
2026-11-02 09:20;0.640
2026-11-02 09:25;0.205
2026-11-02 09:30;0.219
2026-11-02 09:35;0.193
"""
lecteur = csv.DictReader(io.StringIO(export), delimiter=";")
temps_ms = [float(ligne["value"]) * 1000 for ligne in lecteur]
print("Mesures :", len(temps_ms))
print(f"Moyenne : {st.mean(temps_ms):.1f} ms")
print(f"Médiane : {st.median(temps_ms):.1f} ms")
print(f"σ : {st.pstdev(temps_ms):.1f} ms")
print(f"Maximum : {max(temps_ms):.1f} ms")
Mesures : 8
Moyenne : 260.6 ms
Médiane : 208.5 ms
σ : 144.0 ms
Maximum : 640.0 ms
Une seule mesure lente (640 ms à 9 h 20) place la moyenne au-dessus de toutes les autres valeurs : c'est la médiane, 208,5 ms, qui décrit le temps habituel (chapitre 2).
Et directement en SQL
Quand les données sont dans une base, inutile de les exporter : le SGBD calcule les indicateurs. Sur la base parc (MySQL 8.4) :
SELECT COUNT(*) AS n,
ROUND(AVG(duree_min), 1) AS moyenne,
ROUND(STDDEV_POP(duree_min), 1) AS ecart_type,
MIN(duree_min) AS mini,
MAX(duree_min) AS maxi
FROM intervention;
La requête renvoie 17 interventions, une moyenne de 73,8 min, un écart type de 63,0 min, un minimum de 10 et un maximum de 240. STDDEV_POP et VAR_POP divisent par N (σ), STDDEV_SAMP et VAR_SAMP par N − 1 (s).
Par technicien, avec une jointure et un GROUP BY (cours SQL) :
SELECT e.prenom, e.nom,
COUNT(*) AS n,
ROUND(AVG(i.duree_min), 1) AS moyenne,
ROUND(STDDEV_POP(i.duree_min), 1) AS ecart_type
FROM intervention i
JOIN employe e ON e.id_employe = i.id_technicien
GROUP BY e.id_employe, e.prenom, e.nom
ORDER BY moyenne;
| prenom | nom | n | moyenne | ecart_type |
|---|---|---|---|---|
| Chloé | Lambert | 6 | 49.2 | 47.9 |
| Moussa | Diallo | 7 | 64.3 | 52.3 |
| Antoine | Mercier | 4 | 127.5 | 68.3 |
MySQL n'a pas de fonction médiane. Pour une série de 17 valeurs, la médiane est la 9e : SELECT duree_min FROM intervention ORDER BY duree_min LIMIT 1 OFFSET 8; renvoie 55. Dans le cas général, on numérote les lignes avec une fonction de fenêtrage, ROW_NUMBER() (fonctions de fenêtrage).
Exercices
Exercice 1 sur 3 La feuille des sauvegardes Réussi À revoir
Dans un tableur, les volumes des cinq sauvegardes (20 à 100 Go) sont en A2:A6 et les durées (14 à 50 min) en B2:B6.
- Écris les formules qui donnent le point moyen, a, b et r.
- Écris la formule qui estime la durée pour 150 Go.
- Un collègue a écrit
=PENTE(A2:A6;B2:B6)et trouve 2,19. Explique son erreur.
Voir la réponseMasquer la réponse de l'exercice 1
=MOYENNE(A2:A6)(60) et=MOYENNE(B2:B6)(32) ;=PENTE(B2:B6;A2:A6)(0,455) ;=ORDONNEE.ORIGINE(B2:B6;A2:A6)(4,7) ;=COEFFICIENT.CORRELATION(A2:A6;B2:B6)(0,9989…).=PREVISION.LINEAIRE(150;B2:B6;A2:A6), qui renvoie 72,95, soit environ 73 minutes.- Il a inversé les plages :
PENTEattend d'abord les y. Sa formule calcule le coefficient directeur de la droite de x en y (x = 2,19y − 10,17), qui exprime les gigaoctets en fonction des minutes.
Tu avais trouvé ?
Exercice 2 sur 3 Moyenne et écart type d'un tableau d'effectifs Réussi À revoir
Écris en Python deux fonctions, moyenne_ponderee(valeurs, effectifs) et ecart_type(valeurs, effectifs) (σ, division par N), sans utiliser le module statistics. Teste-les sur les tickets du chapitre 1 (valeurs 0 à 6, effectifs 1, 3, 5, 6, 3, 1, 1), puis vérifie le résultat avec statistics.
Voir la réponseMasquer la réponse de l'exercice 2
import math
import statistics as st
def moyenne_ponderee(valeurs, effectifs):
"""Moyenne d'une série donnée par un tableau valeurs / effectifs."""
n = sum(effectifs)
return sum(x * e for x, e in zip(valeurs, effectifs)) / n
def ecart_type(valeurs, effectifs):
"""Écart type σ (division par N), par la formule de König-Huygens."""
n = sum(effectifs)
m = moyenne_ponderee(valeurs, effectifs)
moyenne_carres = sum(x * x * e for x, e in zip(valeurs, effectifs)) / n
return math.sqrt(moyenne_carres - m * m)
tickets = [0, 1, 2, 3, 4, 5, 6]
jours = [1, 3, 5, 6, 3, 1, 1]
print(moyenne_ponderee(tickets, jours))
print(round(ecart_type(tickets, jours), 3))
# Vérification avec le module statistics, sur la série « dépliée »
serie = [x for x, e in zip(tickets, jours) for _ in range(e)]
print(st.mean(serie), round(st.pstdev(serie), 3))
2.7
1.418
2.7 1.418
On retrouve x̄ = 2,7 et σ ≈ 1,42, comme au chapitre 4. La série « dépliée » répète chaque valeur autant de fois que son effectif.
Tu avais trouvé ?
Exercice 3 sur 3 La durée selon la priorité Réussi À revoir
Sur la base parc, écris une requête qui donne, pour chaque priorité de ticket, le nombre d'interventions et leur durée moyenne arrondie à 10⁻¹, triées de la plus courte à la plus longue. Que t'apprend le résultat sur l'exercice « Classer les techniciens ? » du chapitre 5 ?
Voir la réponseMasquer la réponse de l'exercice 3
SELECT t.priorite,
COUNT(*) AS n,
ROUND(AVG(i.duree_min), 1) AS moyenne
FROM intervention i
JOIN ticket t ON t.id_ticket = i.id_ticket
GROUP BY t.priorite
ORDER BY moyenne;
| priorite | n | moyenne |
|---|---|---|
| basse | 2 | 12.5 |
| normale | 4 | 36.3 |
| haute | 6 | 60.8 |
| critique | 5 | 144.0 |
La durée d'une intervention dépend fortement de la priorité du ticket : de 12,5 minutes en moyenne pour une priorité basse à 144 minutes pour une priorité critique. Comparer des techniciens qui ne traitent pas les mêmes priorités revient à comparer des tickets, pas des personnes : c'est le facteur caché de l'exercice du chapitre 5.
Tu avais trouvé ?