web-dev-qa-db-fra.com

Quel est le meilleur moyen de calculer des sujets ou des tags de tendances?

De nombreux sites proposent des statistiques telles que "Les sujets les plus chauds des dernières 24h". Par exemple, Topix.com le montre dans sa section "Nouvelles tendances". Là, vous pouvez voir les sujets qui ont le nombre de mentions croissant le plus rapidement.

Je veux aussi calculer un tel "bourdonnement" pour un sujet. Comment pourrais-je faire ça? L'algorithme doit pondérer les sujets toujours moins chauds. Les sujets qui normalement (presque) personne ne mentionne devraient être les plus chauds.

Google propose "Hot Trends", topix.com affiche "Hot Topics", fav.or.it indique "Keyword Trends" - tous ces services ont une chose en commun: ils ne vous montrent que les tendances à venir qui sont anormalement chaudes à l'heure actuelle.

Des termes comme "Britney Spears", "météo" ou "Paris Hilton" n'apparaîtront pas dans ces listes car ils sont toujours chauds et fréquents. Cet article appelle cela "le problème de Britney Spears".

Ma question: Comment pouvez-vous coder un algorithme ou utiliser un algorithme existant pour résoudre ce problème? Ayant une liste des mots-clés recherchés au cours des dernières 24h, l’algorithme devrait vous montrer les 10 (par exemple) les plus chauds.

Je sais, dans l'article ci-dessus, il y a une sorte d'algorithme mentionné. J'ai essayé de le coder en PHP mais je ne pense pas que ça va marcher. Il trouve juste la majorité, n'est-ce pas?

J'espère que vous pourrez m'aider (des exemples de codage seraient formidables).

170
caw

Ce problème appelle un score z ou un score standard, qui tiendra compte de la moyenne historique, comme le mentionnent d’autres personnes, mais également de l’écart type de ces données historiques, ce qui le rend plus robuste que la simple utilisation de la moyenne.

Dans votre cas, un z-score est calculé à l'aide de 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 hautement positif, la tendance est anormalement à la hausse, tandis que si elle est fortement négative, elle est anormalement à la baisse. . Donc, une fois que vous calculez le score z pour toutes les tendances du candidat, les 10 scores z les plus élevés se rapportent aux scores z les plus anormalement croissants.

S'il vous plaît voir Wikipedia pour plus d'informations, sur les z-scores.

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

