polyband

Outlier-resistant polynomial regression toolkit: the mean relation and its scatter envelope · Python package Boîte à outils de régression polynomiale résistante aux valeurs aberrantes : la relation moyenne et son enveloppe de dispersion · module Python

Statistics toolkitBoîte à outils statistique

polyband

The problemLe problème

You have a cloud of points and you want two things from it: where the middle of the cloud goes, and how thick the cloud is. The second question is usually the interesting one, because it is what lets you say whether a particular value is unusual. The usual approaches each fall short.

  • Binning in x gives a jumpy answer that depends on the bin width, says nothing between bin centres, and wastes points near the edges.
  • A polynomial fit plus its covariance matrix answers a different question altogether. The covariance describes how well the fitted curve is pinned down, which improves as you collect more data. The thickness of the cloud does not improve; it is a property of the population.
  • A polynomial fit plus a single global RMS works only if the scatter is the same everywhere, which is usually the first assumption to fail.

polyband fits a trend and a width at the same time, each as its own polynomial, by maximum likelihood. This page explains the model, shows what each option does, and gives runnable examples. Everything shown here is generated from synthetic data that ships with the package, so every figure can be reproduced with one command and every fitted band can be compared against the width it was actually drawn from.

Vous avez un nuage de points et vous voulez en tirer deux choses : où passe le milieu du nuage, et quelle est son épaisseur. La deuxième question est généralement la plus intéressante, puisque c'est elle qui permet de dire si une valeur donnée est atypique. Les approches habituelles sont toutes insatisfaisantes.

  • Le binning en x donne une réponse en dents de scie qui dépend de la largeur des intervalles, ne dit rien entre les centres des bins, et gaspille les points près des bords.
  • Un ajustement polynomial et sa matrice de covariance répondent à une tout autre question. La covariance décrit la précision de la courbe ajustée, qui s'améliore à mesure que les données s'accumulent. L'épaisseur du nuage, elle, ne s'améliore pas : c'est une propriété de la population.
  • Un ajustement polynomial et un seul écart-type global ne fonctionnent que si la dispersion est identique partout, ce qui est habituellement la première hypothèse à tomber.

polyband ajuste simultanément une tendance et une largeur, chacune sous forme de polynôme, par maximum de vraisemblance. Cette page explique le modèle, montre l'effet de chaque option et donne des exemples exécutables. Tout ce qui est présenté ici provient de données synthétiques livrées avec le module : chaque figure se reproduit avec une seule commande, et chaque enveloppe ajustée peut être comparée à la largeur dont elle a réellement été tirée.

Trend with 1 and 2 sigma bands over a scatter of points
A quadratic trend with a band whose width grows exponentially with x. The dashed green curves are the width the data were actually generated from; the fit recovers it without having been told anything about it. Points: 450 synthetic measurements. Une tendance quadratique avec une enveloppe dont la largeur croît exponentiellement avec x. Les courbes vertes pointillées sont la largeur ayant réellement servi à générer les données ; l'ajustement la retrouve sans en avoir reçu la moindre indication. Points : 450 mesures synthétiques.

InstallInstallation

One command, straight from GitHub. Python 3.9 or newer, with numpy and scipy; matplotlib is only needed for the plotting helpers.

Une seule commande, directement depuis GitHub. Python 3.9 ou plus récent, avec numpy et scipy ; matplotlib n'est requis que pour les fonctions de tracé.

pip install git+https://github.com/eartigau/polyband.git

That installs whatever is on main at the moment you run it. For anything you need to reproduce later, pin a released version instead:

Cette commande installe l'état de main au moment où vous la lancez. Pour tout ce que vous devrez reproduire plus tard, épinglez plutôt une version publiée :

pip install "git+https://github.com/eartigau/polyband.git@v0.2.0"

Citing polybandCiter polyband

If polyband contributes to work you publish, please cite it. GitHub's Cite this repository button reads the CITATION.cff in the repository and hands you BibTeX or APA.

Si polyband contribue à un travail que vous publiez, merci de le citer. Le bouton Cite this repository de GitHub lit le fichier CITATION.cff du dépôt et vous donne le BibTeX ou l'APA.

Two DOIs exist and they are not interchangeable:

Il existe deux DOI, et ils ne sont pas interchangeables :

Quick startDémarrage rapide

order_mean is the degree of the trend, order_width the degree of ln(sigma). They are independent, which is the whole point: a curved trend with a constant-width band is an ordinary combination, and so is a straight trend whose scatter grows.

order_mean est le degré de la tendance, order_width celui de ln(sigma). Ils sont indépendants, et c'est là tout l'intérêt : une tendance courbe avec une enveloppe de largeur constante est une combinaison parfaitement ordinaire, tout comme une tendance droite dont la dispersion augmente.

Copy the whole block and run it: it builds its own data, so it works before you have plugged in yours. Every number printed below is what you should actually see.

Copiez le bloc entier et exécutez-le : il fabrique ses propres données, donc il fonctionne avant même que vous n'y branchiez les vôtres. Chaque valeur indiquée ci-dessous est bien celle que vous devriez obtenir.

import numpy as np
import matplotlib.pyplot as plt
from polyband import fit_polyband, plot_polyband

# ---------------------------------------------------------------------
# 1. The data. Replace this whole block with your own x and y.
# ---------------------------------------------------------------------
rng = np.random.default_rng(0)

# 400 points spread over the x range you care about.
x = rng.uniform(0, 10, 400)

# The curve the points scatter around: a parabola that bends back down.
trend = 3.0 + 1.1 * x - 0.075 * x**2

# How wide that scatter is. The point of the package is that this is NOT
# a constant: it grows from 0.30 at x = 0 to 4.95 at x = 10. A single
# global RMS would be wrong at both ends at once.
sigma = np.exp(-1.2 + 0.28 * x)

# One draw per point, each from its own local sigma.
y = trend + rng.normal(0.0, sigma)

# ---------------------------------------------------------------------
# 2. The fit. Two degrees, chosen independently of each other.
# ---------------------------------------------------------------------
#   order_mean=2  : the trend is a parabola, so degree 2.
#   order_width=1 : sigma is an exponential of x, so ln(sigma) is a
#                   straight line, so degree 1.
# Both polynomials are fitted at the same time by maximum likelihood,
# which is what lets the trend be weighted by the local scatter.
fit = fit_polyband(x, y, order_mean=2, order_width=1)

# ---------------------------------------------------------------------
# 3. Reading the result.
# ---------------------------------------------------------------------
print(fit.summary())          # degrees, N, x range, log L, AIC and BIC

print(fit.predict(5.0))       # the trend at x = 5              -> 6.610
print(fit.scatter(5.0))       # half-width of the 1-sigma band  -> 1.234
print(fit.envelope(5.0))      # the two band edges at x = 5     -> (5.376, 7.844)
print(fit.zscore(5.0, 12.3))  # is y = 12.3 unusual at x = 5?   -> 4.61 sigma

# ---------------------------------------------------------------------
# 4. Check it before you trust it.
# ---------------------------------------------------------------------
# About 68% of the points belong inside the 1-sigma band and 95% inside
# 2 sigma. A band nobody has checked is decoration, not a measurement.
for nsigma, observed, expected in fit.coverage(x, y):
    print(f"{nsigma:.0f} sigma: {observed:.1%} observed vs {expected:.1%} expected")
# 1 sigma: 66.0% observed vs 68.3% expected
# 2 sigma: 96.2% observed vs 95.4% expected
# 3 sigma: 99.8% observed vs 99.7% expected

# ---------------------------------------------------------------------
# 5. Draw it.
# ---------------------------------------------------------------------
# nsigma=(1, 2) nests two bands. plot_polyband draws on the axis you give
# it, never calls show() or legend() itself, and hands back every artist
# it created so you can restyle afterwards.
art = plot_polyband(fit, x, y, nsigma=(1, 2))
art.ax.legend(handles=art.legend_handles)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
from polyband import fit_polyband, plot_polyband

# ---------------------------------------------------------------------
# 1. Les données. Remplacez tout ce bloc par vos propres x et y.
# ---------------------------------------------------------------------
rng = np.random.default_rng(0)

# 400 points répartis sur l'intervalle en x qui vous intéresse.
x = rng.uniform(0, 10, 400)

# La courbe autour de laquelle les points se dispersent : une parabole
# qui finit par redescendre.
trend = 3.0 + 1.1 * x - 0.075 * x**2

# L'ampleur de cette dispersion. Tout l'intérêt du module est qu'elle
# n'est PAS constante : elle passe de 0,30 en x = 0 à 4,95 en x = 10.
# Un unique écart-type global se tromperait aux deux bouts à la fois.
sigma = np.exp(-1.2 + 0.28 * x)

# Un tirage par point, chacun selon son sigma local.
y = trend + rng.normal(0.0, sigma)

# ---------------------------------------------------------------------
# 2. L'ajustement. Deux degrés, choisis indépendamment l'un de l'autre.
# ---------------------------------------------------------------------
#   order_mean=2  : la tendance est une parabole, donc degré 2.
#   order_width=1 : sigma est une exponentielle de x, donc ln(sigma) est
#                   une droite, donc degré 1.
# Les deux polynômes sont ajustés simultanément par maximum de
# vraisemblance, ce qui permet de pondérer la tendance par la
# dispersion locale.
fit = fit_polyband(x, y, order_mean=2, order_width=1)

# ---------------------------------------------------------------------
# 3. Lire le résultat.
# ---------------------------------------------------------------------
print(fit.summary())          # degrés, N, intervalle en x, log L, AIC et BIC

