web-dev-qa-db-fra.com

Comment calculer ou approcher la médiane d'une liste sans stocker la liste

J'essaie de calculer la médiane d'un ensemble de valeurs, mais je ne veux pas stocker toutes les valeurs, car cela pourrait nuire à la mémoire. Existe-t-il un moyen de calculer ou d’approcher la médiane sans stocker ni trier toutes les valeurs individuelles?

Idéalement, j'aimerais écrire mon code un peu comme ceci:

var medianCalculator = new MedianCalculator();
foreach (var value in SourceData)
{
  medianCalculator.Add(value);
}
Console.WriteLine("The median is: {0}", medianCalculator.Median);

Tout ce dont j'ai besoin, c'est du code MedianCalculator!

Mise à jour: Certaines personnes ont demandé si les valeurs pour lesquelles je tente de calculer la médiane avaient des propriétés connues. La réponse est oui. Une valeur est exprimée en incréments de 0,5 d'environ -25 à -0,5. L'autre est également en incréments de 0.5 de -120 à -60. Je suppose que cela signifie que je peux utiliser une forme d'histogramme pour chaque valeur.

Merci

Entaille

39
Nick Randell

Si les valeurs sont discrètes et que le nombre de valeurs distinctes n’est pas trop élevé, vous pouvez simplement accumuler le nombre de fois où chaque valeur apparaît dans un histogramme, puis rechercher la médiane à partir des comptes de l’histogramme (additionnez simplement les comptes du haut et du bas). de l’histogramme jusqu’au milieu). Ou si ce sont des valeurs continues, vous pouvez les répartir dans des bacs - cela ne vous dirait pas la médiane exacte mais vous donnerait une plage, et si vous avez besoin de savoir plus précisément, vous pouvez parcourir à nouveau la liste, en examinant uniquement les éléments dans la corbeille centrale.

40
David Z

Il y a la statistique "corrective". Cela fonctionne en commençant par configurer k tableaux, chacun de longueur b. Les valeurs de données sont introduites dans le premier tableau et, lorsque celui-ci est plein, la médiane est calculée et stockée dans la première position du tableau suivant, après quoi le premier tableau est réutilisé. Lorsque le second tableau est plein, la médiane de ses valeurs est stockée dans la première position du troisième tableau, etc. etc. Vous avez l’idée :)

C'est simple et assez robuste. La référence est ici ...

http://web.ipac.caltech.edu/staff/fmasci/home/astro_refs/Remedian.pdf

J'espère que cela t'aides

Michael

36
michael

J'utilise ces estimateurs moyen et médian incrémental/récursif, qui utilisent tous deux un stockage constant:

mean += eta * (sample - mean)
median += eta * sgn(sample - median)

où eta est un paramètre de vitesse d'apprentissage faible (par exemple, 0,001) et sgn () est la fonction signum qui renvoie l'un des éléments suivants: {-1, 0, 1}.

Ce type d’estimateur moyen incrémental semble être utilisé partout, par ex. dans les règles d’apprentissage des réseaux de neurones non supervisés, mais la version médiane semble beaucoup moins commune, malgré ses avantages (robustesse aux valeurs aberrantes). Il semble que la version médiane pourrait remplacer l’estimateur moyen dans de nombreuses applications.

J'aimerais beaucoup voir un estimateur de mode incrémentiel de forme similaire ...