Notes

  • Vous pouvez utiliser cette méthode avec une fenêtre glissante (c'est-à-dire les 30 derniers jours) si vous souhaitez ne pas trop tenir compte de l'historique, ce qui accentuera les tendances à court terme et réduira le temps de traitement.

  • Vous pouvez également utiliser un z-score pour les valeurs telles que le changement de vues d'un jour à l'autre pour localiser les valeurs anormales d'augmentation/de diminution des vues par jour. Cela revient à utiliser la pente ou le dérivé du graphique de 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, mais simplement de les mettre à jour. 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 indiquant 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 z-score en utilisant le nombre de vues du jour par rapport aux données historiques stockées dans les trois champs de la base de données. Les rubriques, balises ou pages présentant les X scores z 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.

Nouvelle addition

Les scores z normaux décrits ci-dessus ne tiennent pas compte de l'ordre des données; par conséquent, le score z pour une observation de '1' ou de '9' aurait la même valeur que 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 nous souhaitons donc que l'observation "1" ait un score de magnitude supérieur à celui de "9". Pour y parvenir, je propose un z-score 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 similaires. La principale différence entre le score z standard et le score z moyen flottant réside dans 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 fait remarquer à juste titre, si l’on demande une série de valeurs constantes puis un zscore pour une valeur observée qui diffère des autres valeurs, le résultat devrait être différent de zéro. En fait, la valeur renvoyée devrait ê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 gérer les valeurs infinies, une solution acceptable pourrait être de changer la ligne en:

if self.std() == 0: return obs - self.avg
96
Nixuz

Vous avez besoin d'un algorithme qui mesure la vélocité d'un sujet. En d'autres termes, si vous le représentez, vous voulez montrer ceux qui montent à une vitesse incroyable.

Il s’agit de la première dérivée de la ligne de tendance et il n’est pas difficile de l’incorporer comme facteur pondéré de votre calcul global.

Normaliser

Une technique que vous devez faire est de normaliser toutes vos données. Pour chaque sujet que vous suivez, conservez un filtre très bas qui définit la référence de ce sujet. Maintenant, chaque point de données entrant dans ce sujet doit être normalisé - soustrayez sa ligne de base et vous obtiendrez TOUS vos sujets proches de 0, avec des pointes au-dessus et au-dessous de la ligne. Vous voudrez peut-être plutôt diviser le signal par sa magnitude de base, ce qui ramènera le signal à environ 1,0 - cela non seulement aligne tous les signaux les uns sur les autres (normalise la ligne de base), mais normalise également les pointes. Un pic britney va être beaucoup plus grand que le pic de quelqu'un d'autre, mais cela ne signifie pas que vous devriez y prêter attention - le pic peut être très petit par rapport à sa ligne de base.

Dérive

Une fois que vous avez tout normalisé, déterminez la pente de chaque sujet. Prenez deux points consécutifs et mesurez la différence. Une différence positive est à la hausse, une différence négative à la baisse. Ensuite, vous pouvez comparer les différences normalisées et déterminer les sujets qui gagnent en popularité par rapport à d’autres sujets - chaque sujet étant redimensionné en fonction de sa "normale", l’ordre de grandeur pouvant être différent de celui des autres sujets.

C'est vraiment une première passe au problème. Vous devrez utiliser des techniques plus avancées (principalement une combinaison de ce qui précède avec d’autres algorithmes pondérés en fonction de vos besoins), mais cela devrait être suffisant pour vous aider à démarrer.

Concernant l'article

L'article traite des tendances par sujet, mais il ne s'agit pas de calculer ce qui est chaud ou non, mais de traiter la quantité énorme d'informations qu'un tel algorithme doit traiter dans des endroits tels que Lycos et Google. L'espace et le temps requis pour attribuer un compteur à chaque sujet et trouver le compteur de chaque sujet lorsqu'une recherche est effectuée sont énormes. Cet article traite des défis auxquels on est confronté lorsqu’on tente une telle tâche. Il mentionne l'effet Brittney, mais ne dit pas comment le surmonter.

Comme indique Nixuz , on parle également de Z ou Score standard .

92
Adam Davis

Chad Birch et Adam Davis ont raison de dire que vous devrez regarder en arrière pour établir une base de référence. Votre question, telle que libellée, suggère que vous ne souhaitez afficher que les données des dernières 24 heures, et que cela ne va pas très bien.

Une façon de donner de la mémoire à vos données sans avoir à interroger un grand nombre de données historiques consiste à utiliser un moyenne mobile exponentielle. L’avantage de cela est que vous pouvez la mettre à jour une fois par période, puis vider. toutes les anciennes données, il vous suffit donc de vous rappeler une valeur unique. Donc, si votre période est un jour, vous devez conserver un attribut "moyenne quotidienne" pour chaque sujet, ce que vous pouvez faire en:

a_n = a_(n-1)*b + c_n*(1-b)

a_n Est la moyenne mobile du jour n, b est une constante entre 0 et 1 (plus on se rapproche de 1, plus la mémoire est longue) et c_n Est le nombre des hits le jour n. La beauté est que si vous effectuez cette mise à jour à la fin de la journée n, vous pouvez vider c_n Et a_(n-1).

La seule mise en garde est qu'il sera initialement sensible à tout ce que vous choisirez pour votre valeur initiale de a.

MODIFIER

S'il est utile de visualiser cette approche, prenez n = 5, a_0 = 1 Et b = .9.

Disons que les nouvelles valeurs sont 5,0,0,1,4:

a_0 = 1
c_1 = 5 : a_1 = .9*1 + .1*5 = 1.4
c_2 = 0 : a_2 = .9*1.4 + .1*0 = 1.26
c_3 = 0 : a_3 = .9*1.26 + .1*0 = 1.134
c_4 = 1 : a_4 = .9*1.134 + .1*1 = 1.1206
c_5 = 4 : a_5 = .9*1.1206 + .1*5 = 1.40854

Ne ressemble-t-il pas beaucoup à une moyenne? Notez que la valeur est restée proche de 1, même si notre prochaine entrée était 5. Que se passe-t-il? Si vous développez le calcul, ce que vous obtenez:

a_n = (1-b)*c_n + (1-b)*b*c_(n-1) + (1-b)*b^2*c_(n-2) + ... + (leftover weight)*a_0

Qu'est-ce que je veux dire par poids restant? Bien, quelle que soit la moyenne, tous les poids doivent être égaux à 1. Si n est l'infini et que ... pourrait durer éternellement, tous les poids s'élèveraient à 1. Mais si n est relativement petit, vous obtenez une bonne quantité de poids sur l'entrée d'origine.

Si vous étudiez la formule ci-dessus, vous devez prendre conscience de certaines choses à propos de cet usage:

  1. Toutes les données contribuent à quelque chose à la moyenne pour toujours. Pratiquement, il y a un point où la contribution est vraiment, vraiment petite.
  2. Les valeurs récentes contribuent plus que les valeurs antérieures.
  3. Plus b est élevé, moins les nouvelles valeurs sont importantes et plus les anciennes valeurs comptent. Cependant, plus b est élevé, plus vous avez besoin de données pour diluer la valeur initiale de a.

Je pense que les deux premières caractéristiques correspondent exactement à ce que vous recherchez. Pour vous donner une idée de ce qui peut être simple à implémenter, voici une implémentation python (moins toute l’interaction avec la base de données)):