print(fit.predict(5.0))       # la tendance en x = 5              -> 6,610
print(fit.scatter(5.0))       # demi-largeur de l'enveloppe à 1s  -> 1,234
print(fit.envelope(5.0))      # les deux bords en x = 5           -> (5,376 ; 7,844)
print(fit.zscore(5.0, 12.3))  # y = 12,3 est-il atypique en x = 5 ? -> 4,61 sigma

# ---------------------------------------------------------------------
# 4. Vérifiez avant de faire confiance.
# ---------------------------------------------------------------------
# Environ 68 % des points doivent tomber dans l'enveloppe à 1 sigma, et
# 95 % dans celle à 2 sigma. Une enveloppe que personne n'a vérifiée est
# une décoration, pas une mesure.
for nsigma, observed, expected in fit.coverage(x, y):
    print(f"{nsigma:.0f} sigma: {observed:.1%} observé vs {expected:.1%} attendu")
# 1 sigma : 66,0 % observé vs 68,3 % attendu
# 2 sigma : 96,2 % observé vs 95,4 % attendu
# 3 sigma : 99,8 % observé vs 99,7 % attendu

# ---------------------------------------------------------------------
# 5. Tracer.
# ---------------------------------------------------------------------
# nsigma=(1, 2) imbrique deux enveloppes. plot_polyband dessine sur l'axe
# que vous lui donnez, n'appelle jamais show() ni legend() de lui-même, et
# renvoie tous les objets graphiques créés pour que vous puissiez les
# restyler ensuite.
art = plot_polyband(fit, x, y, nsigma=(1, 2))
art.ax.legend(handles=art.legend_handles)
plt.show()

The key distinctionLa distinction essentielle

The band is not the uncertainty on the trendL'enveloppe n'est pas l'incertitude sur la tendance

These are two different quantities, not to be confused. The uncertainty on the trend comes from the coefficient covariance matrix and shrinks like 1/√N. The band describes where the points are, and does not shrink at all: with more data it simply converges on the true population width.

Ce sont deux quantités distinctes, à ne pas confondre. L'incertitude sur la tendance provient de la matrice de covariance des coefficients et décroît en 1/√N. L'enveloppe décrit où se trouvent les points, et ne rétrécit pas du tout : avec davantage de données, elle converge simplement vers la largeur réelle de la population.

Left: both bands on one sample. Right: their behaviour with sample size.
Left: both bands drawn on the same 400 points. The orange strip is the uncertainty on the curve, the blue band is the spread of the points. Right: the same two quantities measured at x = 5 for samples from 30 to 20 000 points. The band sits on the true width across three decades; the trend error follows 1/√N exactly. À gauche : les deux enveloppes tracées sur les mêmes 400 points. La bande orange est l'incertitude sur la courbe, la bande bleue la dispersion des points. À droite : ces deux quantités mesurées à x = 5 pour des échantillons de 30 à 20 000 points. L'enveloppe reste sur la largeur vraie à travers trois décades ; l'erreur sur la tendance suit exactement 1/√N.
DescribesDécrit As N growsQuand N augmente AnswersRépond à
fit.envelope(x) where the points lieoù se trouvent les points converges to the true spreadconverge vers la dispersion vraie is this value unusual?cette valeur est-elle atypique ?
fit.trend_band(x) where the curve liesoù se trouve la courbe shrinks like 1/√Ndécroît en 1/√N how well do I know the mean relation?quelle est la précision de la relation moyenne ?

The modelLe modèle

y = Pmean(x) + noise, noise ~ N(0, s(x))
ln s(x) = Pwidth(x)

Both polynomials are fitted at once by maximum likelihood, not in two passes. That matters: the trend is then weighted by the local scatter, so points in the noisy part of the x range pull less on the mean than points in the quiet part.

Three design choices are worth knowing about.

  • The width polynomial describes ln(sigma), not sigma or sigma squared. Fitting the squared residuals with an ordinary polynomial lets the variance go negative, which then has to be clipped, and it weights outliers by their fourth power. Working in ln(sigma) makes positivity automatic.
  • x is rescaled onto [-1, 1] internally. A Vandermonde matrix built from raw values in the thousands is hopelessly ill-conditioned by degree 3.
  • Maximum-likelihood widths are biased low by roughly √((N-p)/N), so the fit corrects for it by default. This is the same correction as dividing by N-1 rather than N in a plain standard deviation.[4]

Les deux polynômes sont ajustés simultanément par maximum de vraisemblance, et non en deux passes. Cela compte : la tendance est alors pondérée par la dispersion locale, de sorte que les points situés dans la portion bruitée de l'axe des x influencent moins la moyenne que ceux de la portion calme.

Trois choix de conception méritent d'être connus.

  • Le polynôme de largeur décrit ln(sigma), et non sigma ou sigma au carré. Ajuster les résidus au carré avec un polynôme ordinaire laisse la variance devenir négative, ce qu'il faut ensuite tronquer, et donne aux points aberrants un poids en puissance quatrième. Travailler en ln(sigma) rend la positivité automatique.
  • x est ramené sur [-1, 1] en interne. Une matrice de Vandermonde construite à partir de valeurs brutes de l'ordre du millier est irrémédiablement mal conditionnée dès le degré 3.
  • Les largeurs par maximum de vraisemblance sont biaisées vers le bas d'environ √((N-p)/N) ; la correction est appliquée par défaut. C'est la même correction que diviser par N-1 plutôt que par N dans un écart-type ordinaire.[4]

Does it work?Est-ce que ça marche ?

Coverage of the 450-point fit shown at the top of this page, against what a correctly specified band should give:

Couverture de l'ajustement à 450 points montré en haut de cette page, comparée à ce que devrait donner une enveloppe correctement spécifiée :

Inside 1σDans 1σ
69.3 %
ExpectedAttendu
68.3 %
Inside 2σDans 2σ
96.0 %
ExpectedAttendu
95.4 %
Inside 3σDans 3σ
99.8 %
ExpectedAttendu
99.7 %

Two lines to check it on your own data:

Deux lignes pour le vérifier sur vos propres données :

# coverage() returns one tuple per level: the level itself, the fraction of
# your points that actually fell inside, and the fraction a correctly
# specified Gaussian band would contain. Watch the three lines together:
# systematic over-coverage at 1 sigma with the 2 and 3 sigma lines on target
# is the signature of heavier-than-Gaussian tails, which is what nu is for.
for nsigma, observed, expected in fit.coverage(x, y):
    print(f"{nsigma:.0f} sigma: {observed:.1%} observed, {expected:.1%} expected")
# coverage() renvoie un tuple par niveau : le niveau lui-même, la fraction de
# vos points réellement tombés à l'intérieur, et la fraction que contiendrait
# une enveloppe gaussienne correctement spécifiée. Lisez les trois lignes
# ensemble : une sur-couverture systématique à 1 sigma alors que 2 et 3 sigma
# sont justes signale des queues plus lourdes qu'une gaussienne, ce à quoi
# sert nu.
for nsigma, observed, expected in fit.coverage(x, y):
    print(f"{nsigma:.0f} sigma: {observed:.1%} observé, {expected:.1%} attendu")

Under the hoodSous le capot

The objective, and the two terms that fight over itLa fonction objectif, et les deux termes qui s'y opposent

There is a single objective function, and it is minimised in one shot over all p + q + 2 coefficients at once. Everything the package does follows from its shape, so it is worth writing out in full.

Il n'y a qu'une seule fonction objectif, minimisée d'un seul coup sur l'ensemble des p + q + 2 coefficients. Tout ce que fait le module découle de sa forme, ce qui justifie de l'écrire au complet.

objectivefonction objectif
−ln L(a, b)  =  Σi [ ½ zi2 + ln σ(xi) ]

zi = ( yi − μ(xi) ) / σ(xi)
μ(x) = a0 + a1t + … + aptp
ln σ(x) = b0 + b1t + … + bqtq
t = (x − x0) / Δ  ∈  [−1, 1]

with yerr: σ2(xi) is replaced by σ2(xi) + si2avec yerr : σ2(xi) est remplacé par σ2(xi) + si2

Two terms, pulling in opposite directions. The first, ½z², rewards a wide band: make σ large enough and every residual looks small. The second, ln σ, rewards a narrow one, and does so without limit as σ goes to zero. Neither can run away with it. For a single point with residual r, the sum of the two is minimised at exactly σ = |r|. That is the whole mechanism: the band is held in place from both sides at once.

Deux termes, qui tirent en sens inverse. Le premier, ½z², récompense une enveloppe large : prenez σ assez grand et tous les résidus paraissent petits. Le second, ln σ, récompense une enveloppe étroite, et sans borne lorsque σ tend vers zéro. Aucun des deux ne peut l'emporter. Pour un point isolé de résidu r, leur somme est minimale exactement en σ = |r|. C'est là tout le mécanisme : l'enveloppe est tenue des deux côtés à la fois.

Why an underestimate here is not paid for by an overestimate therePourquoi une sous-estimation ici n'est pas compensée par une surestimation ailleurs

This is the natural worry. The objective is one number, summed over the whole sample, so could a band that is too narrow at low x and too wide at high x score just as well as the correct one? It cannot, and there are two separate reasons why.

1. The cost is a sum of per-point terms, none of which can be negative.

Suppose the true width at point i is σtrue,i and the model puts σi = ri σtrue,i. Averaged over noise realisations, the amount by which −ln L exceeds the value it would take for the correct band is:

C'est l'inquiétude naturelle. L'objectif est un seul nombre, sommé sur tout l'échantillon : une enveloppe trop étroite aux petits x et trop large aux grands x pourrait-elle obtenir le même score que la bonne ? Non, et pour deux raisons distinctes.

