Traiter une série de mesures avec Python

La place de la mesure et de son traitement en physique

La physique et l'action de mesure

La physique est une science expérimentale. Toutes ses lois, toutes ses théories sont inévitablement confrontées à l'expérience. C'est même un fondement de notre science, et plus généralement des sciences : n'est considérée comme scientifique qu'une théorie qui est falsifiable (ou réfutable, plus français) par l'expérience, comme nous l'a appris Karl Popper dans les années 1930.

De la mécanique à la physique nucléaire, de la thermodynamique à la physique quantique, l'activité expérimentale est fondamentale, dans le sens étymologique où elle permet de fonder une théorie. L'expérimentation aboutit toujours à une mesure. Mesurer, c'est quantifier une caractéristique physique, une longueur, une température, une tension... Pour vous convaincre de l'importance de la mesure, je vous renvoie à l'histoire récente de la découverte du boson de Higgs. Cette découverte ne fut annoncée et officialisée que lorsque toutes les vérifications statistiques sur les milliards de mesures réalisées dans les détecteurs du LHC ont été validées.

Dans une actualité encore plus récente, je vous invite à lire l'article de "Pour la Science" d'octobre 2016 à propos de l'énigme de la durée de vie du neutron. Vous y constaterez toute l'importance de l'analyse statistique des mesures pour comparer les résulats de deux équipes. Selon la technique de mesure, et toutes erreurs aléatoires prises en compte, la durée de vie d'un neutron diffère de 8 secondes. Alors, erreur systématique ou nouvelle loi physique ?

Prenons un exemple simple, connu de tous les lycéens. Imaginons que vous vouliez vérifier la loi d'Ohm simplifiée, vous savez U = RI ! Pas l'établir, c'est un peu tard, mais simplement la vérifier. Vous allez monter une manip qui vous permettra avec une résistance R constante et en faisant varier l'intensité I, de mesurer la tension U. Pas vraiment difficile !

Oui mais... Vous avez obtenu une série de mesures de U mais que vaut-elle ? Etes-vous sur de ne pas avoir commis d'erreur en mesurant cette tension ? Quelle est l'incertitude sur cette mesure ? Evidemment, ce point est extrémement important, car il conditionne votre capacité à vérifier la loi d'Ohm... Comment s'assurer que nos mesures sont correctes ? Quels sont les outils à utiliser ?

Les outils de traitement des mesures

En mesurant une caractéristique physique, nous pouvons commettre deux types d'erreur: les erreurs aléatoires et les erreurs systématiques. La qualité d'un expérimentateur se révèle dans la capacité à éliminer autant que faire se peut ces dernières. Encore faut-il les identifier et pouvoir les corriger. Elles tiennent très souvent dans la méthode expérimentale, la nature et la qualité des intruments, la minutie des expérimentateurs, l'environnement et bien d'autres choses.

Les erreurs aléatoires sont incontournables, indissociables de l'activité de mesure. Mais heureusement, elles sont modélisables, contrairement aux erreurs systématiques. Nous disposons même d'outils très puissants pour traiter les erreurs aléatoires : les probabilités et les statistiques.

Tout tourne autour d'un théorème fondamental en probabilité : le théorème central limite. Ce théorème nous dit en substance que la distribution d'une série de mesures sujettes à des fluctuations aléatoires tend à sa limite, c'est à dire quand le nombre de mesures tend vers l'infini, vers une distribution gaussienne. Or la distribution gaussienne est très bien connue sur le plan mathématique et nous permet quelques calculs. Réciproquement, si la distribution d'une série de mesures ne tend pas vers une gaussienne, nous pourrons dire que les fluctuations ne sont pas aléatoires et proviennent d'erreurs systématiques.

Il suffit donc de calculer la distribution d'une série de mesures pour avoir une idée de sa qualité: elle tend vers une gaussienne ou pas, la gaussienne est plus ou moins écrasée et autres critères utiles.