>>> class EMA(object):
...  def __init__(self, base, decay):
...   self.val = base
...   self.decay = decay
...   print self.val
...  def update(self, value):
...   self.val = self.val*self.decay + (1-self.decay)*value
...   print self.val
... 
>>> a = EMA(1, .9)
1
>>> a.update(10)
1.9
>>> a.update(10)
2.71
>>> a.update(10)
3.439
>>> a.update(10)
4.0951
>>> a.update(10)
4.68559
>>> a.update(10)
5.217031
>>> a.update(10)
5.6953279
>>> a.update(10)
6.12579511
>>> a.update(10)
6.513215599
>>> a.update(10)
6.8618940391
>>> a.update(10)
7.17570463519
17
David Berger

Généralement, le "buzz" est déterminé en utilisant une forme de mécanisme de décroissance exponentielle/log. Pour un aperçu de la façon dont Hacker News, Reddit et d’autres gèrent cela de manière simple, voir cet article .

Cela ne répond pas complètement aux choses qui sont toujours populaires. Ce que vous recherchez semble s'apparenter à la fonctionnalité " Hot Trends " de Google. Pour cela, vous pouvez diviser la valeur actuelle par une valeur historique, puis soustraire les valeurs inférieures au seuil de bruit.

7
Jeff Moser

Je me demandais s'il était possible d'utiliser une formule d'accélération physique normale dans un tel cas?

v2-v1/t or dv/dt

On peut considérer que v1 est le nombre de commentaires/votes/nombre de commentaires initial par heure et que v2 correspond à la "vitesse" actuelle par heure au cours des 24 dernières heures?

Cela ressemble plus à une question qu'à une réponse, mais il semble que cela puisse fonctionner. Tout contenu présentant la plus forte accélération sera le sujet à la mode ...

Je suis sûr que cela ne résoudra peut-être pas le problème de Britney Spears :-)

7
Sap

Je pense qu'ils sont essentiels Word que vous devez remarquer est "anormalement". Afin de déterminer si quelque chose est "anormal", vous devez savoir ce qui est normal. C'est-à-dire que vous allez avoir besoin de données historiques, que vous pouvez moyenner pour connaître le taux normal d'une requête particulière. Vous voudrez peut-être exclure les jours anormaux du calcul de la moyenne, mais là encore, il faudra déjà disposer de suffisamment de données pour savoir quels jours exclure.

