나는 과거에 다음과 같은 접근 방식을 사용하여 절제 편차를 적당히 효율적으로 계산했습니다. (이 방법은 통계학자가 아닌 프로그래머 접근 방식이므로, 더 효율적일 수있는 shabbychef 와 같은 영리한 트릭이있을 수 있습니다 ).
경고 : 이것은 온라인 알고리즘이 아닙니다. O(n)
메모리 가 필요 합니다. 또한 O(n)
데이터 집합 의 경우 [1, -2, 4, -8, 16, -32, ...]
(예 : 전체 재계 산과 동일) 최악의 성능 을가집니다. [1]
그러나 많은 유스 케이스에서 여전히 잘 수행되므로 여기에 게시하는 것이 좋습니다. 예를 들어, 각 항목이 도착할 때 -100에서 100 사이의 난수 10000의 절대 이탈을 계산하려면 알고리즘이 1 초 미만이 걸리고 전체 재계 산은 17 초가 넘습니다 (내 기계에서는 기계마다 다름) 입력 데이터에 따라). 그러나 전체 벡터를 메모리에 유지해야하는데 일부 사용에는 제약이 될 수 있습니다. 알고리즘의 개요는 다음과 같습니다.
- 과거 측정 값을 저장하기 위해 단일 벡터를 사용하는 대신 3 개의 정렬 된 우선 순위 대기열 (최소 / 최대 힙과 같은 것)을 사용하십시오. 이 세 가지 목록은 입력을 세 개로 나눕니다 : 평균보다 큰 항목, 평균보다 작은 항목 및 평균과 같은 항목.
- (거의) 항목을 추가 할 때마다 평균이 변경되므로 다시 분할해야합니다. 중요한 것은 파티션의 정렬 특성입니다. 즉, 목록의 모든 항목을 다시 분할하기 위해 스캔하는 대신 이동중인 항목 만 읽어야합니다. 최악의 경우 여전히
O(n)
이동 작업 이 필요하지만 많은 유스 케이스의 경우에는 그렇지 않습니다.
- 영리한 부기를 사용하여 파티션을 다시 나누거나 새 항목을 추가 할 때 항상 이탈이 정확하게 계산되도록 할 수 있습니다.
파이썬에서 일부 샘플 코드는 다음과 같습니다. 항목을 목록에만 추가 할 수 있으며 제거 할 수는 없습니다. 이것은 쉽게 추가 될 수 있지만, 나는 이것을 쓸 당시에는 필요가 없었습니다. 우선 순위 대기열을 직접 구현하는 대신 Daniel Stutzbach의 우수한 blist 패키지 에서 정렬 된 목록 을 사용했습니다.이 패키지 는 내부적으로 B + Tree 를 사용 합니다.
이 코드가 MIT 라이센스에 따라 라이센스 된 것으로 간주하십시오 . 그것은 크게 최적화되거나 연마되지는 않았지만 과거에는 저에게 효과적이었습니다. 새 버전은 여기에서 사용할 수 있습니다 . 궁금한 점이 있으면 알려주거나 버그를 찾으십시오.
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] 증상이 지속되면 주치의에게 문의하십시오.