Ce problème appelle un z-score ou score standard, qui prendra en compte la moyenne historique, comme d'autres l'ont mentionné, mais aussi l'écart type de ces données historiques, ce qui les rend plus robustes que la simple utilisation de la moyenne.
Dans votre cas, un z-score est calculé par la formule suivante, où la tendance serait un taux tel que vues / jour.
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
Lorsqu'un z-score est utilisé, plus le z-score est élevé ou bas, plus la tendance est anormale.Par exemple, si le z-score est très positif, la tendance augmente anormalement, tandis que si elle est très négative, elle diminue anormalement . Ainsi, une fois que vous calculez le z-score pour toutes les tendances candidates, les 10 z-scores les plus élevés seront liés aux z-scores les plus anormalement croissants.
Veuillez consulter Wikipedia pour plus d'informations sur les scores z.
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
Exemple de sortie
>>> 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
Remarques
Vous pouvez utiliser cette méthode avec une fenêtre glissante (c'est-à-dire les 30 derniers jours) si vous souhaitez ne pas prendre trop d'historique en compte, ce qui rendra les tendances à court terme plus prononcées et réduira le temps de traitement.
Vous pouvez également utiliser un score z pour des valeurs telles que la modification des vues d'un jour au lendemain pour localiser les valeurs anormales pour augmenter / diminuer les vues par jour. C'est comme utiliser la pente ou la dérivée du graphique des vues par jour.
Si vous gardez une trace de la taille actuelle de la population, du total actuel de la population et du total actuel de x ^ 2 de la population, vous n'avez pas besoin de recalculer ces valeurs, uniquement de les mettre à jour et il vous suffit donc de conservez ces valeurs pour l'historique, pas pour chaque valeur de données. Le code suivant illustre cela.
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()
En utilisant cette méthode, votre flux de travail serait le suivant. Pour chaque sujet, balise ou page, créez un champ à virgule flottante, pour le nombre total de jours, la somme des vues et la somme des vues au carré dans votre base de données. Si vous avez des données historiques, initialisez ces champs à l'aide de ces données, sinon initialisez à zéro. À la fin de chaque journée, calculez le score z en utilisant le nombre de vues de la journée par rapport aux données historiques stockées dans les trois champs de la base de données. Les sujets, balises ou pages avec les scores X les plus élevés sont vos X "tendances les plus chaudes" de la journée. Enfin, mettez à jour chacun des 3 champs avec la valeur du jour et répétez le processus demain.
Nouvel ajout
Les scores z normaux comme discuté ci-dessus ne prennent pas en compte l'ordre des données et par conséquent le score z pour une observation de «1» ou «9» aurait la même grandeur par rapport à la séquence [1, 1, 1, 1 , 9, 9, 9, 9]. Évidemment, pour la recherche de tendances, les données les plus récentes devraient avoir plus de poids que les données plus anciennes et, par conséquent, nous voulons que l'observation «1» ait un score de magnitude plus grand que l'observation «9». Pour y parvenir, je propose un score z moyen flottant. Il devrait être clair que cette méthode n'est PAS garantie d'être statistiquement valable, mais devrait être utile pour la recherche de tendances ou similaire. La principale différence entre le z-score standard et le z-score moyen flottant est l'utilisation d'une moyenne flottante pour calculer la valeur moyenne de la population et la valeur moyenne de la population au carré. Voir le code pour plus de détails:
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()
Exemple d'E / S
>>> 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
Mettre à jour
Comme David Kemp l'a correctement souligné, si une série de valeurs constantes est donnée, puis un zscore pour une valeur observée qui diffère des autres valeurs est demandé, le résultat devrait probablement être différent de zéro. En fait, la valeur renvoyée doit être l'infini. Alors j'ai changé cette ligne,
if self.std() == 0: return 0
à:
if self.std() == 0: return (obs - self.avg) * float("infinity")
Cette modification est reflétée dans le code de la solution fazscore. Si l'on ne veut pas traiter des valeurs infinies, une solution acceptable pourrait être de changer la ligne à la place:
if self.std() == 0: return obs - self.avg