J'ai utilisé l'approche suivante dans le passé pour calculer la déviation d'absolution de manière modérément efficace (notez que c'est une approche de programmeurs, pas de statisticiens, donc il est indubitable qu'il peut y avoir des astuces intelligentes comme celles de shabbychef qui pourraient être plus efficaces).
AVERTISSEMENT: ce n'est pas un algorithme en ligne. Cela nécessite de la O(n)
mémoire. De plus, il présente les performances les plus défavorables de O(n)
, pour des ensembles de données comme [1, -2, 4, -8, 16, -32, ...]
(c'est-à-dire les mêmes que pour le recalcul complet). [1]
Cependant, comme il fonctionne toujours bien dans de nombreux cas d'utilisation, il peut être utile de le publier ici. Par exemple, afin de calculer la déviance absolue de 10000 nombres aléatoires entre -100 et 100 à l'arrivée de chaque élément, mon algorithme prend moins d'une seconde, tandis que le recalcul complet prend plus de 17 secondes (sur ma machine, variera selon la machine et selon les données d'entrée). Cependant, vous devez conserver le vecteur entier en mémoire, ce qui peut être une contrainte pour certaines utilisations. Le contour de l'algorithme est le suivant:
- Au lieu d'avoir un seul vecteur pour stocker les mesures passées, utilisez trois files d'attente prioritaires triées (quelque chose comme un tas min / max). Ces trois listes divisent l'entrée en trois: éléments supérieurs à la moyenne, éléments inférieurs à la moyenne et éléments égaux à la moyenne.
- (Presque) chaque fois que vous ajoutez un élément, la moyenne change, nous devons donc repartitionner. La chose cruciale est la nature triée des partitions, ce qui signifie qu'au lieu d'analyser chaque élément de la liste pour les répartir, nous n'avons qu'à lire les éléments que nous déplaçons. Alors que dans le pire des cas, cela nécessitera toujours des
O(n)
opérations de déplacement, pour de nombreux cas d'utilisation, ce n'est pas le cas.
- En utilisant une comptabilité intelligente, nous pouvons nous assurer que la déviance est correctement calculée à tout moment, lors du repartitionnement et lors de l'ajout de nouveaux éléments.
Un exemple de code, en python, est ci-dessous. Notez qu'il permet uniquement d'ajouter des éléments à la liste, pas de les supprimer. Cela pourrait facilement être ajouté, mais au moment où j'ai écrit cela, je n'en avais pas besoin. Plutôt que d'implémenter moi-même les files d'attente prioritaires, j'ai utilisé la liste de tri de l'excellent package blist de Daniel Stutzbach , qui utilise B + Tree s en interne.
Considérez ce code sous licence MIT . Il n'a pas été optimisé ou poli de manière significative, mais a fonctionné pour moi dans le passé. De nouvelles versions seront disponibles ici . Faites-moi savoir si vous avez des questions ou si vous trouvez des bugs.
from blist import sortedlist
import operator
class deviance_list:
def __init__(self):
self.mean = 0.0
self._old_mean = 0.0
self._sum = 0L
self._n = 0 #n items
# items greater than the mean
self._toplist = sortedlist()
# items less than the mean
self._bottomlist = sortedlist(key = operator.neg)
# Since all items in the "eq list" have the same value (self.mean) we don't need
# to maintain an eq list, only a count
self._eqlistlen = 0
self._top_deviance = 0
self._bottom_deviance = 0
@property
def absolute_deviance(self):
return self._top_deviance + self._bottom_deviance
def append(self, n):
# Update summary stats
self._sum += n
self._n += 1
self._old_mean = self.mean
self.mean = self._sum / float(self._n)
# Move existing things around
going_up = self.mean > self._old_mean
self._rebalance(going_up)
# Add new item to appropriate list
if n > self.mean:
self._toplist.add(n)
self._top_deviance += n - self.mean
elif n == self.mean:
self._eqlistlen += 1
else:
self._bottomlist.add(n)
self._bottom_deviance += self.mean - n
def _move_eqs(self, going_up):
if going_up:
self._bottomlist.update([self._old_mean] * self._eqlistlen)
self._bottom_deviance += (self.mean - self._old_mean) * self._eqlistlen
self._eqlistlen = 0
else:
self._toplist.update([self._old_mean] * self._eqlistlen)
self._top_deviance += (self._old_mean - self.mean) * self._eqlistlen
self._eqlistlen = 0
def _rebalance(self, going_up):
move_count, eq_move_count = 0, 0
if going_up:
# increase the bottom deviance of the items already in the bottomlist
if self.mean != self._old_mean:
self._bottom_deviance += len(self._bottomlist) * (self.mean - self._old_mean)
self._move_eqs(going_up)
# transfer items from top to bottom (or eq) list, and change the deviances
for n in iter(self._toplist):
if n < self.mean:
self._top_deviance -= n - self._old_mean
self._bottom_deviance += (self.mean - n)
# we increment movecount and move them after the list
# has finished iterating so we don't modify the list during iteration
move_count += 1
elif n == self.mean:
self._top_deviance -= n - self._old_mean
self._eqlistlen += 1
eq_move_count += 1
else:
break
for _ in xrange(0, move_count):
self._bottomlist.add(self._toplist.pop(0))
for _ in xrange(0, eq_move_count):
self._toplist.pop(0)
# decrease the top deviance of the items remain in the toplist
self._top_deviance -= len(self._toplist) * (self.mean - self._old_mean)
else:
if self.mean != self._old_mean:
self._top_deviance += len(self._toplist) * (self._old_mean - self.mean)
self._move_eqs(going_up)
for n in iter(self._bottomlist):
if n > self.mean:
self._bottom_deviance -= self._old_mean - n
self._top_deviance += n - self.mean
move_count += 1
elif n == self.mean:
self._bottom_deviance -= self._old_mean - n
self._eqlistlen += 1
eq_move_count += 1
else:
break
for _ in xrange(0, move_count):
self._toplist.add(self._bottomlist.pop(0))
for _ in xrange(0, eq_move_count):
self._bottomlist.pop(0)
# decrease the bottom deviance of the items remain in the bottomlist
self._bottom_deviance -= len(self._bottomlist) * (self._old_mean - self.mean)
if __name__ == "__main__":
import random
dv = deviance_list()
# Test against some random data, and calculate result manually (nb. slowly) to ensure correctness
rands = [random.randint(-100, 100) for _ in range(0, 1000)]
ns = []
for n in rands:
dv.append(n)
ns.append(n)
print("added:%4d, mean:%3.2f, oldmean:%3.2f, mean ad:%3.2f" %
(n, dv.mean, dv._old_mean, dv.absolute_deviance / dv.mean))
assert sum(ns) == dv._sum, "Sums not equal!"
assert len(ns) == dv._n, "Counts not equal!"
m = sum(ns) / float(len(ns))
assert m == dv.mean, "Means not equal!"
real_abs_dev = sum([abs(m - x) for x in ns])
# Due to floating point imprecision, we check if the difference between the
# two ways of calculating the asb. dev. is small rather than checking equality
assert abs(real_abs_dev - dv.absolute_deviance) < 0.01, (
"Absolute deviances not equal. Real:%.2f, calc:%.2f" % (real_abs_dev, dv.absolute_deviance))
[1] Si les symptômes persistent, consultez votre médecin.