1. Le coût est une somme de termes point par point, dont aucun ne peut être négatif.

Supposons que la largeur vraie au point i soit σvrai,i et que le modèle pose σi = ri σvrai,i. En moyenne sur les réalisations du bruit, l'excès de −ln L par rapport à la valeur qu'atteindrait l'enveloppe correcte vaut :

expected excessexcès attendu
E[ Δ(−ln L) ]  =  Σi [ ½ ( 1/ri2 − 1 ) + ln ri ]  ≥  0

each term is a Kullback-Leibler divergence: zero at r = 1, positive everywhere elsechaque terme est une divergence de Kullback-Leibler : nulle en r = 1, positive partout ailleurs
r = 0.80 → +0.058 nats    r = 1.25 → +0.043 nats    both are debitsr = 0,80 → +0,058 nat    r = 1,25 → +0,043 nat    deux débits

Every term vanishes at r = 1 and is strictly positive otherwise. A width 20% too small costs 0.058 nats at that point; a width 25% too large costs 0.043. Both are charges against the fit. There is no credit anywhere in the sum to be spent at the other end of the x range, so errors of opposite sign add up instead of cancelling.

2. The stationarity conditions pin down each moment separately.

Differentiating the objective with respect to the coefficients and setting the result to zero gives two families of equations, one per polynomial:

Chaque terme s'annule en r = 1 et est strictement positif ailleurs. Une largeur 20 % trop petite coûte 0,058 nat en ce point ; une largeur 25 % trop grande en coûte 0,043. Ce sont deux débits. Il n'existe nulle part dans la somme de crédit à dépenser à l'autre bout de l'intervalle en x : les erreurs de signes opposés s'additionnent au lieu de s'annuler.

2. Les conditions de stationnarité fixent chaque moment séparément.

En dérivant l'objectif par rapport aux coefficients et en annulant le résultat, on obtient deux familles d'équations, une par polynôme :

trendtendance k = 0 … p
Σi tik ( yi − μi ) / σi2  =  0

widthlargeur k = 0 … q
Σi tik zi2  =  Σi tik

Read the second family out loud. For k = 0 it says the standardised residuals have mean square exactly 1: the band is right on average. For k = 1 it says the same holds after weighting by t, that is, no tilt is left in z². For k = 2, no curvature is left. And "too narrow on the left, too wide on the right" is precisely a non-zero k = 1 moment, which the k = 1 equation sets to zero. Each degree given to the width polynomial buys one more moment of z² that the fit is forced to get right. On the 450-point fit at the top of this page these conditions hold to one part in 108.

The first family is the trend, and it is the familiar normal equations with one difference: each residual carries a weight 1/σi². Points in the noisy part of the x range pull less on the trend than points in the quiet part, automatically, which is the concrete reason for fitting both polynomials together rather than in two passes. Over 200 synthetic samples of 300 points each, the joint trend at x = 5 is 1.6 times more precise than an unweighted least-squares trend on the same data (0.089 against 0.142 in scatter).

Lisez la seconde famille à voix haute. Pour k = 0, elle dit que les résidus standardisés ont une moyenne quadratique exactement égale à 1 : l'enveloppe est juste en moyenne. Pour k = 1, elle dit que c'est encore vrai après pondération par t, autrement dit qu'il ne subsiste aucune inclinaison dans z². Pour k = 2, aucune courbure. Or « trop étroite à gauche, trop large à droite » est exactement un moment k = 1 non nul, que l'équation k = 1 annule. Chaque degré accordé au polynôme de largeur achète un moment de z² de plus que l'ajustement est contraint de reproduire. Sur l'ajustement à 450 points montré en haut de cette page, ces conditions sont vérifiées à une partie sur 108 près.

La première famille est celle de la tendance : ce sont les équations normales habituelles, à une différence près, chaque résidu portant un poids 1/σi². Les points situés dans la portion bruitée de l'axe des x influencent moins la tendance que ceux de la portion calme, automatiquement, et c'est la raison concrète d'ajuster les deux polynômes ensemble plutôt qu'en deux passes. Sur 200 échantillons synthétiques de 300 points chacun, la tendance conjointe en x = 5 est 1,6 fois plus précise qu'une tendance par moindres carrés non pondérés sur les mêmes données (0,089 contre 0,142 de dispersion).

Three panels: the per-point cost of a wrong width, a compensating tilt, and its price
Left: the expected cost of one point when the band is a factor r too wide, split into its two competing terms. Their sum has one minimum, at the true width. Middle: what a compensating error looks like, a band tilted so that it is 18% too narrow at one end and 22% too wide at the other while its average width stays right; the blue curve is what polyband actually returns on 5000 points. Right: the price of that tilt, measured by forcing it on the fit and re-optimising every other parameter to buy it back. The curve has a single minimum at zero. With 450 points a tilt of 0.2 already costs 6.5 in −ln L, a likelihood ratio of about 700; with 1800 points it costs 24, a factor of 3×1010. À gauche : le coût attendu d'un point lorsque l'enveloppe est d'un facteur r trop large, décomposé en ses deux termes concurrents. Leur somme n'a qu'un minimum, à la largeur vraie. Au centre : à quoi ressemblerait une erreur compensatoire, une enveloppe inclinée de façon à être 18 % trop étroite d'un côté et 22 % trop large de l'autre, sa largeur moyenne restant juste ; la courbe bleue est ce que polyband renvoie réellement sur 5000 points. À droite : le prix de cette inclinaison, mesuré en l'imposant à l'ajustement puis en réoptimisant tous les autres paramètres pour tenter de la racheter. La courbe n'a qu'un seul minimum, en zéro. Avec 450 points, une inclinaison de 0,2 coûte déjà 6,5 en −ln L, soit un rapport de vraisemblance d'environ 700 ; avec 1800 points elle coûte 24, soit un facteur 3×1010.

Where compensation is genuinely possibleOù la compensation est réellement possible

Everything above holds inside the family of models you chose. It says nothing about a width the family cannot represent. If ln σ is genuinely quadratic in x and you fit order_width=1, the fit will trade one region against another, because it has no other option: it satisfies the moments up to k = 1 and lets the rest fall where it must.

The good news is that this failure is loud. Fit 5000 trumpet points, whose true width grows by a factor of 16 across the range, with order_width=0, and the k = 0 equation still holds exactly: mean z² = 1.000000, so the band is still right on average. It is nonetheless 6.9 times too wide at x = 0 and 2.4 times too narrow at x = 10, and the 1σ band swallows 79.1% of the points instead of 68.3%. Add the one missing degree and the width stays within 1.4% of the truth across the whole range, with coverage at 68.1%.

This is exactly why the BIC scan and the residual plots are not optional decoration. The likelihood polices the model you handed it; only you can police the choice of model. It is the same funnel shown further down this page.

Tout ce qui précède vaut à l'intérieur de la famille de modèles que vous avez choisie. Cela ne dit rien d'une largeur que cette famille ne sait pas représenter. Si ln σ est réellement quadratique en x et que vous ajustez order_width=1, l'ajustement arbitrera bien entre les régions, faute d'autre possibilité : il satisfait les moments jusqu'à k = 1 et laisse le reste retomber où il peut.

La bonne nouvelle, c'est que ce défaut est bruyant. Ajustez 5000 points en trompette, dont la largeur vraie croît d'un facteur 16 sur l'intervalle, avec order_width=0 : l'équation k = 0 reste exactement vérifiée, moyenne de z² = 1,000000, donc l'enveloppe est toujours juste en moyenne. Elle est pourtant 6,9 fois trop large en x = 0 et 2,4 fois trop étroite en x = 10, et l'enveloppe à 1σ contient 79,1 % des points au lieu de 68,3 %. Ajoutez le degré manquant et la largeur reste à moins de 1,4 % de la vérité sur tout l'intervalle, avec une couverture de 68,1 %.

C'est précisément pour cela que le balayage par BIC et les graphiques de résidus ne sont pas décoratifs. La vraisemblance surveille le modèle que vous lui avez donné ; vous seul pouvez surveiller le choix du modèle. C'est le même entonnoir que celui montré plus bas sur cette page.

Outliers change the weights, not the logicLes points aberrants changent les poids, pas la logique

student-t · widthlargeur
Σi tik w(zi) = Σi tik

w(z) = (ν+1) z2 / (ν + z2)

Setting nu replaces the Gaussian z² in the width equations by a saturating weight. Since w(1) = 1 for any nu, an ordinary point counts exactly as it did before, but w cannot exceed ν + 1: with nu=4, a point at 10σ contributes 4.8 rather than 100. The moment conditions keep their form, and a handful of far-flung points can no longer dictate them.

Définir nu remplace le z² gaussien des équations de largeur par un poids qui sature. Comme w(1) = 1 quel que soit nu, un point ordinaire compte exactement comme avant, mais w ne peut dépasser ν + 1 : avec nu=4, un point à 10σ contribue pour 4,8 et non pour 100. Les conditions sur les moments gardent leur forme, et une poignée de points très éloignés ne peut plus les dicter.

Finding the minimum in practiceTrouver le minimum en pratique

What the two degrees controlCe que contrôlent les deux degrés

Each row sweeps one degree while the other is held at the value the data were generated with, so each is a clean one-dimensional experiment. Both rows run past the correct answer on purpose: too few degrees is not the only way to be wrong.

Chaque rangée balaie un degré pendant que l'autre est maintenu à la valeur ayant servi à générer les données : chacune est donc une expérience proprement unidimensionnelle. Les deux rangées dépassent volontairement la bonne réponse, car manquer de degrés n'est pas la seule façon de se tromper.