Mesures et statistique

La loi normale

La loi normale, ou loi de Gauss, ou loi des grands nombres, s'exprime mathématique selon deux paramètres : \( \mu \), la moyenne et \( \sigma \), l'écart-type. Son expression est :
\( G(x) = \dfrac{1}{\sigma \sqrt(2 \pi)} \exp(\dfrac{-(x - \mu)^2}{2 \sigma ^2}) \)

Notons qu'en mathématique, on appelle la moyenne \( \mu\) l'espérance mathématique notée E[x].

La courbe représentative de cette fonction est la fameuse courbe en "cloche". Elle est symétrique par rapport à \( \mu \). L'abscisse de son sommet nous indique la valeur moyenne \( \mu \) de la variable x. L'écart-type \( \sigma \) caractérise l'aplatissement de la courbe, plus précisément la dispersion de la distribution autour de la moyenne. Plus \( \sigma \) est petit et plus on a de chance que les valeurs de x se regroupent autour de la moyenne.

S'agissant d'une fonction de densité de probabilité, l'aire sous la courbe sera toujours égale à 1. cette normalisation à 1 est obtenue par le coefficient \( \dfrac{1}{\sigma \sqrt(2 \pi)} \).

Pour illustrer cela, voyons l'aspect de la courbe représentative de la loi normale centrée réduite, cas particulier de gaussienne dont la moyenne est nulle et l'écart-type égal à 1. Le tracé est obtenu avec le script Python TMIncertitudeMesures3.py, en utilisant la fonction norm.pdf() du module stats de scipy.

Loi normale centrée réduite

La loi normale centrée réduite permet de calculer facilement la probabilité qu'une variable x prenne une valeurs inférieure ou égale à a, en posant \( Pb(x \le a) = F(a) = \dfrac{1}{ \sqrt(2 \pi)} \int_{-\infty}^a \exp(-\dfrac{x^2}{2})dx\). On peut en déduire la probabilité pour qu'une variable x soit comprise dans un intervalle [a,b], qui vaut F(a) - F(b).

Ainsi, la probabilité pour qu'une mesure aléatoire soit comprise entre \( \mu - \sigma \) et \( \mu + \sigma \) est égale à F(1) - F(-1), soit 0,68 ou 68%. Elle sera de 95% , ou F(2) - F(-2), pour que la variable soit comprise entre -2*sigma et 2*sigma.

Nous pouvons profiter de nos acquis en calcul symbolique sous Python (voir ici) pour faire rapidement ce calcul. Sur la console Python, introduisez chaque commande suivante et faites ENTER après chaque commande :

from sympy import *

init_printing()

x = Symbol("x")

expr = (1./sqrt(2*pi))*exp(-x*x/2.)

integrate(expr,(x,-oo,1)) - integrate(expr,(x,-oo,-1))

Le résultat retourné par la dernière commande est :

0.482734369334934⋅sqrt(2)

soit 0.6827. Vous pouvez faire le même calcul enn intégrant entre -2 sigma et 2 sigma avec :

integrate(expr,(x,-oo,2)) - integrate(expr,(x,-oo,-2))

ce qui vous donnera 95% de chance de trouver x sous la courbe entre -2 et 2 sigma.

L'expression d'un résultat de mesure

Un résultat expérimental, exprimant une mesure doit s'exprimer sous la forme :
\( x = \bar{x} \pm \delta x \)
où \( \bar{x} \) est la meilleure estimation de la valeur vraie de x et \( \delta x \) l'incertitude absolue sur la mesure, cette incertitude étant généralement l'écart-type de la distribution.

Il signifie, qu'en l'absence de toute erreur systématique, la probabilité que la valeur vraie de x soit comprise dans l'intervalle \( [\bar{x} - \delta x, \bar{x} + \delta x] \) est de 0,68 (68% de chance). J'insiste sur le fait que ce calcul n'est vrai que si toute erreur systématique a été éliminée, ce qui n'est pas toujours facile.

