Questo problema richiede un punteggio z o un punteggio standard, che terrà conto della media storica, come altri hanno già detto, ma anche della deviazione standard di questi dati storici, rendendola più robusta rispetto al semplice utilizzo della media.
Nel tuo caso, un punteggio z viene calcolato con la formula seguente, in cui la tendenza sarebbe una frequenza come visualizzazioni / giorno.
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
Quando viene utilizzato un punteggio z, più alto o più basso è il punteggio z, più anomala è la tendenza, quindi ad esempio se il punteggio z è altamente positivo, la tendenza aumenta in modo anormale, mentre se è altamente negativa diminuisce in modo anomalo . Quindi, una volta calcolato il punteggio z per tutte le tendenze candidate, i 10 punteggi z più alti si collegheranno ai punteggi z che aumentano in modo anomalo.
Per ulteriori informazioni, consultare Wikipedia , sui punteggi z.
Codice
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
Uscita campione
>>> 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
Appunti
È possibile utilizzare questo metodo con una finestra scorrevole (ovvero gli ultimi 30 giorni) se si desidera non tenere conto di molta cronologia, il che renderà le tendenze a breve termine più pronunciate e ridurrà i tempi di elaborazione.
È inoltre possibile utilizzare un punteggio z per valori come la modifica delle viste da un giorno al giorno successivo per individuare i valori anomali per aumentare / ridurre le viste al giorno. È come usare la pendenza o la derivata del grafico delle viste al giorno.
Se si tiene traccia delle dimensioni correnti della popolazione, del totale attuale della popolazione e del totale attuale di x ^ 2 della popolazione, non è necessario ricalcolare questi valori, aggiornarli e quindi è sufficiente mantenere questi valori per la cronologia, non per ciascun valore di dati. Il seguente codice lo dimostra.
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()
Utilizzando questo metodo il flusso di lavoro sarebbe il seguente. Per ogni argomento, tag o pagina creare un campo a virgola mobile, per il numero totale di giorni, la somma delle visualizzazioni e la somma delle visualizzazioni quadrate nel database. Se si dispone di dati storici, inizializzare questi campi utilizzando tali dati, altrimenti inizializzare a zero. Alla fine di ogni giornata, calcola il punteggio z utilizzando il numero di visualizzazioni del giorno rispetto ai dati storici memorizzati nei tre campi del database. Gli argomenti, i tag o le pagine con i punteggi Z più alti sono le X "tendenze più calde" della giornata. Infine, aggiorna ciascuno dei 3 campi con il valore del giorno e ripeti il processo domani.
Nuova aggiunta
I punteggi z normali come discusso sopra non tengono conto dell'ordine dei dati e quindi il punteggio z per un'osservazione di '1' o '9' avrebbe la stessa grandezza rispetto alla sequenza [1, 1, 1, 1 , 9, 9, 9, 9]. Ovviamente per la ricerca delle tendenze, i dati più recenti dovrebbero avere più peso rispetto ai dati più vecchi e quindi vogliamo che l'osservazione "1" abbia un punteggio di grandezza maggiore rispetto all'osservazione "9". Per raggiungere questo obiettivo, propongo un punteggio z mobile medio. Dovrebbe essere chiaro che questo metodo NON è garantito per essere statisticamente valido ma dovrebbe essere utile per trovare tendenze o simili. La differenza principale tra lo z-score standard e lo z-score medio mobile è l'uso di una media mobile per calcolare il valore medio della popolazione e il valore medio della popolazione al quadrato. Vedi il codice per i dettagli:
Codice
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 di esempio
>>> 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
Aggiornare
Come correttamente sottolineato da David Kemp, se viene richiesta una serie di valori costanti e quindi viene richiesto un punteggio z per un valore osservato che differisce dagli altri valori, il risultato dovrebbe probabilmente essere diverso da zero. In effetti il valore restituito dovrebbe essere infinito. Quindi ho cambiato questa linea,
if self.std() == 0: return 0
per:
if self.std() == 0: return (obs - self.avg) * float("infinity")
Questa modifica si riflette nel codice della soluzione fazscore. Se non si desidera gestire valori infiniti, una soluzione accettabile potrebbe essere invece quella di modificare la riga in:
if self.std() == 0: return obs - self.avg