À partir de là, vous devrez définir un seuil (ce qui nécessiterait une expérimentation, j'en suis sûr), et si quelque chose dépasse le seuil, disons 50% de recherches en plus que la normale, vous pouvez le considérer comme une "tendance". Ou, si vous voulez pouvoir trouver le "Top X Trendiest" comme vous l'avez mentionné, il vous suffit de commander les articles en fonction de leur distance (en pourcentage) par rapport à leur taux normal.

Par exemple, supposons que vos données historiques vous indiquent que Britney Spears obtient généralement 100 000 recherches et Paris Hilton 50 000. Si vous vivez tous les 10 000 recherches de plus que la normale, vous devriez considérer Paris comme "plus chaude" que Britney, car ses recherches ont augmenté de 20% de plus que la normale, alors que celle de Britney n'était que de 10%.

Mon dieu, je n'arrive pas à croire que je viens d'écrire un paragraphe comparant "l'actualité" de Britney Spears et de Paris Hilton. Qu'est-ce que tu m'as fait?

5
Chad Birch

probablement un simple gradient de fréquence de sujet fonctionnerait - grand gradient positif = croissance rapide en popularité.

le moyen le plus simple serait de supprimer le nombre de recherches chaque jour, de sorte que vous ayez quelque chose comme:

searches = [ 10, 7, 14, 8, 9, 12, 55, 104, 100 ]

et ensuite découvrir combien il a changé de jour en jour:

hot_factor = [ b-a for a, b in Zip(searches[:-1], searches[1:]) ]
# hot_factor is [ -3, 7, -6, 1, 3, 43, 49, -4 ]

et appliquez simplement une sorte de seuil pour que les jours où l'augmentation est supérieure à 50 soient considérés comme "chauds". vous pouvez aussi compliquer les choses si vous le souhaitez. au lieu de différence absolue, vous pouvez prendre la différence relative de sorte que passer de 100 à 150 est considéré comme chaud, mais pas de 10 000 à 1050. ou un gradient plus compliqué qui prend en compte les tendances sur plusieurs jours.

4
Autoplectic

J'avais travaillé sur un projet où mon objectif était de trouver des sujets de tendances sur Live Twitter Stream et de faire une analyse sentimentale sur les sujets de tendances (déterminer si le sujet de tendance était parlé positivement ou négativement). J'ai utilisé Storm pour gérer le flux Twitter.

J'ai publié mon rapport sous forme de blog: http://sayrohan.blogspot.com/2013/06/finding-trending-topics-and-trending.html

J'ai utilisé Total Count et Z-Score pour le classement.

L'approche que j'ai utilisée est un peu générique et, dans la section de discussion, j'ai expliqué comment nous pouvons étendre le système pour les applications autres que Twitter.

J'espère que l'information aide.

3
Rohan Karwa

Si vous vous contentez de consulter des tweets ou des messages d'état pour obtenir vos sujets, vous allez rencontrer beaucoup de bruit. Même si vous supprimez tous les mots vides. Une façon d'obtenir un meilleur sous-ensemble de candidats à un sujet consiste à se concentrer uniquement sur les tweets/messages partageant une URL et à obtenir les mots-clés du titre de ces pages Web. Et assurez-vous d’appliquer le marquage POS pour obtenir également les noms et les expressions nominales.

Les titres de pages Web sont généralement plus descriptifs et contiennent des mots décrivant le sujet de la page. De plus, le partage d'une page Web est généralement corrélé au partage de nouvelles (c.-à-d. Si une célébrité comme Michael Jackson meurt, vous allez obliger beaucoup de personnes à partager un article sur sa mort).

J'ai mené des expériences dans lesquelles je ne récupérais que les mots clés les plus utilisés dans les titres, puis le nombre total de ces mots clés dans tous les messages d'état, ce qui supprimait définitivement beaucoup de bruit. Si vous le faites de cette façon, vous n’avez pas besoin d’un algorithme complexe, vous n'avez qu’à commander les fréquences des mots-clés, et vous êtes à mi-chemin.

2
Henley Chiu

Vous pouvez utiliser les ratios log-vraisemblance pour comparer la date actuelle avec le dernier mois ou l'année. Ceci est statistiquement correct (étant donné que vos événements ne sont pas normalement distribués, ce qui est supposé de votre question).

Il suffit de trier tous vos termes par logLR et de choisir les dix premiers.

public static void main(String... args) {
    TermBag today = ...
    TermBag lastYear = ...
    for (String each: today.allTerms()) {
        System.out.println(logLikelihoodRatio(today, lastYear, each) + "\t" + each);
    }
} 

public static double logLikelihoodRatio(TermBag t1, TermBag t2, String term) {
    double k1 = t1.occurrences(term); 
    double k2 = t2.occurrences(term); 
    double n1 = t1.size(); 
    double n2 = t2.size(); 
    double p1 = k1 / n1;
    double p2 = k2 / n2;
    double p = (k1 + k2) / (n1 + n2);
    double logLR = 2*(logL(p1,k1,n1) + logL(p2,k2,n2) - logL(p,k1,n1) - logL(p,k2,n2));
    if (p1 < p2) logLR *= -1;
    return logLR;
}

private static double logL(double p, double k, double n) {
    return (k == 0 ? 0 : k * Math.log(p)) + ((n - k) == 0 ? 0 : (n - k) * Math.log(1 - p));
}

PS, un TermBag est une collection de mots non ordonnée. Pour chaque document, vous créez un sac de termes. Il suffit de compter les occurrences de mots. Ensuite, la méthode occurrences renvoie le nombre d’occurrences d’un mot donné et la méthode size renvoie le nombre total de mots. Il est préférable de normaliser les mots d’une manière ou d’une autre, généralement toLowerCase suffit. Bien entendu, dans les exemples ci-dessus, vous créeriez un document avec toutes les requêtes actuelles et un autre avec toutes les requêtes de l'année précédente.

2
akuhn

L'idée est de garder une trace de telles choses et de remarquer quand elles sautent de manière significative par rapport à leur propre base.

Ainsi, pour les requêtes dont le seuil est supérieur à un certain seuil, suivez chacune d'elles et, si elles changent en une valeur (disons presque le double) de sa valeur historique, il s'agit d'une nouvelle tendance en vogue.

0
Joshua