这个问题需要z分数或标准分数,就像其他人提到的那样,它将考虑历史平均值,而且还要考虑该历史数据的标准差,这使其比仅使用平均值更可靠。
在您的情况下,z得分由以下公式计算,其中趋势将是诸如观看次数/天之类的速率。
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
当使用z分数时,z分数越高或越低,趋势就越异常,因此,例如,如果z分数为高正,则趋势异常上升,而如果z分数为负,则趋势异常下降。 。因此,一旦您为所有候选趋势计算了z分数,最高的10个z分数将与异常增加的z分数相关。
有关z得分的更多信息,请参见Wikipedia。
码
from math import sqrt
def zscore(obs, pop):
# Size of population.
number = float(len(pop))
# Average population value.
avg = sum(pop) / number
# Standard deviation of population.
std = sqrt(sum(((c - avg) ** 2) for c in pop) / number)
# Zscore Calculation.
return (obs - avg) / std
样本输出
>>> zscore(12, [2, 4, 4, 4, 5, 5, 7, 9])
3.5
>>> zscore(20, [21, 22, 19, 18, 17, 22, 20, 20])
0.0739221270955
>>> zscore(20, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1])
1.00303599234
>>> zscore(2, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1])
-0.922793112954
>>> zscore(9, [1, 2, 0, 3, 1, 3, 1, 2, 9, 8, 7, 10, 9, 5, 2, 4, 1, 1, 0])
1.65291949506
笔记
如果您不想过多考虑历史记录,可以在滑动窗口(即最近30天)中使用此方法,这将使短期趋势更加明显,并可以减少处理时间。
您还可以使用z分数来表示值,例如从一天到第二天的视图变化,以定位异常值以每天增加/减少视图。这就像使用每天观看次数图表的斜率或导数一样。
如果您跟踪人口的当前大小,人口的当前总数以及人口的x ^ 2的当前总数,则无需重新计算这些值,只需更新它们即可,因此您只需要保留这些值作为历史记录,而不是每个数据值。以下代码演示了这一点。
from math import sqrt
class zscore:
def __init__(self, pop = []):
self.number = float(len(pop))
self.total = sum(pop)
self.sqrTotal = sum(x ** 2 for x in pop)
def update(self, value):
self.number += 1.0
self.total += value
self.sqrTotal += value ** 2
def avg(self):
return self.total / self.number
def std(self):
return sqrt((self.sqrTotal / self.number) - self.avg() ** 2)
def score(self, obs):
return (obs - self.avg()) / self.std()
使用这种方法,您的工作流程如下。对于每个主题,标签或页面,请为数据库中的总天数,视图总和和视图总和创建一个浮点字段。如果您有历史数据,请使用该数据初始化这些字段,否则初始化为零。在每天结束时,使用当天的观看次数对三个数据库字段中存储的历史数据计算z分数。X分数最高的主题,标签或页面是当天X的“最新趋势”。最后,用当天的值更新3个字段中的每个字段,明天再重复该过程。
新增加
如上所述的普通z分数未考虑数据的顺序,因此,观测“ 1”或“ 9”的z分数相对于序列[1、1、1、1、1, ,9,9,9,9]。显然,对于趋势发现而言,最新数据应比旧数据具有更大的权重,因此,我们希望“ 1”观测值比“ 9”观测值具有更大的强度得分。为了实现这一点,我提出了一个浮动平均z分数。应该清楚的是,这种方法不能保证在统计上是正确的,但是对于趋势查找或类似方法应该有用。标准z分数和浮动平均值z分数之间的主要区别是使用浮动平均值计算平均人口值和平均人口值的平方。有关详细信息,请参见代码:
码
class fazscore:
def __init__(self, decay, pop = []):
self.sqrAvg = self.avg = 0
# The rate at which the historic data's effect will diminish.
self.decay = decay
for x in pop: self.update(x)
def update(self, value):
# Set initial averages to the first value in the sequence.
if self.avg == 0 and self.sqrAvg == 0:
self.avg = float(value)
self.sqrAvg = float((value ** 2))
# Calculate the average of the rest of the values using a
# floating average.
else:
self.avg = self.avg * self.decay + value * (1 - self.decay)
self.sqrAvg = self.sqrAvg * self.decay + (value ** 2) * (1 - self.decay)
return self
def std(self):
# Somewhat ad-hoc standard deviation calculation.
return sqrt(self.sqrAvg - self.avg ** 2)
def score(self, obs):
if self.std() == 0: return (obs - self.avg) * float("infinity")
else: return (obs - self.avg) / self.std()
样品IO
>>> fazscore(0.8, [1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9]).score(1)
-1.67770595327
>>> fazscore(0.8, [1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9]).score(9)
0.596052006642
>>> fazscore(0.9, [2, 4, 4, 4, 5, 5, 7, 9]).score(12)
3.46442230724
>>> fazscore(0.9, [2, 4, 4, 4, 5, 5, 7, 9]).score(22)
7.7773245459
>>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20]).score(20)
-0.24633160155
>>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]).score(20)
1.1069362749
>>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]).score(2)
-0.786764452966
>>> fazscore(0.9, [1, 2, 0, 3, 1, 3, 1, 2, 9, 8, 7, 10, 9, 5, 2, 4, 1, 1, 0]).score(9)
1.82262469243
>>> fazscore(0.8, [40] * 200).score(1)
-inf
更新资料
正如大卫·肯普(David Kemp)正确指出的那样,如果给定一系列常数值,然后要求观测值的zscore与其他值不同,则结果可能应该为非零。实际上,返回的值应该是无穷大。所以我改变了这一行,
if self.std() == 0: return 0
至:
if self.std() == 0: return (obs - self.avg) * float("infinity")
此更改反映在fazscore解决方案代码中。如果不想处理无限值,可以接受的解决方案是改为将行更改为:
if self.std() == 0: return obs - self.avg