Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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.

IndicateurExcelLibreOffice Calc
Effectif (cellules numériques)NBNB
MoyenneMOYENNEMOYENNE
MédianeMEDIANEMEDIANE
ModeMODE.SIMPLEMODE.SIMPLE
QuartileQUARTILE.INCLUREQUARTILE.INCLURE
CentileCENTILE.INCLURECENTILE.INCLURE
Minimum, maximumMIN, MAXMIN, MAX
Variance (÷ N)VAR.PVAR.P
Écart type σ (÷ N)ECARTYPE.PEARSONECARTYPE.P
Écart type s (÷ N − 1)ECARTYPE.STANDARDECARTYPE.S
Covariance (÷ N)COVARIANCE.PEARSONCOVARIANCE.PEARSON
Coefficient directeur aPENTEPENTE
Ordonnée à l'origine bORDONNEE.ORIGINEORDONNEE.ORIGINE
Coefficient de corrélation rCOEFFICIENT.CORRELATIONCOEFFICIENT.CORRELATION
Coefficient de détermination r²COEFFICIENT.DETERMINATIONCOEFFICIENT.DETERMINATION
Prévision par la droitePREVISION.LINEAIREPREVISION.LINEAIRE
Logarithme népérienLNLN

Pour les sessions et la charge processeur du chapitre 6, saisies en A2:A11 (x) et B2:B11 (y) :

text
=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

  1. Sélectionne les deux colonnes de données.
  2. Insère un graphique de type Nuage de points (Excel) ou XY (dispersion) (Calc), sans lignes entre les points.
  3. Clic droit sur un point du nuage : Ajouter une courbe de tendance (Excel) ou Insérer une courbe de tendance (Calc).
  4. 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.

python
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))
text
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
FonctionCe qu'elle calculeAttention
mean, medianMoyenne, médiane—
mode, multimodeLe premier mode rencontré, la liste des modesmode ne signale pas qu'il y en a plusieurs
pstdev, pvarianceσ et σ² (division par N)Ce sont ceux de la statistique descriptive
stdev, variances 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)CovarianceElle divise par N − 1 : multiplie par (N − 1) ÷ N pour retrouver celle du cours
correlation(x, y)Coefficient rIdentique 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 xRenvoie 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 :

python
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} %")
text
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 :

python
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}")
text
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 :

python
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)}")
text
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").

python
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")
text
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) :

sql
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) :

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;
prenomnomnmoyenneecart_type
ChloéLambert649.247.9
MoussaDiallo764.352.3
AntoineMercier4127.568.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.

  1. Écris les formules qui donnent le point moyen, a, b et r.
  2. Écris la formule qui estime la durée pour 150 Go.
  3. 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
  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…).
  2. =PREVISION.LINEAIRE(150;B2:B6;A2:A6), qui renvoie 72,95, soit environ 73 minutes.
  3. Il a inversé les plages : PENTE attend 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
python
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))
text
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
sql
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;
prioritenmoyenne
basse212.5
normale436.3
haute660.8
critique5144.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é ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Exercices corrigés de statistiques type EF2Revoir le sommaire du cours