L'analyse statistique d'un échantillon

Pour exprimer une valeur x à partir d'un échantillon de dimension finie de mesures, il faut donc déterminer la meilleure estimation de la valeur vraie de x et la meilleure estimation de son écart-type.

La meilleure estimation de la valeur vraie de x est la moyenne \( \bar{x} \) des mesures de l'échantillon, ce qui est facile à calculer.

Le cas de l'estimateur de l'écart-type est un peu plus compliqué. Par définition, l'estimateur de l'écart-type, que je vais noter \( \sigma_x \) est égale à :
\( \sigma_x = \sqrt(\dfrac{1}{n-1} \sum_{i=1}^n (x_i - \bar{x})^2) \)

Mais dans le cas d'un nombre peu élevé de mesures, on montre en statistique que \( \sigma_x \) n'est pas un bon estimateur de l'écart-type de la distribution et qu'il est en fait égale à \( t*\dfrac{\sigma_x}{\sqrt(n)} \), où t est le coefficient de Student. Le coefficient de Student dépend de la taille de l'échantillon et du niveau de confiance requis. Les valeurs de t sont disponibles dans des tables spécialisées mais Python permet également de le calculer (dans le module stats.t, la méthode interval).

On peut cependant se simplifier un peu la vie : en regardant les tables de Student, on s'aperçoit que pour un intervalle de confiance d'un écart-type, le coefficient t tend rapidement vers 1 lorsque la taille n de l'échantillon dépasse la cinquantaine. Ce sera notre cas et donc je poserai t = 1 et finalement mon estimateur d'incertitude deviendra \( \delta x = \dfrac{\sigma_x}{\sqrt n}\).

En conclusion, j'exprimerai donc mon résultat de mesure sous la forme :
\( x = \bar{x} \pm \dfrac{\sigma_x}{\sqrt n} \)

Pour calculer l'écart type d'une série de mesures, vous pouvez utiliser la fonction std() de scipy ou bien la routine ci-dessous:

def Ecart_Type(liste):

n = len(liste)

ecart = 0

mu = mean(liste)

for i in range(n):

ecart = ecart + (liste[i] - mu)**2

ecart = sqrt(ecart/(n-1))

return ecart

qui n'est que l'impémentation pure et dure de la formule ci-dessus...

Histogramme et distribution normale

Imaginons une série de variables aléatoires, qui serait constituée de mesures physiques dont on serait certain qu'elles ne sont affectées par aucune erreur systèmatique, mais uniquement par des fluctuations aléatoires. Pour être sur de ce fait, je vais construire une série de mesures en partant d'une valeur moyenne auquel j'affecterai un coefficient aléatoire.

Le script Python TMIncertitudeMesures2.py construit une telle série avec les instructions suivantes:

U = 3.992

NbMesures = 1000

for i in range(NbMesures):

ListeMesures.append(random.normal(U,0.002))

L'instruction random.normal() construit une variable aléatoire de valeur moyenne U et d'écart-type 0.002. Nous verrons plus loin pourquoi ces valeurs numériques. La série comprend 1000 mesures, ce qui permet d'appliquer le théorème centrale limite.

Je vais d'abord estimer visuellement la forme de la distribution de ces mesures autour de la valeur moyenne que j'ai imposé, U = 3.992 V. Je vais construire pour cela un histogramme.

Un histogramme est une variété de graphique qui permet de visualiser la répartition du nombre de mesures dans une série en fonction de leur valeurs. On le construit assez facilement en découpant la plage de valeurs prises par les mesures en classes ou intervalles de valeurs. Le nombre de classes et donc leur largeur dépend de la série de mesures. Il y a des règles générales mais je préfère réfléchir un peu... Puis on compte le nombre de mesures dans chaque classe et on affiche des barres verticales, une par classe, dont la hauteur est proportionnelle au nombre de valeurs par classe. Rien de bien difficile...

