Ten problem wymaga wyniku z-score lub standardowego, który weźmie pod uwagę średnią historyczną, jak wspomnieli inni ludzie, ale także standardowe odchylenie tych danych historycznych, co czyni ją bardziej niezawodną niż zwykłe stosowanie średniej.
W twoim przypadku wynik Z obliczany jest według następującego wzoru, w którym trendem będzie wskaźnik, taki jak liczba wyświetleń / dzień.
z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends]
Gdy stosuje się wynik Z, im wyższy lub niższy wynik Z, tym bardziej nienormalny jest trend, więc na przykład, jeśli wynik Z jest bardzo pozytywny, to trend rośnie nienormalnie, a jeśli jest wysoce ujemny, to nienormalnie spada . Tak więc po obliczeniu wyniku Z dla wszystkich trendów kandydujących najwyższe 10 punktów Z odniesie się do najbardziej nienormalnie rosnących wyników Z.
Więcej informacji na temat wyników Z można znaleźć na Wikipedii .
Kod
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
Przykładowe dane wyjściowe
>>> 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
Notatki
Możesz użyć tej metody z przesuwanym oknem (tj. Z ostatnich 30 dni), jeśli nie chcesz brać pod uwagę zbyt dużej historii, co sprawi, że trendy krótkoterminowe będą bardziej wyraźne i mogą skrócić czas przetwarzania.
Możesz również użyć wyniku Z dla wartości takich jak zmiana wyświetleń z jednego dnia na następny dzień, aby zlokalizować nieprawidłowe wartości zwiększania / zmniejszania wyświetleń dziennie. To jest jak użycie nachylenia lub pochodnej wykresów na dzień.
Jeśli śledzisz bieżącą wielkość populacji, bieżącą sumę populacji i bieżącą sumę x ^ 2 populacji, nie musisz ponownie obliczać tych wartości, tylko je aktualizować, a zatem musisz tylko zachowaj te wartości dla historii, nie dla każdej wartości danych. Poniższy kod to pokazuje.
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()
Dzięki tej metodzie przepływ pracy wyglądałby następująco. Dla każdego tematu, znacznika lub strony utwórz zmiennoprzecinkowe pole dla całkowitej liczby dni, sumy wyświetleń i sumy wyświetleń w bazie danych. Jeśli masz dane historyczne, zainicjuj te pola przy użyciu tych danych, w przeciwnym razie zainicjuj do zera. Na koniec każdego dnia oblicz wynik Z na podstawie liczby wyświetleń w danym dniu w porównaniu do danych historycznych przechowywanych w trzech polach bazy danych. Tematy, tagi lub strony z najwyższymi wynikami X-Z to Twoje „najgorętsze trendy” dnia. Na koniec zaktualizuj każde z 3 pól wartością dnia i powtórz proces jutro.
Nowy dodatek
Normalne wyniki Z, jak omówiono powyżej, nie uwzględniają kolejności danych, a zatem wynik Z dla obserwacji „1” lub „9” miałby taką samą wielkość w stosunku do sekwencji [1, 1, 1, 1 , 9, 9, 9, 9]. Oczywiście w celu znalezienia trendów najbardziej aktualne dane powinny mieć większą wagę niż starsze dane, dlatego chcemy, aby obserwacja „1” miała większy wynik jasności niż obserwacja „9”. Aby to osiągnąć, proponuję zmienną średnią z-score. Powinno być jasne, że ta metoda NIE jest gwarantowana pod względem statystycznym, ale powinna być użyteczna do znajdowania trendów lub podobnych. Główną różnicą między standardowym wynikiem Z i zmienną średnią oceną Z jest zastosowanie zmiennej ruchomej do obliczenia średniej wartości populacji i kwadratowej średniej wartości populacji. Szczegóły w kodzie:
Kod
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()
Próbka 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
Aktualizacja
Jak słusznie zauważył David Kemp, jeśli otrzyma się ciąg stałych wartości, a następnie zscore dla obserwowanej wartości, która różni się od innych wartości, wynik powinien być prawdopodobnie niezerowy. W rzeczywistości zwracana wartość powinna być nieskończonością. Więc zmieniłem tę linię,
if self.std() == 0: return 0
do:
if self.std() == 0: return (obs - self.avg) * float("infinity")
Ta zmiana znajduje odzwierciedlenie w kodzie rozwiązania fazscore. Jeśli nie chcesz zajmować się nieskończonymi wartościami, akceptowalnym rozwiązaniem może być zmiana linii na:
if self.std() == 0: return obs - self.avg