Two rows of four fits, each sweeping one polynomial degree from too rigid through correct to too flexible
350 points. Green shows the truth: dashed for the trend, dotted for the 1σ width. BIC falls to 574 at the degrees the data were generated with and climbs again on the far side, by 36 for a degree-9 trend and by 34 for a degree-8 width. Look at how little those two panels appear to be wrong. At this sample size the surplus degrees buy a wiggle you would have trouble spotting by eye, which is exactly why the criterion is worth consulting and the picture is not enough. Note also the first panel of the bottom row, where a constant-width band is wrong in both directions at once: too wide on the left, too narrow on the right. 350 points. Le vert indique la vérité : tirets pour la tendance, pointillés pour la largeur à 1σ. Le BIC descend à 574 aux degrés ayant généré les données, puis remonte de l'autre côté, de 36 pour une tendance de degré 9 et de 34 pour une largeur de degré 8. Observez à quel point ces deux panneaux semblent peu fautifs. À cette taille d'échantillon, les degrés excédentaires n'achètent qu'une ondulation difficile à repérer à l'œil, et c'est précisément pourquoi il vaut la peine de consulter le critère : l'image ne suffit pas. Notez aussi le premier panneau de la rangée du bas, où une enveloppe de largeur constante se trompe dans les deux sens à la fois : trop large à gauche, trop étroite à droite.

Choosing the degreesChoisir les degrés

If you have no reason to prefer particular degrees, let BIC[2] scan them. It penalises extra parameters more firmly than AIC[1], which is what you want here: the failure mode to avoid is a width polynomial flexible enough to chase noise.

Si rien ne justifie un choix particulier de degrés, laissez le BIC[2] les balayer. Il pénalise les paramètres supplémentaires plus fermement que l'AIC[1], ce qui est souhaitable ici : le mode de défaillance à éviter est un polynôme de largeur assez souple pour épouser le bruit.

from polyband import select_orders

# Fits every combination from (0, 0) up to the two maxima and scores each one.
# Combinations that fail to fit are skipped rather than raising, so this is
# safe to point at data you have not inspected yet.
fit, table = select_orders(x, y, max_order_mean=4, max_order_width=2)

# `fit` is the winner, already fitted and ready to use. No need to refit.
print(fit.order_mean, fit.order_width)

# `table` is every combination that worked, best first, as
# (order_mean, order_width, criterion). Look at the runners-up rather than
# just the winner: if the top few are within about 2 of each other they are
# a tie, and the simplest of them is the one to keep.
for order_mean, order_width, bic in table[:5]:
    print(f"order_mean={order_mean} order_width={order_width}  BIC={bic:.1f}")

# Pass criterion="aic" to be more permissive, or any fit_polyband argument
# (log_y, nu, yerr) to scan under the same assumptions as your final fit.
fit, table = select_orders(x, y, max_order_mean=4, max_order_width=2, nu=4)
from polyband import select_orders

# Ajuste toutes les combinaisons de (0, 0) jusqu'aux deux maxima et attribue
# un score à chacune. Les combinaisons qui échouent sont ignorées plutôt que
# de lever une exception : on peut donc lancer ceci sur des données non
# encore inspectées.
fit, table = select_orders(x, y, max_order_mean=4, max_order_width=2)

# `fit` est le gagnant, déjà ajusté et prêt à l'emploi. Inutile de réajuster.
print(fit.order_mean, fit.order_width)

# `table` contient toutes les combinaisons ayant fonctionné, meilleure en
# tête, sous la forme (order_mean, order_width, critère). Regardez les
# suivants et pas seulement le gagnant : si les premiers sont à moins de 2
# les uns des autres, c'est une égalité, et c'est le plus simple qu'il faut
# retenir.
for order_mean, order_width, bic in table[:5]:
    print(f"order_mean={order_mean} order_width={order_width}  BIC={bic:.1f}")

# Passez criterion="aic" pour être plus permissif, ou n'importe quel argument
# de fit_polyband (log_y, nu, yerr) pour balayer sous les mêmes hypothèses
# que votre ajustement final.
fit, table = select_orders(x, y, max_order_mean=4, max_order_width=2, nu=4)

Rule of thumb: treat a BIC difference below about 2 as a tie[3] and keep the simpler model. An over-flexible width is not a harmless extra degree of freedom; it produces a band that wiggles to chase noise and then misstates how unusual any given point is.

Règle empirique : considérez un écart de BIC inférieur à environ 2 comme une égalité[3] et conservez le modèle le plus simple. Une largeur trop souple n'est pas un degré de liberté anodin : elle produit une enveloppe qui ondule pour suivre le bruit, et donne ensuite une mauvaise mesure du caractère atypique de chaque point.

Read the residualsLire les résidus

Standardised residuals, (y - trend) / sigma(x), should look like a structureless strip of constant width. Their shape tells you which degree is wrong:

Les résidus standardisés, (y - tendance) / sigma(x), doivent ressembler à une bande sans structure et de largeur constante. Leur forme indique quel degré est en cause :

from polyband import plot_diagnostics

# Draws the four panels shown below and returns the figure, so you can
# restyle or save it. Non-finite residuals are dropped for you.
fig = plot_diagnostics(fit, x, y)
fig.savefig("diagnostics.pdf")

# The same residuals as raw numbers, if you would rather test than look.
z = fit.zscore(x, y)
print(z.std())            # should be close to 1 by construction
print(abs(z).max())       # your most extreme point, in local sigma
from polyband import plot_diagnostics

# Trace les quatre panneaux montrés ci-dessous et renvoie la figure, que vous
# pouvez donc restyler ou enregistrer. Les résidus non finis sont écartés
# automatiquement.
fig = plot_diagnostics(fit, x, y)
fig.savefig("diagnostics.pdf")

# Les mêmes résidus sous forme de nombres, si vous préférez tester plutôt
# que regarder.
z = fit.zscore(x, y)
print(z.std())            # doit être proche de 1 par construction
print(abs(z).max())       # votre point le plus extrême, en sigma local
Standardised residuals for a too-rigid and a correct width polynomial
The funnel, made concrete. Left: the width forced to be constant, on data whose scatter really does grow. The band swallows 80% of the points where it should swallow 68%, and it is far too narrow at high x. Right: one extra degree fixes it. This diagnostic costs nothing and catches the problem immediately. L'entonnoir, concrètement. À gauche : la largeur forcée à rester constante, sur des données dont la dispersion croît réellement. L'enveloppe contient 80 % des points là où elle devrait en contenir 68 %, et elle est bien trop étroite aux grands x. À droite : un degré de plus règle le problème. Ce diagnostic ne coûte rien et détecte immédiatement le défaut.
Four diagnostic panels: residuals, distribution, QQ plot, coverage
The four panels of plot_diagnostics: standardised residuals against x, their distribution against a unit Gaussian, a quantile-quantile plot, and the coverage curve. A well specified band gives a structureless first panel and a coverage curve on the diagonal. Les quatre panneaux de plot_diagnostics : les résidus standardisés en fonction de x, leur distribution comparée à une gaussienne réduite, un diagramme quantile-quantile, et la courbe de couverture. Une enveloppe correctement spécifiée donne un premier panneau sans structure et une courbe de couverture sur la diagonale.

Quantities that span orders of magnitudeQuantités s'étendant sur plusieurs ordres de grandeur

When the scatter is multiplicative rather than additive, y itself is the wrong variable to fit. Pass log_y=True and polyband works on log10(y) internally while handing results back in the units of y, so nothing downstream has to know about the transform.

Quand la dispersion est multiplicative plutôt qu'additive, y n'est pas la bonne variable à ajuster. Passez log_y=True et polyband travaille en interne sur log10(y) tout en renvoyant les résultats dans les unités de y, de sorte que rien en aval n'a besoin de connaître la transformation.

The same data fitted in linear and in log space
The same 400 points, fitted both ways. In linear space the band dips below zero over a third of the x range, which is impossible for a positive quantity, and the residuals are strongly skewed. In log space the band is a tidy strip and the coverage is 69.5% at 1σ against 68.3% expected. Les mêmes 400 points, ajustés des deux façons. En espace linéaire, l'enveloppe passe sous zéro sur un tiers de l'intervalle en x, ce qui est impossible pour une quantité positive, et les résidus sont fortement asymétriques. En espace logarithmique, l'enveloppe est une bande nette et la couverture atteint 69,5 % à 1σ contre 68,3 % attendu.

The one place the transform stays visible is the width: fit.scatter() returns dex, because a band symmetric in log space is asymmetric in linear space and there is no single number to report otherwise. Use fit.envelope() for the edges, which does come back in the units of y.

Le seul endroit où la transformation reste visible est la largeur : fit.scatter() renvoie des dex, car une enveloppe symétrique en espace logarithmique est asymétrique en espace linéaire et aucun nombre unique ne peut la décrire autrement. Utilisez fit.envelope() pour les bords, qui sont bien renvoyés dans les unités de y.

Samples with outliersÉchantillons avec valeurs aberrantes

Under a Gaussian likelihood, a handful of far-flung points drags the band open across the whole x range. Setting nu[5] switches to a Student-t likelihood, which stops letting those few points set the width. Values of 3 to 5 are strongly resistant; larger values approach the Gaussian case.

Sous une vraisemblance gaussienne, une poignée de points très éloignés élargit l'enveloppe sur tout l'intervalle en x. Définir nu[5] bascule vers une vraisemblance de Student, qui empêche ces quelques points de dicter la largeur. Des valeurs de 3 à 5 sont très résistantes ; des valeurs plus grandes tendent vers le cas gaussien.