Python dispose d'une fonction puissante pour tracer les histogrammes dans le module matplotlib. Il suffit de déclarer le nombre de classes (le nombre de barres) et la largeur de chaque classe (j'ai choisi une largeur égale à l'écart-type) :

NbClasses = 10

ClasseRange = 0.002

puis de faire appel à la fonction hist() :

plt.hist(ListeMesures, NbClasses, facecolor='green')

Je vais maintenant superposer à cet histogramme la courbe gaussienne avec pour paramètres la moyenne et l'écart-type de mon échantillon. Pour ce faire, je créé d'abord une fonction qui calcule la gaussienne :

def Gaussienne(x,moyenne,ecarttype):

return 1/(ecarttype*sqrt(2*pi))*exp(-(x-moyenne)**2/(2*ecarttype**2))

puis je calcule les vecteurs x et y pour pouvoir l'afficher:

x = arange(MinListe,MaxListe,0.0001)

y = Gaussienne(x,mean(ListeMesures),std(ListeMesures))

avec bien sur :

MinListe = min(ListeMesures)

MaxListe = max(ListeMesures)

et je l'affiche sur le même référentiel que l'histogramme

plt.plot(x,y,'blue')

et voici ce que j'obtiens :

Histogramme distribution aléatoire de mesures

Vous remarquez que la distribution des classes, même si notre série de mesures a été construite de façon aléatoire, ne colle pas parfaitement à la gaussienne. Il y a à cela deux explications.

La première est que notre série n'est pas vraiment aléatoire. Les générateurs de nombres dits "aléatoires" des ordinateurs ne le sont pas autant que ça ! Pour plus de précision, vous pouvez consulter cette page.

Mais surtout, notre échantillon ne comporte pas un nombre infini de mesures ! Il est relativement grand mais pas infini.

Acquérir et traiter une série de mesures obtenue avec Arduino

Pour travailler sur une série de mesures, encore faut-il disposer d'une telle série, suffisamment grande pour se passer des coefficients de Student. On peut toujours s'en procurer sur le net mais il est beaucoup plus amusant d'en produire une.

Et c'est facile avec les outils dont on dispose ! Par exemple, nous avons déjà utilisé la carte Arduino pour étudier la décharge d'une pile (voir ici). Aussi, je vous propose d'adopter le même principe pour acquérir une série de mesures, de tension par exemple.

Le principe de la manip

Notre carte Arduino dispose d'un convertisseur analogique-numérique (un CAN) qui permet de mesurer facilement des tensions comprises entre 0 et 5 V sans autres composants. Nous allons donc choisir une source de tension stable, en l'occurence ici une alimentation stabilisée de laboratoire puis programmer notre Arduino pour qu'il effectue un grand nombre de mesures de tension, 1 000 par exemple, sur cette source. Il est possible d'utiliser une pile si l'acquisition est suffisamment brève pour que l'on puisse négliger la décharge de la pile en cours d'acquisition. Cela devrait être le cas avec nos paramètres : 1 000 mesures en l'espace de 100 secondes.

Pour rappel, le CAN de la carte Arduino fonctionne selon le principe des approximations suucessives. Son pas de calcul (en fait sa précision) est égale à \( \dfrac{V_{ref}}{2^N} \). Dans le cas de l'Arduino, N = 10 et notre tension de référence est de 5V (on pourrait la diminuer). La précision du CAN, c'est à dire la plus petite tension qu'il est capable de discriminer est donc de 5/1023, soit 4,88 mV. Sa fréquence maxima d'acquisition est de 10 kHz, c'est à dire une acquisition toutes les 0,1 ms.

Le montage et le logiciel arduino

Le montage

Je suis parti du montage utilisé pour le suivi de décharge d'une pile en le simplifiant pour ne garder que l'essentiel : la carte Arduino et la carte supportant la mémoire SD. J'ai connecté directement les bornes de sortie de l'alimentation - et + respectivement sur les entrées Gnd (masse) et A0 de la carte Arduino. C'est tout !

