Notebook originel: exam24.ipynb
Examen Science des données: estimateurs robustes de la dispersion🔗
Date: 5 novembre 2024, 10h00-12h00
Durée: 2 h
L’objectif général de l’exercice est de mettre en place des estimateurs robustes de la dispersion d’un échantillon, c.à.d des estimateurs moins sensibles à la présence éventuelle de points abérrants (i.e. des points ne respectant pas la distribution intrinsèque de la population) dans l’échantillon.
Consignes🔗
Créez un répertoire Exam24_NOM_Prenom/ dans lequel vous travaillerez, et où seront stockés
le notebook jupyter d’analyse (
nom_prenom.ipynb, copié à partir de ce notebook énoncé sans l’extension.orig), que vous complèterez progressivement en ajoutant une ou plusieurs cellules en dessous de chaque question;le package python en développement, selon les instructions ci-dessous.
Vous devez alors mettre ce répertoire sous contrôle git sur un projet dédié sur le serveur https://gitlab.in2p3.fr, et en transmettre l’adresse à y.copin@ipnl.in2p3.fr.
Toutefois, si, par manque de temps ou de connaissance, vous ne pouvez pas créer un dépôt git, envoyez par mail le notebook et/ou le package (sous forme d’un fichier tar ou zip) à y.copin@ipnl.in2p3.fr (cette solution sera pénalisée dans la notation).
Tests🔗
Ce notebook inclut des tests unitaires vous permettant de tester a minima le code du notebook. Utilisez ces tests pour guider votre développement et vérifier vos résultats. Ne modifiez pas (a fortiori, n’effacez pas) ces tests.
Si vous n’avez pas réussi à stocker votre package sous ``git``, envoyer votre notebook à y.copin@ipnl.in2p3.fr.
[1]:
%matplotlib inline
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
[2]:
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats
[3]:
np.set_printoptions(precision=3, suppress=True);
Détermination robuste de la dispersion d’un échantillon🔗
Considérons un échantillon monovarié \(x = \{x_i\}_{i=1\ldots N}\). Les statistiques suivantes sont des estimateurs de la dispersion de l’échantillon, renormalisés pour fournir l’écart type dans le cas d’une distribution normale \(\mathcal{N}\).
Écart type🔗
Median Absolute Deviation🔗
où \(\Phi\) est la the fonction de distribution cumulative normale standard (scipy.stats.norm.cdf), et son inverse \(\Phi^{-1}\) est la fonction quantile normale (scipy.stats.norm.ppf).
Écart Inter-Quartile🔗
Il s’agit de la plage tronquée à 25%, c’est-à-dire la différence entre le 75e et le 25e percentile d’un échantillon.
Les estimateurs MAD et IQR ont tous deux un point de rupture optimal de 0,5 : jusqu’à la moitié des points de l’échantillon peuvent être arbitrairement grands avant que l’estimateur donne une estimation arbitrairement grande. (En comparaison, l’écart-type de l’échantillon a un point de rupture de 0.) Malheureusement, ces estimateurs sont également moins efficaces statistiquement que l’écart type traditionnel.
Par ailleurs, ces estimateurs sont légèrement biaisés (voir Croux & Rousseeuw, 1992), notamment pour les petits échantillons, mais nous n’aborderons pas ce point dans cet examen.
Différences absolues par pairs🔗
D’autres estimateurs de dispersion avec un point de rupture maximal sont présentés dans Croux and Rousseeuw (1992):
Nous les implémenterons dans une version simplifiée.
Échantillons de test (avec et sans outliers)🔗
[4]:
m, n = 10, 8
rng = np.random.default_rng(0)
# Échantillon sans outliers
arr_no = rng.normal(size=m * n)
# Échantillon avec outliers
arr_wo = arr_no.copy()
for i in range(0, m*n, n+1):
arr_wo[i] += (-1)**i * 6
Questions🔗
Tracer un histogramme normalisé de ces deux échantillons (
ax.hist(density=True)), ainsi que la distribution normale \(\mathcal{N}(\mu=0, \sigma=1)\) (scipy.stats.norm.pdf).
Écrire une fonction
sigma_std_1D(arr)qui retourne l’écart type de l’échantillon (np.std(ddof=1)).Pour cette fonction et les suivantes, s’assurer (
assert) que le tableau d’entrée est unidimensionnel.
Écrire les fonctions
mad_1D(arr)etsigma_mad_1D(arr)qui retourne le MAD normalisé de l’échantillon (utilisernp.medianetscipy.stats.norm.ppf).
Écrire les fonctions
iqr_1D(arr)etsigma_iqr_1D(arr)qui retourne l’écart inter-quartile normalisé de l’échantillon (utilisernp.quantile).
Pour l’estimateur \(Sn\), il est d’abord nécessaire de déterminer la normalisation \(c\), qui résout cette équation:
\[\Phi\left(\Phi^{-1}(3/4) + 1/c\right) - \Phi\left(\Phi^{-1}(3/4) - 1/c\right) - 1/2 = 0\]Définir la fonction
objfn(c)dont on cherche le zéro.Tracer cette fonction sur l’intervale \([1/2, 2]\).
Écrire une fonction
Sn_norm()qui retourne la solution de l’équation précédente.
Implémentation (naïve et approximative) de l’estimateur \(Sn\)
Écrire une fonction
all_pairs_absdiff(arr)qui retourne le tableau \(N\times N\) des différences absolues \(\left[\,|x_i - x_j|\,\right]_{i,j = 1\ldots N}\) (utiliser une double liste en compréhension).Écrire la fonction
sigma_Sn_1D(arr)qui retourne l’estimateur \(Sn\) normalisé de l’échantillon (utiliser \(c = 1.19259855\) si vous n’avez pas sû répondre à la question 5.C).
Implémentation (naïve et approximative) de l’estimateur \(Qn\).
En utilisant la fonction
upper_pairs_absdiff(arr)ci-dessous, écrire la fonctionsigma_Qn_1D(arr)qui retourne l’estimateur \(Qn\) normalisé de l’échantillon (utilisernp.quantile).
[5]:
def upper_pairs_absdiff(arr):
n, = np.shape(arr)
idx = np.triu_indices(n, k=1)
return np.abs(arr[idx[0]] - arr[idx[1]])
Écrire les fonctions
sigma_mad(arr, axis=None)etsigma_iqr(arr, axis=None)généralisant les fonctions unidimensionnelles précédentes aux tableaux de rang arbitraire, et en calculant les statistiques le long des axes spécifiés (Nonepour l’ensemble du tableau).
Packaging🔗
Extraire toutes les fonctions précédentes et les inclure dans un fichier
scale.py. Il constituera le module de votre package.Documenter a minima le module et les fonctions par des docstrings (vous pouvez vous aider de l’énoncé).
Créer un package
Exam24_Nom_Prenomconstitué du seul modulescale.py, avec l’arborescence suivante:Exam24_Nom_Prenom/ ├── scale_nom_prenom │ ├── scale.py │ └── __init__.py ├── LICENSE ├── README ├── setup.cfg └── setup.py
Ajouter un répertoire
doc/pour configurer et générer la documentation Sphinx du package (voir documentation pyyc). Y inclure votre notebook (sousdoc/notebooks/) dans une section dédiée.Ajouter un répertoire
test/pour inclure les tests unitaires de ce notebook (voir documentation pyyc).Commit/pushtoutes vos modifications sur votre répo centralgit, et envoyer l’adresse de ce répo à y.copin@ipnl.in2p3.fr.Si vous n’avez pas réussi à stocker votre package sous git, générer une distribution des sources de votre package avec la commande
$ python setup.py sdist
et envoyer l’archive correspondante (générée sous
dist/) à y.copin@ipnl.in2p3.fr. Si vous n’avez pas non plus réussi à configurer votre package, générer manuellement une archive de votre package:$ tar cvzf Exam23_NOM_Prenom.tgz Exam24_NOM_Prenom/
et envoyer le tarball.
Tests (ne pas modifier)🔗
[7]:
import unittest
class TestNotebook(unittest.TestCase):
def setUp(self):
m, n = self.m, self.n = 10, 8
rng = np.random.default_rng(0)
self.arr_no = rng.normal(size=m * n) # Échantillon sans outliers
self.arr_wo = self.arr_no.copy() # Échantillon avec outliers
for i in range(0, m*n, n+1):
self.arr_wo[i] += (-1)**i * 6
self.arr2D = self.arr_no.reshape((m, n))
self.places = 6
def test_01_std_shape(self):
with self.assertRaises(AssertionError):
sigma_std_1D(self.arr2D) # Invalid shape
def test_02_std(self):
self.assertAlmostEqual(sigma_std_1D(self.arr_no), 0.9697001436773827, places=self.places)
self.assertAlmostEqual(sigma_std_1D(self.arr_wo), 2.326360764433049, places=self.places)
def test_03_mad(self):
self.assertAlmostEqual(mad_1D(self.arr_no), 0.7009412573844651, places=self.places)
self.assertAlmostEqual(mad_1D(self.arr_wo), 0.8302102070751392, places=self.places)
def test_04_sigma_mad(self):
self.assertAlmostEqual(sigma_mad_1D(self.arr_no), 1.0392170632403142, places=self.places)
self.assertAlmostEqual(sigma_mad_1D(self.arr_wo), 1.2308714948355965, places=self.places)
def test_05_iqr(self):
self.assertAlmostEqual(iqr_1D(self.arr_no), 1.4012263172056918, places=self.places)
self.assertAlmostEqual(iqr_1D(self.arr_wo), 1.5901879638726397, places=self.places)
def test_06_sigma_iqr(self):
self.assertAlmostEqual(sigma_iqr_1D(self.arr_no), 1.0387306232587965, places=self.places)
self.assertAlmostEqual(sigma_iqr_1D(self.arr_wo), 1.1788081015392409, places=self.places)
def test_07_objfn(self):
self.assertAlmostEqual(objfn(1), 0.0805853432046475, places=self.places)
def test_08_Sn_norm(self):
self.assertAlmostEqual(Sn_norm(), 1.1925985531232088, places=12)
def test_09_all_pairs(self):
np.testing.assert_array_equal(all_pairs_absdiff(np.arange(3)), [[0, 1, 2], [1, 0, 1], [2, 1, 0]])
def test_10_Sn(self):
self.assertAlmostEqual(sigma_Sn_1D(self.arr_no), 0.9833810779051267, places=self.places)
self.assertAlmostEqual(sigma_Sn_1D(self.arr_wo), 1.1717986891987089, places=self.places)
# def test_upper_pairs(self):
# np.testing.assert_array_equal(upper_pairs_absdiff(np.arange(3)), [1, 2, 1])
def test_11_Qn(self):
self.assertAlmostEqual(sigma_Qn_1D(self.arr_no), 1.059319495196456, places=self.places)
self.assertAlmostEqual(sigma_Qn_1D(self.arr_wo), 1.2943757905148814, places=self.places)
def test_12_sigma_mad_nd(self):
self.assertAlmostEqual(sigma_mad(self.arr2D), 1.0392170632403142, places=self.places)
np.testing.assert_array_almost_equal(sigma_mad(self.arr2D, axis=0),
[1.073203, 0.858985, 1.459733, 1.263337,
0.395109, 0.719357, 1.715088, 1.262144])
np.testing.assert_array_almost_equal(sigma_mad(self.arr2D, axis=1),
[0.572676, 0.761405, 0.803225, 0.727297, 0.636682,
0.926232, 0.97819 , 1.053814, 0.866482, 1.677964])
def test_13_sigma_iqr_nd(self):
self.assertAlmostEqual(sigma_iqr(self.arr2D), 1.0387306232587965, places=self.places)
np.testing.assert_array_almost_equal(sigma_iqr(self.arr2D, axis=0),
[0.914075, 0.710991, 1.173704, 1.135337,
0.436489, 0.647078, 1.554884, 1.056983])
np.testing.assert_array_almost_equal(sigma_iqr(self.arr2D, axis=1),
[0.497738, 0.540138, 0.698781, 0.677263, 0.547558,
1.002339, 0.835115, 0.952673, 0.643802, 1.361982])
unittest.main(argv=[''], verbosity=2, exit=False);
test_01_std_shape (__main__.TestNotebook) ... ERROR
test_02_std (__main__.TestNotebook) ... ERROR
test_03_mad (__main__.TestNotebook) ... ERROR
test_04_sigma_mad (__main__.TestNotebook) ... ERROR
test_05_iqr (__main__.TestNotebook) ... ERROR
test_06_sigma_iqr (__main__.TestNotebook) ... ERROR
test_07_objfn (__main__.TestNotebook) ... ERROR
test_08_Sn_norm (__main__.TestNotebook) ... ERROR
test_09_all_pairs (__main__.TestNotebook) ... ERROR
test_10_Sn (__main__.TestNotebook) ... ERROR
test_11_Qn (__main__.TestNotebook) ... ERROR
test_12_sigma_mad_nd (__main__.TestNotebook) ... ERROR
test_13_sigma_iqr_nd (__main__.TestNotebook) ... ERROR
======================================================================
ERROR: test_01_std_shape (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 20, in test_01_std_shape
sigma_std_1D(self.arr2D) # Invalid shape
NameError: name 'sigma_std_1D' is not defined
======================================================================
ERROR: test_02_std (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 23, in test_02_std
self.assertAlmostEqual(sigma_std_1D(self.arr_no), 0.9697001436773827, places=self.places)
NameError: name 'sigma_std_1D' is not defined
======================================================================
ERROR: test_03_mad (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 27, in test_03_mad
self.assertAlmostEqual(mad_1D(self.arr_no), 0.7009412573844651, places=self.places)
NameError: name 'mad_1D' is not defined
======================================================================
ERROR: test_04_sigma_mad (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 31, in test_04_sigma_mad
self.assertAlmostEqual(sigma_mad_1D(self.arr_no), 1.0392170632403142, places=self.places)
NameError: name 'sigma_mad_1D' is not defined
======================================================================
ERROR: test_05_iqr (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 35, in test_05_iqr
self.assertAlmostEqual(iqr_1D(self.arr_no), 1.4012263172056918, places=self.places)
NameError: name 'iqr_1D' is not defined
======================================================================
ERROR: test_06_sigma_iqr (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 39, in test_06_sigma_iqr
self.assertAlmostEqual(sigma_iqr_1D(self.arr_no), 1.0387306232587965, places=self.places)
NameError: name 'sigma_iqr_1D' is not defined
======================================================================
ERROR: test_07_objfn (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 43, in test_07_objfn
self.assertAlmostEqual(objfn(1), 0.0805853432046475, places=self.places)
NameError: name 'objfn' is not defined
======================================================================
ERROR: test_08_Sn_norm (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 46, in test_08_Sn_norm
self.assertAlmostEqual(Sn_norm(), 1.1925985531232088, places=12)
NameError: name 'Sn_norm' is not defined
======================================================================
ERROR: test_09_all_pairs (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 49, in test_09_all_pairs
np.testing.assert_array_equal(all_pairs_absdiff(np.arange(3)), [[0, 1, 2], [1, 0, 1], [2, 1, 0]])
NameError: name 'all_pairs_absdiff' is not defined. Did you mean: 'upper_pairs_absdiff'?
======================================================================
ERROR: test_10_Sn (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 52, in test_10_Sn
self.assertAlmostEqual(sigma_Sn_1D(self.arr_no), 0.9833810779051267, places=self.places)
NameError: name 'sigma_Sn_1D' is not defined
======================================================================
ERROR: test_11_Qn (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 59, in test_11_Qn
self.assertAlmostEqual(sigma_Qn_1D(self.arr_no), 1.059319495196456, places=self.places)
NameError: name 'sigma_Qn_1D' is not defined
======================================================================
ERROR: test_12_sigma_mad_nd (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 63, in test_12_sigma_mad_nd
self.assertAlmostEqual(sigma_mad(self.arr2D), 1.0392170632403142, places=self.places)
NameError: name 'sigma_mad' is not defined
======================================================================
ERROR: test_13_sigma_iqr_nd (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3190279/3804737905.py", line 72, in test_13_sigma_iqr_nd
self.assertAlmostEqual(sigma_iqr(self.arr2D), 1.0387306232587965, places=self.places)
NameError: name 'sigma_iqr' is not defined
----------------------------------------------------------------------
Ran 13 tests in 0.065s
FAILED (errors=13)
[ ]:
Cette page a été générée à partir de
exam24.ipynb.