Ho usato il seguente approccio in passato per calcolare la deviazione di assoluzione in modo moderatamente efficiente (nota, questo è un approccio programmatore, non uno statistico, quindi indubbiamente potrebbero esserci trucchi intelligenti come quello di Shabbychef che potrebbero essere più efficienti).
ATTENZIONE: questo non è un algoritmo online. Richiede O(n)
memoria. Inoltre, ha prestazioni nel peggiore dei casiO(n)
, per insiemi di dati come [1, -2, 4, -8, 16, -32, ...]
(cioè lo stesso del ricalcolo completo). [1]
Tuttavia, poiché funziona ancora bene in molti casi d'uso, potrebbe valere la pena pubblicare qui. Ad esempio, al fine di calcolare la devianza assoluta di 10000 casuali numeri tra -100 e 100 all'arrivo di ogni articolo, il mio algoritmo impiega meno di un secondo, mentre il ricalcolo completo richiede oltre 17 secondi (sulla mia macchina, varierà per macchina e in base ai dati di input). Tuttavia, è necessario mantenere l'intero vettore in memoria, il che può costituire un vincolo per alcuni usi. Lo schema dell'algoritmo è il seguente:
- Invece di avere un singolo vettore per memorizzare le misurazioni passate, usa tre code di priorità ordinate (qualcosa come un heap min / max). Queste tre liste suddividono l'input in tre: elementi maggiori della media, elementi minori della media e elementi uguali alla media.
- (Quasi) ogni volta che aggiungi un oggetto la media cambia, quindi dobbiamo ripartizionare. La cosa cruciale è la natura ordinata delle partizioni, il che significa che invece di scansionare tutti gli elementi dell'elenco per ripararli, dobbiamo solo leggere quegli elementi che stiamo spostando. Mentre nel peggiore dei casi ciò richiederà ancora
O(n)
operazioni di spostamento, per molti casi d'uso non è così.
- Usando una contabilità intelligente, possiamo assicurarci che la devianza sia calcolata correttamente in ogni momento, durante il ripartizionamento e quando si aggiungono nuovi elementi.
Di seguito è riportato un codice di esempio, in python. Si noti che consente solo di aggiungere elementi all'elenco, non rimossi. Questo potrebbe essere facilmente aggiunto, ma al momento in cui ho scritto questo non ne avevo bisogno. Piuttosto che implementare personalmente le code prioritarie, ho usato la lista ordinata dall'eccellente pacchetto blist di Daniel Stutzbach , che usa B + Tree internamente .
Considera questo codice concesso in licenza con la licenza MIT . Non è stato significativamente ottimizzato o perfezionato, ma ha funzionato per me in passato. Le nuove versioni saranno disponibili qui . Fammi sapere se hai domande o trova qualche bug.
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] Se i sintomi persistono, consultare il medico.