Montage Arduino pour acquisition de mesures de tension

On distingue au premier plan la carte Arduino et le shield Ethernet qui supporte la mémoire SD, monté en "piggy-back". La connexion avec l'alimentation stabilisée est réalisée sur la plaque d'expérimentation au second plan. L'alimentation se trouve à l'arrière plan. Notez l'affichage sur l'alimentation : la tension affichée est de 4,00 volts et le courant délivré est nul.

Le programme

Vous devez bien sur disposer de l'environnement Arduino sur votre ordinateur. Cet environnement existe sous Windows, Linux et Mac OS, même dans les dernières versions. Si vous n'en disposez pas, vous pouvez le télécharger depuis le site d'Arduino.

Le programme est très semblable à celui écrit pour mesurer la décharge d'une pile. J'ai simplement viré la partie du code gérant la LED verte et modifier la condition de sortie de la boucle, qui n'est plus une tension seuil mais un nombre de mesures. La boucle est quittée lorsque le nombre de mesures demandé, 1000 ici, est atteint.

Le code du sketch est disponible ici.

Après avoir saisi ou recopier le code, il est toujours préférable de le vérifier avant de le compiler (nous faisons du C pas du Python !), ce qui s'obtient par la commande Croquis/Vérifier-Compiler du menu de la fenêtre Arduino.

Les mesures

Pour lancer l'acquisition, rien de plus simple. Il faut d'abord ouvrir le moniteur série pour suivre l'affichage du programme, ce qui se fait par le menu de la fenêtre Arduino Outils/Moniteur Série.

Puis après avoir vérifier le code comme indiqué ci-dessus, il suffit de le charger dans la carte Arduino à l'aide du menu Croquis/Téléverser. Au passage, admirez la traduction française pour sketch (Croquis) et upload (Téléverser) !

Notre logiciel a produit un fichier de 1 000 mesures de tension nommé MESURES1.CSV, disponible ici . Pour disposer d'une référence, j'ai mesuré la tension aux bornes de l'alimentation stabilisée avec un multimètre de laboratoire 4 000 points. Je lis sur le multimètre 3,992 V

Au fait, que signifie "un multimètre 4 000 points" ? Cela signifie qu'il effectue une mesure de tension avec une résolution numérique égale à la gamme de tension, dans mon cas 0 - 4 V divisé par le nombre de points que son convertisseur analogique-numérique sait mesurer, soit 4 000 points. Sa résolution numérique est donc dans mon cas 0,001 V. La résolution serait donc meilleure avec un multimètre 20 000 points. Autre chose, son impédance d'entrée est de 10 MOhms.

La tension mesurée sur l'alimentation stabilisée est donc de 3,992 V +- 0,001 V, à rapprocher des 4,00 V affichés par l'alimentation.

Nos petits bricolages électro-informatiques sont maintenant achevés et nous disposons d'un fichier au format CSV qui contient les données de mesures. Nous allons pouvoir maintenant travailler à son exploitation.

Tracer l'histogramme de la série de mesures

Les principes de base

Nous voulons vérifier que nos mesures sont bien distribuées selon une gaussienne, dont nous voulons déterminer les paramètres, moyenne et écart-type. C'est à dire, pratiquement, vérifier que les erreurs commises sont bien aléatoires et qu'il n'y a pas d'erreurs systématiques.

Nous allons construire la distribution de nos mesures à l'aide d'un histogramme. Après avoir calculer la moyenne et l'écart-type (du moins un estimateur de ces grandeurs) de notre série de mesures, nous tracerons en superposition la gaussienne correspondante. Et nous ferons une comparaison visuelle.