Gaussian versus Student-t fit on a contaminated sample
600 points, 9% of them drawn from a component six times broader. The true width of the clean component is 0.60. The Gaussian fit returns 1.33, more than twice too wide; with nu = 4 the fit returns 0.64. 600 points, dont 9 % tirés d'une composante six fois plus large. La largeur vraie de la composante propre est 0,60. L'ajustement gaussien renvoie 1,33, soit plus du double ; avec nu = 4, l'ajustement renvoie 0,64.

RobustnessRobustesse

One weight, applied to both polynomials at onceUn seul poids, appliqué simultanément aux deux polynômes

With nu set, polyband is a genuinely robust fit of the trend and of the envelope, in a single self-consistent operation. That is a strong claim, so here is exactly what it means, in terms of the stationarity conditions written further up this page.

Define one number per point:

Avec nu défini, polyband est un ajustement véritablement robuste de la tendance et de l'enveloppe, en une seule opération auto-cohérente. L'affirmation est forte, voici donc exactement ce qu'elle recouvre, en termes des conditions de stationnarité écrites plus haut sur cette page.

Définissons un nombre par point :

the weightle poids
ui = (ν + 1) / (ν + zi2)

trendtendance k = 0 … p
Σi ui tik ( yi − μi ) / σi2  =  0

widthlargeur k = 0 … q
Σi ui tik zi2  =  Σi tik

the Gaussian default is exactly the special case ui = 1le cas gaussien par défaut est exactement le cas particulier ui = 1

The same ui appears in both families. Three things follow, each of which is checked numerically rather than asserted.

  • The trend is exactly a weighted least squares with weight uii². Solving that weighted normal equation directly reproduces the fitted coefficients to 6×10−8. The robustness and the heteroscedastic weighting are the same mechanism, not two stacked corrections.
  • Σi ui = N, exactly. It falls out of the k = 0 width equation: ν Σu + Σu z² = (ν+1)N and Σu z² = N. So this is a redistribution of voting power, not a rejection of points. Weight taken from the tails is handed to the core, where u rises to (ν+1)/ν = 1.25 for nu=4. On an 800-point sample with 12% contamination, the 43 points beyond 5σ hold 0.43% of the total weight while making up 5.4% of the sample. Nothing was deleted; they were outvoted.
  • It is self-consistent in the strict sense. u depends on z; z depends on μ and σ; μ and σ solve their equations under u. There is one fixed point, and it is reached by minimising one function. Compare the usual recipe: fit a trend by least squares, clip on the residuals, take the RMS of the survivors. Those are three different estimators stapled together, and nothing makes the final width consistent with the trend that produced the mask.

C'est le même ui qui apparaît dans les deux familles. Trois conséquences en découlent, chacune vérifiée numériquement plutôt qu'affirmée.

  • La tendance est exactement des moindres carrés pondérés de poids uii². Résoudre directement cette équation normale pondérée redonne les coefficients ajustés à 6×10−8 près. La robustesse et la pondération hétéroscédastique sont un seul et même mécanisme, non deux corrections empilées.
  • Σi ui = N, exactement. Cela découle de l'équation de largeur k = 0 : ν Σu + Σu z² = (ν+1)N et Σu z² = N. Il s'agit donc d'une redistribution du droit de vote, et non d'un rejet de points. Le poids retiré aux queues est remis au cœur de la distribution, où u monte à (ν+1)/ν = 1,25 pour nu=4. Sur un échantillon de 800 points contaminé à 12 %, les 43 points au-delà de 5σ détiennent 0,43 % du poids total tout en représentant 5,4 % de l'échantillon. Rien n'a été supprimé : ils ont été mis en minorité.
  • C'est auto-cohérent au sens strict. u dépend de z ; z dépend de μ et σ ; μ et σ résolvent leurs équations sous le poids u. Il y a un seul point fixe, atteint en minimisant une seule fonction. À comparer à la recette habituelle : ajuster une tendance par moindres carrés, écrêter sur les résidus, prendre l'écart-type des survivants. Ce sont trois estimateurs différents agrafés ensemble, et rien ne garantit que la largeur finale soit cohérente avec la tendance ayant produit le masque.

And it is already a mixture model. A Student-t is a continuous scale mixture of Gaussians: if y | λ ~ N(μ, σ²/λ) with λ ~ Gamma(ν/2, ν/2), then y ~ tν. The posterior of that per-point precision is E[λi | yi] = (ν+1)/(ν+zi²), which is ui exactly. So the weights above are the EM weights of a mixture, with the mixing integrated out analytically. "Student-t or a mixture model" is not really a choice between two things.

Et c'est déjà un modèle de mélange. Une loi de Student est un mélange continu de gaussiennes en échelle : si y | λ ~ N(μ, σ²/λ) avec λ ~ Gamma(ν/2, ν/2), alors y ~ tν. La loi a posteriori de cette précision par point vaut E[λi | yi] = (ν+1)/(ν+zi²), soit exactement ui. Les poids ci-dessus sont donc les poids EM d'un mélange, dont la loi de mélange a été intégrée analytiquement. « Student ou modèle de mélange » n'est pas vraiment un choix entre deux choses.

How deviant they are, and how many there areÀ quel point ils dévient, et combien ils sont

Outliers hurt along two independent axes, and the two behave completely differently. The pull a single point exerts on the trend is ψ(z) = u z = (ν+1)z/(ν+z²). Under a Gaussian likelihood u = 1 and ψ(z) = z grows without bound: a point ten times further out pulls ten times harder, forever. Under Student-t, ψ peaks at z = √ν and then falls back towards zero. With nu=4 it peaks at 1.25 for a point at 2σ and is down to 0.005 for a point at 1000σ, so the wildest point in the sample pulls 250 times less than an ordinary one.

Les valeurs aberrantes nuisent selon deux axes indépendants, qui se comportent de façon complètement différente. L'attraction qu'un point exerce sur la tendance vaut ψ(z) = u z = (ν+1)z/(ν+z²). Sous une vraisemblance gaussienne, u = 1 et ψ(z) = z croît sans borne : un point dix fois plus éloigné tire dix fois plus fort, indéfiniment. Sous une loi de Student, ψ culmine en z = √ν puis redescend vers zéro. Avec nu=4, le maximum vaut 1,25 pour un point à 2σ et tombe à 0,005 pour un point à 1000σ : le point le plus extravagant de l'échantillon tire 250 fois moins qu'un point ordinaire.

Eight panels scanning outlier fraction along the top row and outlier deviance along the bottom row
500 points, true width 0.60 (green dashed), fitted with order_mean=2, order_width=0. Top row: more and more outliers, each six times wider than the core. Bottom row: always 10% of the sample, but each one further out. Along the bottom row the Gaussian band goes from 0.86 to 150.72, a factor of 175, while the Student-t band moves from 0.58 to 0.70 and then stops noticing at all. Along the top row both degrade, because the fraction is the axis neither likelihood can ignore. 500 points, largeur vraie 0,60 (pointillés verts), ajustés avec order_mean=2, order_width=0. Rangée du haut : de plus en plus d'aberrants, chacun six fois plus large que le cœur. Rangée du bas : toujours 10 % de l'échantillon, mais chacun de plus en plus éloigné. Sur la rangée du bas, l'enveloppe gaussienne passe de 0,86 à 150,72, un facteur 175, tandis que l'enveloppe de Student va de 0,58 à 0,70 puis cesse complètement de réagir. Sur la rangée du haut, les deux se dégradent, car la fraction est l'axe qu'aucune des deux vraisemblances ne peut ignorer.

The same two axes, measuredLes deux mêmes axes, mesurés

Three panels: width versus outlier deviance, versus outlier fraction, and the cost on clean data
Median over 25 realisations of 500 points each. Left: at 10% contamination, as the outliers move from 1× to 1000× the true width, the Gaussian answer climbs from 1.00 to 324 and keeps going, while nu=4 saturates at 1.29 and nu=3 at 1.11. Middle: at a fixed 10× deviance, every likelihood degrades steadily with the fraction. Right: what that insurance costs when there is nothing to insure against. Médiane sur 25 réalisations de 500 points chacune. À gauche : à 10 % de contamination, lorsque les aberrants passent de 1× à 1000× la largeur vraie, la réponse gaussienne grimpe de 1,00 à 324 et continue, tandis que nu=4 sature à 1,29 et nu=3 à 1,11. Au centre : à déviance fixée à 10×, toutes les vraisemblances se dégradent régulièrement avec la fraction. À droite : ce que coûte cette assurance quand il n'y a rien à assurer.

The practical rule: budget by fraction, not by amplitude.

Past roughly 10σ the deviance of an outlier stops mattering, so there is no point worrying about how extreme your worst point is. The fraction never stops mattering. At a 10× deviance, nu=4 stays within 11% of the truth up to 10% contamination, is 56% high at 20%, and a factor 3.4 high at 40%.

That degradation is a property of the estimator, not of the optimiser. Restarting the fit from the exact generating parameters at 30% contamination with 100× outliers converges to the same wide answer in 15 trials out of 15, with an identical likelihood. There is no better optimum being missed: the bounded influence is per point, and the total influence of a large enough fraction is not bounded.

La règle pratique : raisonnez en fraction, pas en amplitude.

Au-delà d'environ 10σ, le degré de déviance d'un point cesse de compter : inutile donc de vous inquiéter du caractère extrême de votre pire point. La fraction, elle, ne cesse jamais de compter. À déviance 10×, nu=4 reste à moins de 11 % de la vérité jusqu'à 10 % de contamination, surestime de 56 % à 20 %, et d'un facteur 3,4 à 40 %.

