ฉันเคยใช้วิธีการต่อไปนี้ในอดีตเพื่อคำนวณการเบี่ยงเบนการอภัยโทษในระดับปานกลางอย่างมีประสิทธิภาพ (โปรดทราบว่านี่เป็นวิธีการเขียนโปรแกรมไม่ใช่นักสถิติดังนั้นจึงอาจมีเทคนิคที่ฉลาดเช่นshabbychefซึ่งอาจมีประสิทธิภาพมากกว่า)
คำเตือน:นี่ไม่ใช่อัลกอริทึมออนไลน์ มันต้องใช้O(n)
หน่วยความจำ นอกจากนี้มันยังมีประสิทธิภาพของตัวพิมพ์ที่แย่ที่สุดO(n)
สำหรับชุดข้อมูลเช่น[1, -2, 4, -8, 16, -32, ...]
(เช่นเดียวกับการคำนวณใหม่ทั้งหมด) [1]
อย่างไรก็ตามเนื่องจากมันยังทำงานได้ดีในการใช้งานหลายกรณีมันอาจคุ้มค่าที่จะโพสต์ที่นี่ ตัวอย่างเช่นในการคำนวณค่าเบี่ยงเบนสัมบูรณ์ของ 10,000 ตัวเลขสุ่มระหว่าง -100 ถึง 100 เมื่อแต่ละรายการมาถึงอัลกอริทึมของฉันใช้เวลาน้อยกว่าหนึ่งวินาทีในขณะที่การคำนวณใหม่ทั้งหมดใช้เวลามากกว่า 17 วินาที (บนเครื่องของฉันจะแตกต่างกันต่อเครื่องและ ตามข้อมูลอินพุต) คุณจำเป็นต้องบำรุงรักษาเวกเตอร์ทั้งหมดในหน่วยความจำอย่างไรก็ตามซึ่งอาจเป็นข้อ จำกัด สำหรับการใช้งานบางอย่าง โครงร่างของอัลกอริทึมเป็นดังนี้:
- แทนที่จะมีเวกเตอร์เดียวเพื่อเก็บการวัดที่ผ่านมาให้ใช้ลำดับความสำคัญที่เรียงลำดับสามลำดับ (คล้ายกับ min / max heap) รายการทั้งสามนี้แบ่งพาร์ติชันอินพุตเป็นสาม: รายการที่สูงกว่าค่าเฉลี่ยรายการที่น้อยกว่าค่าเฉลี่ยและรายการเท่ากับค่าเฉลี่ย
- (เกือบ) ทุกครั้งที่คุณเพิ่มรายการการเปลี่ยนแปลงค่าเฉลี่ยดังนั้นเราจึงจำเป็นต้องแบ่งพาร์ติชันใหม่ สิ่งสำคัญคือลักษณะที่จัดเรียงของพาร์ติชันซึ่งหมายความว่าแทนที่จะสแกนทุกรายการในรายการเพื่อแบ่งส่วนเราเพียงแค่อ่านรายการที่เรากำลังเคลื่อนไหว ในขณะที่ในกรณีที่เลวร้ายที่สุดนี้จะยังคงต้อง
O(n)
ดำเนินการย้ายสำหรับกรณีการใช้งานจำนวนมากนี้ไม่เป็นเช่นนั้น
- ด้วยการทำบัญชีที่ชาญฉลาดเราสามารถตรวจสอบให้แน่ใจว่าค่าเบี่ยงเบนถูกคำนวณอย่างถูกต้องตลอดเวลาเมื่อแบ่งพาร์ติชันและเมื่อเพิ่มรายการใหม่
โค้ดตัวอย่างบางส่วนในไพ ธ อนอยู่ด้านล่าง โปรดทราบว่าจะอนุญาตให้เพิ่มรายการในรายการเท่านั้นไม่ได้ลบออก สามารถเพิ่มได้อย่างง่ายดาย แต่ในขณะที่ฉันเขียนสิ่งนี้ฉันไม่จำเป็นต้องใช้มัน แทนที่จะใช้คิวลำดับความสำคัญของตัวเองผมได้ใช้SortedListจากแดเนียล Stutzbach ยอดเยี่ยมของแพคเกจ BLISTซึ่งใช้B + ต้นไม้ s ภายใน
พิจารณารหัสนี้ได้รับใบอนุญาตภายใต้ใบอนุญาตเอ็มไอที มันไม่ได้รับการปรับปรุงหรือขัดอย่างมีนัยสำคัญ แต่ได้ทำงานให้ฉันในอดีต รุ่นใหม่จะสามารถใช้ได้ที่นี่ แจ้งให้เราทราบหากคุณมีข้อสงสัยหรือพบข้อบกพร่องใด ๆ
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] หากอาการยังคงอยู่ให้ไปพบแพทย์