この問題は、Zスコアまたは標準スコアを必要とします。これは、他の人々が言及しているように、履歴平均だけでなく、この履歴データの標準偏差も考慮に入れるため、平均を使用するよりも堅牢になります。
あなたの場合、Zスコアは次の式で計算されます。この場合、傾向はビュー/日などのレートになります。
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
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日間)でこの方法を使用できます。これにより、短期的な傾向がより顕著になり、処理時間を短縮できます。
1日あたりのビューの変化などの値にZスコアを使用して、1日あたりのビューの増加/減少の異常な値を見つけることもできます。これは、1日あたりのビューの勾配または導関数を使用するようなものです。
母集団の現在のサイズ、母集団の現在の合計、母集団の現在の合計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()
この方法を使用すると、ワークフローは次のようになります。トピック、タグ、またはページごとに、データベースで二乗された合計日数、ビューの合計、およびビューの合計について、浮動小数点フィールドを作成します。履歴データがある場合は、そのデータを使用してこれらのフィールドを初期化します。それ以外の場合はゼロに初期化します。毎日の終わりに、3つのデータベースフィールドに保存されている履歴データに対するその日のビュー数を使用してZスコアを計算します。XのZスコアが最も高いトピック、タグ、ページは、その日のXの「最も人気のあるトレンド」です。最後に、3つのフィールドのそれぞれを日の値で更新し、明日プロセスを繰り返します。
新しい追加
上記の通常のZスコアはデータの順序を考慮しないため、「1」または「9」の観測値のZスコアは、シーケンス[1、1、1、1]と同じ大きさになります。 、9、9、9、9、9]。明らかに傾向を見つけるために、最新のデータは古いデータよりも重みが大きくなる必要があるため、「1」の観測には「9」の観測よりも大きい等級スコアが必要です。これを達成するために、浮動平均Zスコアを提案します。この方法は統計的に確実であることが保証されているわけではありませんが、傾向の発見などに役立つはずです。標準zスコアと変動平均zスコアの主な違いは、変動平均を使用して平均母集団値と平均母集団値の2乗を計算することです。詳細については、コードを参照してください。
コード
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