Cette dégradation est une propriété de l'estimateur, non de l'optimiseur. En relançant l'ajustement depuis les paramètres exacts ayant généré les données, à 30 % de contamination avec des aberrants à 100×, on converge vers la même réponse large dans 15 essais sur 15, avec une vraisemblance identique. Aucun meilleur optimum n'est manqué : l'influence bornée l'est point par point, et l'influence totale d'une fraction suffisamment grande, elle, ne l'est pas.

What nu costs, and the calibration trapCe que coûte nu, et le piège de calibration

A Student-t width is the scale parameter of a t distribution[5], not a standard deviation. On perfectly clean Gaussian data the fitted width comes back systematically below the true sd, and envelope(x, 1) is no longer a 68% interval. This is not a bug; it is what the parameter means. It does have to be corrected for if you intend to read the band as a coverage statement.

Une largeur de Student est le paramètre d'échelle d'une loi de Student[5], non un écart-type. Sur des données gaussiennes parfaitement propres, la largeur ajustée revient systématiquement en dessous de l'écart-type vrai, et envelope(x, 1) n'est plus un intervalle à 68 %. Ce n'est pas un défaut : c'est le sens du paramètre. Mais il faut en tenir compte si vous entendez lire l'enveloppe comme un énoncé de couverture.

nu fitted width / true sdlargeur ajustée / écart-type vrai band multiple for 68.3%multiple d'enveloppe pour 68,3 %
2.50.7700,7701.2961,296
30.7960,7961.2531,253
40.8330,8331.1911,191
50.8590,8591.1571,157
70.8930,8931.1141,114
100.9230,9231.0811,081
200.9610,9611.0381,038
none (Gaussian)aucun (gaussien)1.0041,0040.9910,991

Median over 60 clean samples of 600 points. Read the middle column as the deflation to undo if you want a standard deviation, and the right-hand column as the nsigma to ask for if you want a 68.3% interval. Better still, measure it on your own data, which takes one line:

Médiane sur 60 échantillons propres de 600 points. Lisez la colonne du milieu comme la déflation à annuler si vous voulez un écart-type, et celle de droite comme le nsigma à demander si vous voulez un intervalle à 68,3 %. Mieux encore, mesurez-la sur vos propres données, ce qui tient en une ligne :

# With nu set, one fitted width is no longer a 68% interval. Ask coverage()
# what multiple actually is, on your own data rather than from the table.
robust = fit_polyband(x, y, 2, 1, nu=4)

for nsigma, observed, expected in robust.coverage(x, y, nsigma=(1.0, 1.19, 2.0)):
    print(f"{nsigma:.2f} x width: {observed:.1%} inside")

# Then use that multiple everywhere you want a 1-sigma-equivalent band.
lo, hi = robust.envelope(robust.grid(), nsigma=1.19)
# Avec nu défini, une largeur ajustée n'est plus un intervalle à 68 %.
# Demandez à coverage() quel multiple l'est réellement, sur vos propres
# données plutôt que d'après le tableau.
robust = fit_polyband(x, y, 2, 1, nu=4)

for nsigma, observed, expected in robust.coverage(x, y, nsigma=(1.0, 1.19, 2.0)):
    print(f"{nsigma:.2f} x largeur : {observed:.1%} à l'intérieur")

# Utilisez ensuite ce multiple partout où vous voulez une enveloppe
# équivalente à 1 sigma.
lo, hi = robust.envelope(robust.grid(), nsigma=1.19)

Which nuQuel nu

Against sigma clipping and against a fitted mixtureFace à l'écrêtage sigma et à un mélange ajusté

The honest comparison, on identical data: 600 points, a quadratic trend of constant true width 0.60, 200 realisations per column. The entry is the median fitted width divided by the true width, so 1.000 is perfect. Clipping is iterated to a fixed mask; the corrected rows divide by the Gaussian truncation factor; the mixture is a fitted two-component Gaussian with its own free fraction and ratio.

La comparaison honnête, sur des données identiques : 600 points, une tendance quadratique de largeur vraie constante 0,60, 200 réalisations par colonne. La valeur est la largeur ajustée médiane divisée par la largeur vraie ; 1,000 est donc parfait. L'écrêtage est itéré jusqu'à masque stable ; les lignes corrigées divisent par le facteur de troncature gaussien ; le mélange est un modèle à deux composantes gaussiennes ajusté avec sa propre fraction et son propre rapport libres.

methodméthode no outlierssans aberrants 9% ×6 25% ×6 10% one-sided10 % unilatéraux true t3 tailsvraies queues t3[5]
GaussianGaussienne1.0031,0032.0382,0383.1043,1042.8582,8581.6781,678
Student-t nu=40.8330,8331.0231,0231.5441,5441.1961,1961.0761,076
3σ clipécrêtage 3σ0.9880,9881.0311,0311.1901,1900.9940,9941.2131,213
3σ clip, correctedécrêtage 3σ, corrigé1.0011,0011.0451,0451.2061,2061.0081,0081.2301,230
2σ clip, correctedécrêtage 2σ, corrigé0.8480,8480.8610,8610.8900,8900.8520,8520.8360,836
2-component mixturemélange à 2 composantes0.9730,9730.9960,9960.9950,9950.9540,9541.0791,079

What the table actually says.

  • Sigma clipping at 3σ is good, and deserves to be said so. With the truncation correction it is the most accurate method on clean data (1.001) and the best of all on one-sided outliers (1.008), where Student-t is 20% high because it downweights a one-sided tail without removing it. Its weaknesses are elsewhere: the correction is Gaussian-specific and threshold-specific, and at 2σ it is 11 to 16% low in every column, because the mask and the fit are not independent and no analytic factor repairs that. It also needs a working σ(x) to clip against, which is the quantity you were trying to estimate.
  • The fitted mixture wins where the data really are a mixture (0.996 and 0.995), which is no surprise since it is then the true model. It pays for it in precision: the realisation-to-realisation scatter of its width is 0.057 on clean data against 0.026 for Student-t, and 0.108 on genuine t3 tails against 0.042. It also carries two extra parameters, a likelihood that can degenerate onto a single point, and no guarantee of a unique optimum.
  • Student-t is the one-parameter middle, and it is the only row in the table that never needs a threshold, never deletes a point, and applies the same weight to the trend and to the envelope. Its bill is the deflation in the first column.

Choosing. Contamination below about 10% and you want a single robust pass with no knobs: nu=4. One-sided contamination, or you need the band to be a literal standard deviation: 3σ clipping with the correction, or nu plus a coverage calibration. The contaminating population is physically interesting and you want to measure it: fit the mixture, and accept the extra variance. Contamination above roughly 30%: none of these is trustworthy without modelling the contaminant explicitly.

Ce que dit réellement ce tableau.

  • L'écrêtage à 3σ est bon, et il faut le dire. Avec la correction de troncature, c'est la méthode la plus exacte sur données propres (1,001) et la meilleure de toutes sur les aberrants unilatéraux (1,008), là où Student surestime de 20 % parce qu'il sous-pondère une queue unilatérale sans la supprimer. Ses faiblesses sont ailleurs : la correction est propre au cas gaussien et au seuil choisi, et à 2σ elle sous-estime de 11 à 16 % dans toutes les colonnes, car le masque et l'ajustement ne sont pas indépendants et aucun facteur analytique ne répare cela. Il faut de plus disposer d'un σ(x) de travail pour écrêter, c'est-à-dire de la quantité même que l'on cherchait à estimer.
  • Le mélange ajusté gagne là où les données sont réellement un mélange (0,996 et 0,995), ce qui n'a rien d'étonnant puisqu'il en est alors le modèle vrai. Il le paie en précision : la dispersion de sa largeur d'une réalisation à l'autre vaut 0,057 sur données propres contre 0,026 pour Student, et 0,108 sur de vraies queues t3 contre 0,042. Il porte en outre deux paramètres de plus, une vraisemblance qui peut dégénérer sur un point isolé, et aucune garantie d'optimum unique.
  • Student est le juste milieu à un paramètre, et c'est la seule ligne du tableau qui ne demande aucun seuil, ne supprime aucun point et applique le même poids à la tendance et à l'enveloppe. Sa facture, c'est la déflation de la première colonne.

Choisir. Contamination sous environ 10 % et vous voulez une passe robuste unique sans réglage : nu=4. Contamination unilatérale, ou besoin que l'enveloppe soit un écart-type au sens littéral : écrêtage à 3σ avec la correction, ou nu assorti d'une calibration par la couverture. La population contaminante vous intéresse physiquement et vous voulez la mesurer : ajustez le mélange, et acceptez la variance supplémentaire. Contamination au-dessus d'environ 30 % : aucune de ces méthodes n'est fiable sans modéliser explicitement le contaminant.

Points with measurement errorsPoints avec barres d'erreur

If your points carry uncertainties, the observed spread is the intrinsic spread and the measurement noise added in quadrature. Usually the intrinsic part is the one you want to describe. Pass yerr and the fit separates them inside the likelihood.

Si vos points portent des incertitudes, la dispersion observée est la somme quadratique de la dispersion intrinsèque et du bruit de mesure. C'est généralement la part intrinsèque que vous cherchez à décrire. Passez yerr et l'ajustement les sépare au sein de la vraisemblance.

Fit with and without measurement errors supplied
400 points whose error bars grow with x, on top of a constant intrinsic scatter of 0.45. Ignoring the error bars gives a band of 1.11, which is just the observed spread. Supplying them recovers 0.46. 400 points dont les barres d'erreur croissent avec x, superposées à une dispersion intrinsèque constante de 0,45. Ignorer les barres d'erreur donne une enveloppe de 1,11, qui n'est que la dispersion observée. Les fournir permet de retrouver 0,46.

Compared to binningComparaison avec le binning

