Dieses Problem erfordert einen Z-Score oder Standard-Score, der den historischen Durchschnitt berücksichtigt, wie andere bereits erwähnt haben, aber auch die Standardabweichung dieser historischen Daten, wodurch sie robuster sind als nur die Verwendung des Durchschnitts.
In Ihrem Fall wird ein Z-Score nach der folgenden Formel berechnet, wobei der Trend eine Rate wie Ansichten / Tag ist.
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
Wenn ein Z-Score verwendet wird, ist der Trend umso abnormaler, je höher oder niedriger der Z-Score ist. Wenn beispielsweise der Z-Score hoch positiv ist, steigt der Trend abnormal an, während er ungewöhnlich stark fällt, wenn er stark negativ ist . Sobald Sie den Z-Score für alle Kandidatentrends berechnet haben, beziehen sich die höchsten 10 Z-Scores auf die ungewöhnlich ansteigenden Z-Scores.
Weitere Informationen zu Z-Scores finden Sie in Wikipedia .
Code
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
Beispielausgabe
>>> 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
Anmerkungen
Sie können diese Methode mit einem Schiebefenster (dh den letzten 30 Tagen) verwenden, wenn Sie nicht zu viel Verlauf berücksichtigen möchten, wodurch kurzfristige Trends deutlicher werden und die Verarbeitungszeit verkürzt werden kann.
Sie können auch einen Z-Score für Werte wie die Änderung der Ansichten von einem Tag zum nächsten Tag verwenden, um die abnormalen Werte für das Erhöhen / Verringern der Ansichten pro Tag zu ermitteln. Dies entspricht der Verwendung der Steigung oder Ableitung der Ansichten pro Tag.
Wenn Sie die aktuelle Bevölkerungsgröße, die aktuelle Gesamtbevölkerung und die aktuelle Gesamtgröße von x ^ 2 der Bevölkerung verfolgen, müssen Sie diese Werte nicht neu berechnen, sondern nur aktualisieren und müssen daher nur Behalten Sie diese Werte für den Verlauf bei, nicht für jeden Datenwert. Der folgende Code demonstriert dies.
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()
Mit dieser Methode würde Ihr Arbeitsablauf wie folgt aussehen. Erstellen Sie für jedes Thema, Tag oder jede Seite ein Gleitkommafeld für die Gesamtzahl der Tage, die Summe der Ansichten und die Summe der Ansichten in Ihrer Datenbank. Wenn Sie historische Daten haben, initialisieren Sie diese Felder mit diesen Daten, andernfalls initialisieren Sie sie auf Null. Berechnen Sie am Ende eines jeden Tages den Z-Score anhand der Anzahl der Aufrufe des Tages anhand der in den drei Datenbankfeldern gespeicherten historischen Daten. Die Themen, Tags oder Seiten mit den höchsten X Z-Scores sind Ihre X "heißesten Trends" des Tages. Aktualisieren Sie abschließend jedes der 3 Felder mit dem Tageswert und wiederholen Sie den Vorgang morgen.
Neuer Zusatz
Normale Z-Scores, wie oben diskutiert, berücksichtigen nicht die Reihenfolge der Daten, und daher hätte der Z-Score für eine Beobachtung von '1' oder '9' gegenüber der Sequenz die gleiche Größe [1, 1, 1, 1 9, 9, 9, 9]. Offensichtlich sollten für die Trenderkennung die aktuellsten Daten mehr Gewicht haben als ältere Daten, und daher möchten wir, dass die '1'-Beobachtung eine größere Größenbewertung als die' 9'-Beobachtung hat. Um dies zu erreichen, schlage ich einen gleitenden durchschnittlichen Z-Score vor. Es sollte klar sein, dass diese Methode NICHT garantiert statistisch fundiert ist, sondern für die Trenderkennung oder ähnliches nützlich sein sollte. Der Hauptunterschied zwischen dem Standard-Z-Score und dem gleitenden Durchschnitt des Z-Scores besteht in der Verwendung eines gleitenden Durchschnitts zur Berechnung des durchschnittlichen Bevölkerungswerts und des durchschnittlichen quadratischen Bevölkerungswerts. Siehe Code für Details:
Code
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()
Beispiel 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
Aktualisieren
Wie David Kemp richtig hervorhob, sollte das Ergebnis wahrscheinlich ungleich Null sein, wenn eine Reihe konstanter Werte und dann ein Z-Wert für einen beobachteten Wert angegeben werden, der sich von den anderen Werten unterscheidet. Tatsächlich sollte der zurückgegebene Wert unendlich sein. Also habe ich diese Zeile geändert,
if self.std() == 0: return 0
zu:
if self.std() == 0: return (obs - self.avg) * float("infinity")
Diese Änderung spiegelt sich im Code der Fazscore-Lösung wider. Wenn man sich nicht mit unendlichen Werten befassen möchte, könnte eine akzeptable Lösung darin bestehen, stattdessen die Zeile zu ändern in:
if self.std() == 0: return obs - self.avg