La résolution du CAN de l'Arduino est de 4,88 mV, soit en arrondissant 5 mV. La résolution de notre référence est de 1 mV. Un tri rapide de mon fichier à l'aide d'Excel (d'où l'intérêt du formet csv !) m'apprend que la tension minima mesurée est de 4,144 V et la maxima de 4,183 V, soit une plage de 39 mV, arrondie à 40 mV. Je vais donc créer 8 plages de 5 mV entre 4,144 V et 4,184 V. Choisir une largeur de classe plus petite n'aurait pas de sens physique au regard de la résolution de mon CAN.

Le chargement de la série de mesures

Nos mesures sont stockées dans un fichier plat au format csv. Chaque enregistrement, correspondant à une mesure contient deux informations : le numéro de la mesure, variant de 0 à 999, et la valeur de la tension en volt. Pour traiter ces données, il faut les charger dans une structure Python appropriée. Ici j'ai choisi une liste.

Il existe dans Python un module spécialisé, le module csv, qui va nous faire cela très facilement. Voilà le code de la fonction :

def ChargementListe(NomFic):

liste = []

Fic1 = open(NomFic,'r')

Fic1CSV = csv.reader(Fic1, delimiter = ';')

for row in Fic1CSV:

if row :

mesure = float(row[1])

liste.append(mesure)

Fic1.close()

return liste

Je déclare une liste vide nommée liste. Puis j'ouvre le fichier de mesures dont le nom est passé en paramètre, en lecture seule. Je précise qu'il s'agit d'un fichier csv avec un délimiteur de donnée égale à ";". Puis je lis le fichier ligne par ligne jusqu'à sa fin, en extrayant sur chaque ligne la deuxième colonne qui contient la valeur de la tension. J'ajoute cette valeur à la liste après l'avoir converti en float.

Préparation et tracé de l'histogramme

J'utilise la fonction hist() de Python en déclarant le nombre de classes, le calcul de la largeur des classes se fait automatiquement :

NbClasses = 8

plt.hist(ListeMesures, NbClasses, facecolor='green')

après avoir préparé l'affichage graphique avec les fonctions habituelles. Voici l'histogramme obtenu :

Histogramme distribution mesures

Le programme nous indique que la moyenne de l'échantillon est de 4.160 V, son écart type de 0.008. Le minimum est 4.144 V et le maximum 4.183 V.

Superposer la gaussienne

Je vais maintenant superposer à cet histogramme la courbe gaussienne avec pour paramètres la moyenne et l'écart-type de mon échantillon et je l'affiche sur le même référentiel que l'histogramme. Et voici ce que j'obtiens :

Histogramme distribution mesures avec gaussienne

Interpréter les résultats

Notre résultat de mesure peut donc s'écrire V = 4.160 V +- 0.008 V, avec une probabilité de 0.68 que la valeur vraie soit dans cette intervalle. Comme la référence Vref est égale à Vref = 3.992 +- 0.001 V, nous sommes largement hors des clous.

A la vue de l'histogramme, je pense même ne pas pouvoir faire du calcul d'erreur aléatoire, dans la mesure où il est clair que j'ai un problème d'erreur systématique. En effet, la distribution des mesures n'a rien de gaussien, même de loin. Les mesures se répartissent principalement en deux classes, sans que je sache pourquoi, pour l'instant.

Je referai la manip et je compléterai cette page si je trouve quelque chose.

Les scripts Python

Les scripts Python étudiés dans cette page sont disponibles dans le package TMIncertitudeMesures.zip :

Pour conclure

L'analyse statistique est une discipline importante à cultiver en physique, surtout pour les expérimentateurs. Les numériciens ont d'autres préoccupations, pas si éloignées si l'on se réfère aux erreurs d'arrondi et à leur propagation ! Le calcul d'incertitude ne se limite pas au calcul des différentielles. Il serait peut être temps de passer à autre chose de plus consistant : l'analyse statistique.


Contenu et design par Dominique Lefebvre - www.tangenteX.com octobre 2016   Licence Creative Commons   Contact : PhysiqueX ou

Cette œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 France.