He utilizado el siguiente enfoque en el pasado para calcular la desviación de absolución de manera moderadamente eficiente (tenga en cuenta que este es un enfoque de programadores, no un estadístico, por lo que indudablemente puede haber trucos inteligentes como el de shabbychef que podrían ser más eficientes).
ADVERTENCIA: este no es un algoritmo en línea. Requiere O(n)
memoria Además, tiene un rendimiento en el peor de los casos O(n)
, para conjuntos de datos como [1, -2, 4, -8, 16, -32, ...]
(es decir, el mismo que el recálculo completo). [1]
Sin embargo, debido a que aún funciona bien en muchos casos de uso, podría valer la pena publicarlo aquí. Por ejemplo, para calcular la desviación absoluta de 10000 números aleatorios entre -100 y 100 a medida que llega cada elemento, mi algoritmo tarda menos de un segundo, mientras que el recálculo completo lleva más de 17 segundos (en mi máquina, variará por máquina y según datos de entrada). Sin embargo, debe mantener todo el vector en la memoria, lo que puede ser una restricción para algunos usos. El esquema del algoritmo es el siguiente:
- En lugar de tener un solo vector para almacenar mediciones pasadas, use tres colas de prioridad ordenadas (algo así como un montón mínimo / máximo). Estas tres listas dividen la entrada en tres: elementos mayores que la media, elementos menores que la media y elementos iguales a la media.
- (Casi) cada vez que agrega un elemento, la media cambia, por lo que debemos repartir. Lo crucial es la naturaleza ordenada de las particiones, lo que significa que en lugar de escanear cada elemento de la lista para repartir, solo necesitamos leer los elementos que estamos moviendo. Mientras que en el peor de los casos esto todavía requerirá
O(n)
operaciones de movimiento, para muchos casos de uso esto no es así.
- Usando una contabilidad inteligente, podemos asegurarnos de que la desviación se calcule correctamente en todo momento, al repartir y al agregar nuevos elementos.
Algún código de muestra, en python, está debajo. Tenga en cuenta que solo permite agregar elementos a la lista, no eliminarlos. Esto podría agregarse fácilmente, pero en el momento en que escribí esto no lo necesitaba. En lugar de implementar las colas de prioridad yo mismo, he usado la lista ordenada del excelente paquete blist de Daniel Stutzbach , que usa B + Tree s internamente.
Considere este código licenciado bajo la licencia MIT . No se ha optimizado ni pulido significativamente, pero ha funcionado para mí en el pasado. Nuevas versiones estarán disponibles aquí . Avíseme si tiene alguna pregunta o si encuentra algún error.
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 los síntomas persisten, consulta a tu médico.