Binned mean and standard deviation versus a polyband fit
The same 220 points. Left: mean and standard deviation in ten bins of width 1. The result is jumpy, undefined between bin centres, and the last bin holds too few points to estimate a width from. Right: the continuous fit, which uses every point at every x and recovers the true width (dashed). Les mêmes 220 points. À gauche : moyenne et écart-type dans dix intervalles de largeur 1. Le résultat est en dents de scie, indéfini entre les centres, et le dernier intervalle contient trop peu de points pour en estimer une largeur. À droite : l'ajustement continu, qui utilise tous les points à chaque x et retrouve la largeur vraie (pointillés).

Matplotlib integrationIntégration matplotlib

plot_polyband draws on the axis you give it, never touches figure-level state, never calls show() or legend() by itself, and returns every artist it created. That makes it safe to call twice on the same axis, which is how you overlay two fits.

plot_polyband dessine sur l'axe que vous lui donnez, ne touche jamais à l'état global de la figure, n'appelle jamais show() ni legend() de lui-même, et renvoie tous les objets graphiques qu'il a créés. On peut donc l'appeler deux fois sur le même axe, ce qui est la façon de superposer deux ajustements.

fig, ax = plt.subplots(figsize=(9, 5))

art = plot_polyband(
    fit, x, y, ax=ax,                     # ax is yours; nothing global is touched
    nsigma=(1, 2, 3),                     # three nested bands, widest most transparent
    color="#c77dff",                      # trend and bands
    point_kw=dict(s=14, marker="^"),      # forwarded verbatim to ax.scatter
    trend_kw=dict(linewidth=3.0),         # forwarded verbatim to ax.plot
    band_kw=dict(hatch="//"),             # forwarded verbatim to ax.fill_between
    show_trend_error=True,                # add the (much narrower) error on the curve
)

# Everything it drew comes back, so restyle after the fact instead of
# passing more arguments in.
art.trend.set_zorder(10)
art.points.set_alpha(0.15)
ax.legend(handles=art.legend_handles)     # it never calls legend() itself

# Call it twice on the same axis to overlay a second fit. show_points=False
# stops the scatter being drawn again on top of itself.
plot_polyband(robust_fit, ax=ax, show_points=False,
              color="tab:green", label_prefix="Student-t: ")

# Curves stop where the data stop. Ask for an extension deliberately, and
# style it so the reader can see it is an extrapolation.
plot_polyband(fit, ax=ax, show_points=False, extrapolate=0.15,
              trend_kw=dict(linestyle=":"), labels=False)
fig, ax = plt.subplots(figsize=(9, 5))

art = plot_polyband(
    fit, x, y, ax=ax,                     # l'axe est le vôtre, rien de global n'est touché
    nsigma=(1, 2, 3),                     # trois enveloppes imbriquées, la plus large la plus transparente
    color="#c77dff",                      # tendance et enveloppes
    point_kw=dict(s=14, marker="^"),      # transmis tel quel à ax.scatter
    trend_kw=dict(linewidth=3.0),         # transmis tel quel à ax.plot
    band_kw=dict(hatch="//"),             # transmis tel quel à ax.fill_between
    show_trend_error=True,                # ajoute l'erreur (bien plus étroite) sur la courbe
)

# Tout ce qui a été dessiné est renvoyé : restylez après coup plutôt que de
# passer davantage d'arguments.
art.trend.set_zorder(10)
art.points.set_alpha(0.15)
ax.legend(handles=art.legend_handles)     # la fonction n'appelle jamais legend() elle-même

# Appelez-la deux fois sur le même axe pour superposer un second ajustement.
# show_points=False évite de redessiner le nuage par-dessus lui-même.
plot_polyband(robust_fit, ax=ax, show_points=False,
              color="tab:green", label_prefix="Student-t: ")

# Les courbes s'arrêtent où s'arrêtent les données. Demandez une extension
# délibérément, et stylez-la pour que le lecteur voie qu'il s'agit d'une
# extrapolation.
plot_polyband(fit, ax=ax, show_points=False, extrapolate=0.15,
              trend_kw=dict(linestyle=":"), labels=False)

Curves stop exactly where the data stop. A polynomial band has no business being drawn where nothing constrains it, so extrapolate defaults to 0 and any extension is something you have to ask for deliberately.

Les courbes s'arrêtent exactement là où s'arrêtent les données. Une enveloppe polynomiale n'a rien à faire là où rien ne la contraint : extrapolate vaut 0 par défaut, et toute extension doit être demandée délibérément.

Runnable examplesExemples exécutables

Five scripts in examples/, each self-contained and running on synthetic data shipped with the package:

Cinq scripts dans examples/, chacun autonome et fonctionnant sur des données synthétiques livrées avec le module :

The figures on this page come from docs/make_figures.py, which regenerates all of them in one run.

Les figures de cette page proviennent de docs/make_figures.py, qui les régénère toutes en une seule exécution.

Missing and non-finite dataDonnées manquantes et non finies

Real catalogues have holes in them, so you can hand one straight to fit_polyband without cleaning it first. NaN and inf are treated identically and removed row-wise: a point enters the fit only if its x, its y and, when supplied, its yerr are all finite. The three arrays are masked in a single operation, which is what keeps them aligned; filtering them one at a time is the classic way to silently pair the wrong x with the wrong y.

  • log_y=True additionally drops y ≤ 0, which has no logarithm.
  • yerr must be finite and non-negative. A point whose error bar is NaN is dropped rather than quietly treated as having no error, because those are different statements. yerr = 0 is legitimate and kept: it says the measurement is exact.
  • Removal is silent, so nothing interrupts a batch job. fit.n_points reports how many points actually entered the fit, and comparing it against len(x) is how you find out what was discarded.
  • If fewer points survive than there are free parameters, you get a ValueError rather than a meaningless fit.

Going the other way, the methods on the fit object propagate rather than filter: predict(np.nan) is NaN, and in_range is False for both NaN and inf. coverage() is the one exception and drops non-finite residuals, since a fraction computed over NaN would mean nothing.

Les vrais catalogues sont troués, vous pouvez donc en passer un directement à fit_polyband sans le nettoyer au préalable. NaN et inf sont traités de la même façon et retirés par ligne : un point n'entre dans l'ajustement que si son x, son y et, le cas échéant, son yerr sont tous finis. Les trois tableaux sont masqués en une seule opération, ce qui garantit leur alignement ; les filtrer un à un est la façon classique d'apparier silencieusement le mauvais x avec le mauvais y.

  • log_y=True écarte en plus les y ≤ 0, qui n'ont pas de logarithme.
  • yerr doit être fini et positif ou nul. Un point dont la barre d'erreur vaut NaN est retiré plutôt que considéré comme dépourvu d'erreur, car ce sont deux énoncés différents. yerr = 0 est légitime et conservé : cela signifie que la mesure est exacte.
  • Le retrait est silencieux, de sorte que rien n'interrompt un traitement par lots. fit.n_points indique combien de points sont réellement entrés dans l'ajustement, et le comparer à len(x) vous dit ce qui a été écarté.
  • S'il survit moins de points qu'il n'y a de paramètres libres, vous obtenez une ValueError plutôt qu'un ajustement dénué de sens.

Dans l'autre sens, les méthodes de l'objet ajusté propagent au lieu de filtrer : predict(np.nan) vaut NaN, et in_range est False aussi bien pour NaN que pour inf. coverage() fait seule exception et écarte les résidus non finis, une fraction calculée sur des NaN n'ayant aucun sens.

ReferenceRéférence

Fitting optionsOptions d'ajustement

ArgumentArgument What it doesRôle
order_meanDegree of the trend polynomial. 0 is a constant, 1 a straight line.Degré du polynôme de tendance. 0 pour une constante, 1 pour une droite.
order_widthDegree of the ln(sigma) polynomial, independent of the trend. 0 gives a band of constant width.Degré du polynôme en ln(sigma), indépendant de la tendance. 0 donne une enveloppe de largeur constante.
log_y=TrueFit log10(y). For quantities spanning decades or with multiplicative scatter.Ajuster log10(y). Pour les quantités s'étendant sur des décades ou à dispersion multiplicative.
yerr=...Per-point measurement errors, so the fitted width is the intrinsic scatter.Erreurs de mesure point par point, pour que la largeur ajustée soit la dispersion intrinsèque.
nu=4Student-t likelihood, for samples with outliers.Vraisemblance de Student, pour les échantillons avec valeurs aberrantes.
dof_correctionUndo the downward bias of maximum-likelihood widths. On by default.Corriger le biais vers le bas des largeurs par maximum de vraisemblance. Actif par défaut.
n_bootstrapEstimate the coefficient covariance by resampling instead of from the Hessian.Estimer la covariance des coefficients par rééchantillonnage plutôt que par le hessien.

On the fit objectSur l'objet ajusté

MethodMéthode ReturnsRenvoie
predict(x)The trend, in the units of y.La tendance, dans les unités de y.
scatter(x)Half-width of the 1σ band, in dex when log_y is set.Demi-largeur de l'enveloppe à 1σ, en dex si log_y est actif.
envelope(x, nsigma)Lower and upper edges of the band, where the points lie.Bords inférieur et supérieur de l'enveloppe, là où se trouvent les points.
trend_band(x)Confidence band on the curve itself, a different and narrower thing.Bande de confiance sur la courbe elle-même, quantité différente et plus étroite.
zscore(x, y)How unusual a point is, in units of the local width.À quel point un point est atypique, en unités de la largeur locale.
coverage(x, y)Observed against expected fraction inside the band.Fraction observée dans l'enveloppe, comparée à l'attendu.
grid(n, extrapolate)An x grid spanning the fitted range, for plotting.Une grille en x couvrant l'intervalle ajusté, pour le tracé.
summary()Human-readable description of the fit.Description lisible de l'ajustement.
aic, bicInformation criteria, for comparing degrees[1][2].Critères d'information, pour comparer les degrés[1][2].

