Skip to article frontmatterSkip to article content

Analyse des blancs – Visualisation et interprétation

Ce graphique représente une série temporelle de mesures de blancs analytiques, c’est-à-dire des échantillons censés ne contenir aucun élément détectable.
Ces mesures sont utilisées pour vérifier la qualité des analyses et détecter d’éventuelles contaminations.

🎯 Objectifs de l’exercice :

  • Visualiser les blancs dans l’ordre d’analyse.
  • Contrôler la variabilité des mesures à l’aide de bandes d’erreur statistiques (±3LD, ±5LD, ±10LD). LD est la limite de détection de l’appareil, il est courant de basé la mesure d’erreur sur cette valeur. Des fois, on se basse sur un pourcentage de la teneur de coupure, p.ex, 5% t.c..
  • Identifier les valeurs aberrantes (hors de la distribution normale attendue).
  • Comparer les mesures aux seuils d’acceptabilité :
    • Seuils basés sur la limite de détection (LD) : 3LD, 5LD, 10LD.
    • Seuils relatifs à la teneur de coupure : 5% et 10%.

📊 Ce que montre le graphique :

  • Les points verts représentent les mesures des blancs.
  • Les bandes vertes autour de la ligne de base (0 ppm) indiquent les intervalles de confiance statistiques.
  • Les lignes bleues sont les seuils de contrôle utilisés pour détecter les échecs.
  • Les points rouges signalent les valeurs aberrantes.
  • Le nombre de valeurs dépassant la limite de détection est affiché dans le titre.
Source
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider

def generate_blank_series(n_points=1000, noise_level=1.0):
    t = np.arange(n_points)
    base = 0.0
    noise = np.random.normal(0, noise_level, size=n_points)
    series = base + noise
    series = np.clip(series, 0, None)
    return t, series

def plot_blank_series(noise_level=1.0, standard_error=1.0):
    np.random.seed(42)
    n_points = 1000
    t, blancs = generate_blank_series(n_points, noise_level)
    
    plt.figure(figsize=(12, 5))
    plt.plot(t, blancs, label="Blancs mesurés", color='green', linestyle='', marker='x')

    base_blanc = np.zeros_like(t)

    # Zones ±3LD, ±5LD, ±10LD
    limits = [3, 5, 10]
    alphas = [0.3, 0.2, 0.1]
    for k, alpha in zip(limits, alphas):
        upper_bound = base_blanc + k * standard_error
        lower_bound = np.maximum(base_blanc - k * standard_error, 0)
        plt.fill_between(t, lower_bound, upper_bound,
                         color='green', alpha=alpha, label=f'+{k} LD')

    plt.plot(t, base_blanc, color='green', linestyle='--', label="Teneur attendue (blanc)")

    diff = blancs - base_blanc

    # Catégories d’outliers
    idx_1_3LD = (diff > 1 * standard_error) & (diff <= 3 * standard_error)
    idx_3_5LD = (diff > 3 * standard_error) & (diff <= 5 * standard_error)
    idx_5_10LD = (diff > 5 * standard_error) & (diff <= 10 * standard_error)
    idx_sup_10LD = (diff > 10 * standard_error)

    # Couleurs
    colors = np.array(['none'] * len(diff))
    colors[idx_1_3LD] = '#ffd480'    # orange pâle
    colors[idx_3_5LD] = '#ff9999'    # rouge clair
    colors[idx_5_10LD] = '#cc3333'   # rouge moyen
    colors[idx_sup_10LD] = '#660000' # rouge foncé

    outlier_indices = np.where(colors != 'none')[0]
    if outlier_indices.size > 0:
        plt.scatter(t[outlier_indices], blancs[outlier_indices],
                    s=50,
                    color=colors[outlier_indices],
                    label="Outliers détectés",
                    edgecolors='black', linewidths=0.7,
                    alpha=0.9)

    # Comptage par classe
    count_1_3LD = np.sum(idx_1_3LD)
    count_3_5LD = np.sum(idx_3_5LD)
    count_5_10LD = np.sum(idx_5_10LD)
    count_sup_10LD = np.sum(idx_sup_10LD)
    total_outliers = count_1_3LD + count_3_5LD + count_5_10LD + count_sup_10LD

    # Titre avec les totaux
    plt.title(f"Série des blancs avec détection d’outliers\n"
              f"Total outliers: {total_outliers} (1–3LD: {count_1_3LD}, 3–5LD: {count_3_5LD}, "
              f"5–10LD: {count_5_10LD}, >10LD: {count_sup_10LD})")
    plt.xlim([0, 1000])
    plt.xlabel("Temps (échantillon)")
    plt.ylabel("Teneur (ppm)")
    plt.ylim(-0.5, 30 * standard_error)
    plt.legend()
    plt.grid(True)
    plt.show()

interact(
    plot_blank_series,
    noise_level=FloatSlider(value=1.0, min=0.1, max=5.0, step=0.1, description="Niveau de bruit"),
    standard_error=FloatSlider(value=0.5, min=0.1, max=2.0, step=0.1, description="Limite de détection (LD)"),
)
Loading...