Eu usei a seguinte abordagem no passado para calcular o desvio de absolvição moderadamente eficiente (observe que essa é uma abordagem de programadores, não um estatístico, então, indubitavelmente, pode haver truques inteligentes como o shabbychef que podem ser mais eficientes).
AVISO: Este não é um algoritmo online. Isso requer O(n)
memória. Além disso, possui o pior desempenho possível O(n)
, para conjuntos de dados como [1, -2, 4, -8, 16, -32, ...]
(ou seja, o mesmo que o recálculo completo). [1]
No entanto, como ainda funciona bem em muitos casos de uso, pode valer a pena postar aqui. Por exemplo, para calcular o desvio absoluto de 10000 números aleatórios entre -100 e 100 à medida que cada item chega, meu algoritmo leva menos de um segundo, enquanto o recálculo completo leva mais de 17 segundos (na minha máquina, variará por máquina e de acordo com os dados de entrada). No entanto, é necessário manter o vetor inteiro na memória, o que pode ser uma restrição para alguns usos. O esboço do algoritmo é o seguinte:
- Em vez de ter um único vetor para armazenar medições passadas, use três filas de prioridade classificadas (algo como um heap mínimo / máximo). Essas três listas dividem a entrada em três: itens maiores que a média, itens menores que a média e itens iguais à média.
- (Quase) sempre que você adiciona um item, a média muda, por isso precisamos reparticionar. O crucial é a natureza classificada das partições, o que significa que, em vez de digitalizar todos os itens da lista para reparação, precisamos apenas ler os itens que estamos movendo. Embora, no pior caso, isso ainda exija
O(n)
operações de movimentação, para muitos casos de uso, não é o caso.
- Usando alguma contabilidade inteligente, podemos garantir que o desvio seja calculado corretamente o tempo todo, ao reparticionar e adicionar novos itens.
Alguns exemplos de código, em python, estão abaixo. Observe que apenas permite que itens sejam adicionados à lista, não removidos. Isso poderia ser facilmente adicionado, mas no momento em que escrevi isso não era necessário. Em vez de implementar as filas de prioridade, usei a lista classificada do excelente pacote de blist de Daniel Stutzbach , que usa internamente as árvores B + Tree .
Considere este código licenciado sob a licença MIT . Não foi significativamente otimizado ou polido, mas funcionou para mim no passado. Novas versões estarão disponíveis aqui . Deixe-me saber se você tiver alguma dúvida ou encontrar algum erro.
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 os sintomas persistirem, consulte seu médico.