(Remarque: j'ai également posté ceci sur un sujet similaire ici: Algorithmes "en ligne" (itérateur) pour l'estimation de la médiane statistique, du mode, de l'asymétrie, de la kurtosis? )

17
Tyler Streeter

Voici une approche folle que vous pourriez essayer. C'est un problème classique dans les algorithmes de streaming. Les règles sont

  1. Vous avez une mémoire limitée, par exemple, O(log n)n est le nombre d'éléments que vous souhaitez.
  2. Vous pouvez examiner chaque élément une fois, prendre une décision à ce moment-là, et savoir quoi en faire. Si vous le stockez, cela coûtera de la mémoire, si vous le jetez, il disparaîtra pour toujours.

L'idée de trouver une médiane est simple. Échantillonner des éléments O(1 / a^2 * log(1 / p)) * log(n) de la liste au hasard, vous pouvez le faire via un échantillonnage de réservoir (voir une question précédente ). Maintenant, renvoyez simplement la médiane de vos éléments échantillonnés, en utilisant une méthode classique.

La garantie est que l'index de l'article renvoyé sera (1 +/- a) / 2 avec une probabilité d'au moins 1-p. Il existe donc une probabilité p d’échec, vous pouvez le choisir en échantillonnant plus d’éléments. Et elle ne renverra pas la médiane ou ne garantira pas que la valeur de l'élément renvoyé est proche de la médiane. Seulement, lorsque vous triez la liste, l'élément renvoyé sera proche de la moitié de la liste.

Cet algorithme utilise O(log n) espace supplémentaire et s'exécute en temps linéaire.

13
Pall Melsted

Cela est difficile à comprendre en général, en particulier pour gérer les séries dégénérées déjà triées ou pour avoir un tas de valeurs au début de la liste mais la fin de la liste a des valeurs dans une plage différente.

L'idée de base de faire un histogramme est la plus prometteuse. Cela vous permet d’accumuler des informations sur la distribution et de répondre à des requêtes (comme la médiane). La médiane sera approximative puisque vous ne stockez évidemment pas toutes les valeurs. L'espace de stockage est fixe afin qu'il fonctionne avec n'importe quelle séquence de longueur que vous avez.

Mais vous ne pouvez pas simplement construire un histogramme à partir des 100 premières valeurs et utiliser cet histogramme de manière continue. Les données modifiées peuvent rendre cet histogramme invalide. Vous avez donc besoin d'un histogramme dynamique pouvant changer de plage et de bacs à la volée.

Faire une structure qui a N bacs. Vous allez stocker la valeur X de chaque transition d’emplacement (nombre total de N + 1) ainsi que la population de la corbeille. 

Diffusez dans vos données. Enregistrez les premières valeurs N + 1. Si le flux se termine avant cela, tant mieux, vous aurez toutes les valeurs chargées et vous pourrez trouver la médiane exacte et la renvoyer. Sinon, utilisez les valeurs pour définir votre premier histogramme. Il suffit de trier les valeurs et d’utiliser celles-ci comme définitions de poubelle, chaque poubelle ayant une population de 1. C’est bien d’avoir des dupes (0 poubelles de largeur). 

Maintenant, diffusez de nouvelles valeurs. Pour chacun d'entre eux, recherchez binaire pour trouver le bac auquel il appartient . Dans le cas habituel, vous augmentez simplement la population de ce bac et continuez . Si votre échantillon se situe au-delà des bords de l'histogramme (le plus élevé ou le plus bas), étendez simplement la plage de la corbeille d'extrémité pour l'inclure . Lorsque votre flux est terminé, vous trouvez la valeur médiane de l'échantillon en recherchant la corbeille qui a une population égale de ses deux côtés et en interpolant linéairement la largeur de corbeille restante.

Mais cela ne suffit pas ... vous devez toujours ADAPTER l'histogramme aux données au fur et à mesure de leur flux. Lorsqu'un bac est saturé, vous perdez des informations sur la sous-distribution de ce bac . Vous pouvez résoudre ce problème en adaptant basé sur une heuristique ... Le plus simple et le plus robuste est si un bin atteint un certain seuil de population (quelque chose comme 10 * v/N où v = # de valeurs vues jusqu'à présent dans le flux, et N est le nombre de bacs ), vous partagez cette corbeille trop abondante. Ajoutez une nouvelle valeur au milieu de la corbeille, donnez à chaque côté la moitié de la population de la corbeille d'origine. Mais maintenant, vous avez trop de bacs, vous devez donc SUPPRIMER un bac. Une bonne heuristique consiste à trouver la corbeille avec le plus petit produit de population et de largeur. Supprimez-le et fusionnez-le avec son voisin gauche ou son voisin droit (celui qui a le plus petit produit de largeur et de population.). Fait! Notez que la fusion ou le fractionnement de bacs perd des informations, mais que c'est inévitable… vous ne disposez que d'un stockage fixe.

Cet algorithme est agréable dans la mesure où il traitera tous les types de flux d'entrée et donnera de bons résultats. Si vous avez le luxe de choisir l'ordre d'échantillon, il est préférable d'utiliser un échantillon aléatoire, car cela minimise les fractionnements et les fusions.

L'algorithme vous permet également d'interroger n'importe quel centile, pas seulement la médiane, puisque vous disposez d'une estimation complète de la distribution.

J'utilise cette méthode dans mon propre code à de nombreux endroits, principalement pour le débogage des journaux .. où certaines statistiques que vous enregistrez ont une distribution inconnue. Avec cet algorithme, vous n'avez pas besoin de deviner à l'avance.

L'inconvénient est que les largeurs de casier inégales signifient que vous devez effectuer une recherche binaire pour chaque échantillon. Votre algorithme net est donc O (NlogN).

7
SPWorley

La suggestion de David semble être l'approche la plus judicieuse pour se rapprocher de la médiane.

Un mean courant pour le même problème est beaucoup plus facile à calculer:

Mn = Mn-1 + ((Vn - Mn-1)/n)

Où mn est la moyenne de n valeurs, Mn-1 est la moyenne précédente, et Vn est la nouvelle valeur.

En d'autres termes, la nouvelle moyenne est la moyenne existante plus la différence entre la nouvelle valeur et la moyenne, divisée par le nombre de valeurs.

Dans le code, cela ressemblerait à quelque chose comme:

new_mean = prev_mean + ((value - prev_mean) / count)

bien évidemment, vous voudrez peut-être prendre en compte des éléments spécifiques à la langue tels que les erreurs d'arrondi en virgule flottante, etc.

3
GrahamS

Je ne pense pas qu'il soit possible de le faire sans avoir la liste en mémoire. Vous pouvez évidemment approximer avec

  • moyenne si vous savez que les données sont réparties symétriquement
  • ou calculez la médiane appropriée d'un petit sous-ensemble de données (qui tient dans la mémoire) - si vous savez que vos données ont la même distribution dans l'échantillon (par exemple, le premier élément a la même distribution que le dernier)
3
Grzenio

Recherchez Min et Max dans la liste contenant N éléments grâce à la recherche linéaire et nommez-les HighValue et LowValue Soit MedianIndex = (N + 1)/2

Recherche binaire du 1er ordre:

Répétez les 4 étapes suivantes jusqu'à LowValue <HighValue.

  1. Obtenir la valeur médiane environ = (valeur élevée + valeur faible)/2

  2. Obtenir NumberOfItemsWichich Are Are LessThanorEqualToMedianValue = K

  3. est K = MedianIndex, puis retourne MedianValue

  4. est K> MedianIndex? then HighValue = MedianValue Else LowValue = MedianValue

Ce sera plus rapide sans consommer de mémoire 

Recherche binaire 2ème ordre:

LowIndex = 1 HighIndex = N

Répétez les 5 étapes suivantes jusqu'à (LowIndex <HighIndex)

  1. Obtenir une distribution approximative de DistrbutionPerUnit = (HighValue-LowValue)/(HighIndex-LowIndex)

  2. Obtenir une valeur médiane approximative = Valeur basse + (MedianIndex-LowIndex) * DistributionPerUnit

  3. Obtenir NumberOfItemsWichich Are Are LessThanorEqualToMedianValue = K

  4. est (K = MedianIndex)? retour MedianValue

  5. est (K> MedianIndex)? then HighIndex = K et HighValue = MedianValue Else LowIndex = K et LowValue = MedianValue

Ce sera plus rapide que le 1er ordre sans consommer de mémoire 

Nous pouvons également penser à adapter HighValue, LowValue et MedianValue avec HighIndex, LowIndex et MedianIndex à une parabole, et nous pouvons obtenir une recherche binaire ThirdOrder qui sera plus rapide que le second ordre sans utiliser de mémoire ...

2
lakshmanaraj

Généralement, si l'entrée se situe dans une certaine plage, disons entre 1 et 1 million, il est facile de créer un tableau de comptes: lisez le code de "quantile" et "ibucket" ici: http://code.google.com/ p/ea-utils/source/parcourir/trunk/clipper/sam-stats.cpp

Cette solution peut être généralisée à titre approximatif en contraignant l'entrée dans un nombre entier compris dans une plage en utilisant une fonction que vous inversez ensuite: IE: foo.Push ((int) input/1000000) et quantile (foo) * 1000000 . 

Si votre entrée est un nombre double précision arbitraire, vous devez alors mettre à l'échelle automatique votre histogramme lorsque les valeurs entrées sont hors limites (voir ci-dessus).

Ou vous pouvez utiliser la méthode médiane-triplets décrite dans cet article: http://web.cs.wpi.edu/~hofri/medsel.pdf

0
Erik Aronesty

J'ai repris l'idée du calcul quantile itératif. Il est important d’avoir une bonne valeur pour le point de départ et l’éta, ceux-ci peuvent provenir de la moyenne et de la sigma. Alors j'ai programmé ceci:

Fonction QuantileIterative (Var x: Tableau de Double; n: Entier; p, moyenne, sigma: Double): Double;
Var eta, quantile, q1, dq: double;
i: entier;
Commencer
quantile: = moyenne + 1,25 * sigma * (p-0,5);
q1: = quantile;
eta: = 0,2 * sigma/xy (1 + n, 0,75); // ne devrait pas être trop grand! définit la précision
Pour i: = 1 à n Do quantile: = quantile + eta * (signum_smooth (x [i] - quantile, eta) + 2 * p - 1);
dq: = abs (q1-quantile);
Si dq> eta
puis commence
Si dq <3 * eta alors eta: = eta/4;
Pour i: = 1 à n Do quantile: = quantile + eta * (signum_smooth (x [i] - quantile, eta) + 2 * p - 1);
fin;
QuantileIterative: = quantile
fin;

Comme la moyenne de deux éléments serait la moyenne, j'ai utilisé une fonction de signum lissée, et xy () est x ^ y. Y a-t-il des idées pour l'améliorer? Bien sûr, si nous avons plus de connaissances a priori, nous pouvons ajouter du code en utilisant min et max du tableau, l'inclinaison, etc. Pour les mégadonnées, vous n'utiliseriez peut-être pas de tableau, mais pour le tester, c'est plus facile. 

0
user32038