NotesNotes

What AIC, BIC and t3 actually areCe que sont réellement l'AIC, le BIC et t3

  1. [1]
    AIC, the Akaike information criterion, is 2p − 2 ln L, where p is the number of free parameters and L the likelihood at the optimum. Adding a parameter always raises the likelihood, so the fit alone can never tell you to stop; the 2p term is the price. What Akaike showed is that this particular price makes the criterion an estimate of how much information is lost by using the model instead of the truth, up to a constant common to all the candidates. Two consequences follow. Only differences between AIC values mean anything, never one value on its own. And AIC aims at predicting well, not at identifying a true model: it does not assume the truth is among the candidates, and it does not become certain as the sample grows. See [1a], [1c].
    L'AIC, critère d'information d'Akaike, vaut 2p − 2 ln L, où p est le nombre de paramètres libres et L la vraisemblance à l'optimum. Ajouter un paramètre augmente toujours la vraisemblance : l'ajustement seul ne peut donc jamais vous dire de vous arrêter, et le terme 2p est le prix à payer. Ce qu'Akaike a montré, c'est que ce prix-là fait du critère une estimation de la quantité d'information perdue en utilisant le modèle plutôt que la réalité, à une constante près commune à tous les candidats. Deux conséquences. Seules les différences d'AIC ont un sens, jamais une valeur isolée. Et l'AIC vise à bien prédire, non à identifier un modèle vrai : il ne suppose pas que la vérité figure parmi les candidats, et il ne devient pas certain quand l'échantillon grandit. Voir [1a], [1c].
  2. [2]
    BIC, the Bayesian information criterion, is p ln N − 2 ln L. Same shape, different price: ln N instead of 2. It comes from a different question, namely an approximation to the probability the data assign to each model after integrating over its parameters, so it estimates which model generated the data rather than which will predict best. Because the penalty grows with the sample, BIC keeps demanding more evidence for each extra parameter as data accumulate, and it will settle on the true model given enough of them, if the true model is among the candidates. From 8 points on, ln N exceeds 2, so BIC is the stricter of the two for any realistic sample. See [1b], [1d].
    Le BIC, critère d'information bayésien, vaut p ln N − 2 ln L. Même forme, prix différent : ln N au lieu de 2. Il découle d'une autre question, à savoir une approximation de la probabilité que les données accordent à chaque modèle après intégration sur ses paramètres ; il estime donc quel modèle a engendré les données plutôt que lequel prédira le mieux. Comme la pénalité croît avec l'échantillon, le BIC exige toujours plus de preuves pour chaque paramètre supplémentaire à mesure que les données s'accumulent, et il finit par retenir le modèle vrai s'il figure parmi les candidats. Dès 8 points, ln N dépasse 2 : le BIC est donc le plus strict des deux pour tout échantillon réaliste. Voir [1b], [1d].
  3. [3]
    Why a difference below about 2 counts as a tie. A BIC difference is roughly twice the logarithm of the Bayes factor between the two models, and on the scale Kass and Raftery [2a] set out for reading Bayes factors, that bottom band is the one they describe as not worth more than a bare mention. The same threshold is conventional for AIC. This is why select_orders gives you the whole ranking rather than only the winner: if the top few are within 2 of each other, the criterion has not chosen anything, and the simplest of them is the one to keep.
    Pourquoi un écart inférieur à environ 2 vaut une égalité. Un écart de BIC vaut approximativement deux fois le logarithme du facteur de Bayes entre les deux modèles, et sur l'échelle proposée par Kass et Raftery [2a] pour lire les facteurs de Bayes, cette première bande est celle qu'ils décrivent comme ne méritant pas plus qu'une simple mention. Le même seuil est d'usage pour l'AIC. C'est pourquoi select_orders vous renvoie tout le classement et pas seulement le gagnant : si les premiers sont à moins de 2 les uns des autres, le critère n'a rien tranché, et c'est le plus simple qu'il faut retenir.
  4. [4]
    Why maximum-likelihood widths come out too narrow. The fitted trend follows the noise a little, so the residuals around it are systematically smaller than the true deviations, by a factor of about √((N − p)/N). This is the same effect that makes a plain standard deviation divide by N − 1 rather than N [3f], generalised to p fitted parameters. dof_correction=True undoes it and is the default.
    Pourquoi les largeurs par maximum de vraisemblance sont trop étroites. La tendance ajustée épouse un peu le bruit, de sorte que les résidus autour d'elle sont systématiquement plus petits que les écarts vrais, d'un facteur d'environ √((N − p)/N). C'est le même effet qui fait diviser un écart-type ordinaire par N − 1 plutôt que par N [3f], généralisé à p paramètres ajustés. dof_correction=True le corrige et constitue le comportement par défaut.
  5. [5]
    t3, and the Student-t family. nu is the number of degrees of freedom of a Student-t distribution, written tν, and it controls how heavy the tails are. As ν grows the distribution becomes a Gaussian: by ν = 30 the two are already hard to tell apart. As ν shrinks the tails thicken. Take t3, the column in the table above. Its scale parameter is still called σ, but its standard deviation is √3 σ, about 1.73 times larger, and excursions that a Gaussian treats as impossible become ordinary: a 3σ departure happens 21 times more often than under a Gaussian, and a 5σ one roughly 27 000 times more often. Push further and the family stops being well behaved at all: at ν ≤ 2 the variance is infinite, and at ν = 1 it is the Cauchy distribution, which has no mean.
    t3, et la famille de Student. nu est le nombre de degrés de liberté d'une loi de Student, notée tν, et il gouverne l'épaisseur des queues. Quand ν croît, la loi tend vers une gaussienne : dès ν = 30 les deux sont difficiles à distinguer. Quand ν diminue, les queues s'épaississent. Prenez t3, la colonne du tableau ci-dessus. Son paramètre d'échelle s'appelle toujours σ, mais son écart-type vaut √3 σ, environ 1,73 fois plus, et des écarts qu'une gaussienne juge impossibles y deviennent ordinaires : un écart de 3σ y survient 21 fois plus souvent que sous une gaussienne, et un écart de 5σ environ 27 000 fois plus souvent. En poussant plus loin, la famille cesse d'être raisonnable : à ν ≤ 2 la variance est infinie, et à ν = 1 c'est la loi de Cauchy, qui n'a même pas de moyenne.

ReferencesRéférences

Model selectionSélection de modèles

  • [1a] Akaike, H. (1974). A new look at the statistical model identification. IEEE Transactions on Automatic Control 19, 716–723. doi:10.1109/TAC.1974.1100705
  • [1b] Schwarz, G. (1978). Estimating the dimension of a model. The Annals of Statistics 6, 461–464. doi:10.1214/aos/1176344136
  • [1c] Cavanaugh, J. E. & Neath, A. A. (2019). The Akaike information criterion: background, derivation, properties, application, interpretation, and refinements. WIREs Computational Statistics 11, e1460. doi:10.1002/wics.1460
  • [1d] Neath, A. A. & Cavanaugh, J. E. (2012). The Bayesian information criterion: background, derivation, and applications. WIREs Computational Statistics 4, 199–203. doi:10.1002/wics.199
  • [2a] Kass, R. E. & Raftery, A. E. (1995). Bayes factors. Journal of the American Statistical Association 90, 773–795. doi:10.1080/01621459.1995.10476572

Robustness and outliersRobustesse et valeurs aberrantes

  • [2b] Huber, P. J. (1964). Robust estimation of a location parameter. The Annals of Mathematical Statistics 35, 73–101. doi:10.1214/aoms/1177703732
  • [2c] Lange, K. L., Little, R. J. A. & Taylor, J. M. G. (1989). Robust statistical modeling using the t distribution. Journal of the American Statistical Association 84, 881–896. doi:10.1080/01621459.1989.10478852 — the paper behind the nu option— l'article derrière l'option nu
  • [2d] Rousseeuw, P. J. (1984). Least median of squares regression. Journal of the American Statistical Association 79, 871–880. doi:10.1080/01621459.1984.10477105 — the trimmed idea behind the starting guess— l'idée de troncature derrière le point de départ
  • [2e] Rousseeuw, P. J. & Croux, C. (1993). Alternatives to the median absolute deviation. Journal of the American Statistical Association 88, 1273–1283. doi:10.1080/01621459.1993.10476408

Likelihood, information, resamplingVraisemblance, information, rééchantillonnage

  • [3a] Kullback, S. & Leibler, R. A. (1951). On information and sufficiency. The Annals of Mathematical Statistics 22, 79–86. doi:10.1214/aoms/1177729694 — the divergence that makes the band uncompensatable— la divergence qui rend l'enveloppe non compensable
  • [3b] Dempster, A. P., Laird, N. M. & Rubin, D. B. (1977). Maximum likelihood from incomplete data via the EM algorithm. Journal of the Royal Statistical Society Series B 39, 1–22. doi:10.1111/j.2517-6161.1977.tb01600.x
  • [3c] Efron, B. (1979). Bootstrap methods: another look at the jackknife. The Annals of Statistics 7, 1–26. doi:10.1214/aos/1176344552 — the n_bootstrap option— l'option n_bootstrap
  • [3d] Nelder, J. A. & Mead, R. (1965). A simplex method for function minimization. The Computer Journal 7, 308–313. doi:10.1093/comjnl/7.4.308

Background readingPour aller plus loin