Analyse de tweets par NLP
Analyse de tweets par NLP
1 Introduction
On applique des traitements NLP des modules spaCy et Top2vec a des tweets. Pour recuperer les tweets avec le module tweepy on a cree un compte developpeur Twitter, cf https://developer.twitter.com/fr/developer-terms/policy. Lors de cette creation, 4 cles vous sont fournies : 2 cles d’acces non confidentielles, analogues a des logins (consumer key et access key), et 2 cles secretes (consumer secret et access secret). On les a stockees dans un fichier ‘tokens.txt’.
2 Chargement des modules Python et des cles secretes Twitter
import numpy as np
import pandas as pd
import os
import datetime as dttm
import pickle
import timeit
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
from scipy.spatial.distance import squareform
import matplotlib.pyplot as plt
from pyvis.network import Network
import tweepy as tw
import spacy
from spacymoji import Emoji
from top2vec import Top2Vec
from matplotlib.backends.backend_pdf import PdfPages
"display.max_columns", None)
pd.set_option("max_colwidth", 180)
pd.set_option(
# on charge le modele francais de taille moyenne
= spacy.load("fr_core_news_md")
nlp # pour identifier les emojis avant tout autre traitement
= nlp.add_pipe("emoji", first=True) _
# le pipeline complete par le traitement des emoji
nlp.pipe_names
['emoji', 'tok2vec', 'morphologizer', 'parser', 'ner', 'attribute_ruler', 'lemmatizer']
# chemin vers les donnnees et les cles de Twitter
= 'donnees_twitter' working_dir
Les cles sont stockees dans le fichier comme suit.
CONS_KEY = cle1_sans_guillemets_autour
CONS_SECRET = cle2_sans_guillemets_autour
ACCESS_KEY = cle3_sans_guillemets_autour
ACCESS_SECRET = cle4_sans_guillemets_autour
On charge les cles.
with open(os.path.join(working_dir,'tokens.txt')) as f:
for line in f:
= line.replace('\n', '').split(' = ')
key, value = value os.environ[key]
On s’authentifie.
= tw.OAuthHandler(os.environ['CONS_KEY'], os.environ['CONS_SECRET'])
auth 'ACCESS_KEY'], os.environ['ACCESS_SECRET'])
auth.set_access_token(os.environ[= tw.API(auth, wait_on_rate_limit=True) api
3 Recuperation des tweets
3.1 Interrogation de l’API Twitter
On recupere les tweets de la derniere semaine en se limitant aux champs utiles, cf https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet.
Le mot de recherche et le volume voulu.
= "euro"
mot = "euro"
mot_fichier = 1500 nb_tweets
Interrogation de l’API, compter une minute.
# on enleve les retweets
= mot + "-filter:retweets"
search_words
= dttm.date.strftime(dttm.date.today() - dttm.timedelta(8), "%Y-%m-%d")
date_since
= tw.Cursor(api.search,
tweets = search_words,
q = "fr").items(nb_tweets)
lang
= timeit.default_timer()
tic = list(tweets)
tweets = timeit.default_timer()
toc - tic
toc
len(tweets)
= pd.json_normalize([r._json for r in tweets])
dtf
# on sauvegarde les champs utiles en csv
"text", "created_at", "user.screen_name", "in_reply_to_screen_name"]
dtf[[+ '.csv'), index = False) ].to_csv(os.path.join(working_dir, mot_fichier
3.2 Le dataframe des tweets
On recharge les tweets.
= pd.read_csv(os.path.join(working_dir, mot_fichier + '.csv'))
dtf
dtf.head() dtf.tail()
text \
0 @DidiDeschamps tu as la meilleure équipe au monde tu as droit à remporter cet euro
1 #Euro2020: Quelle erreur des Russes…. #Zobnin fait une passe en retrait mais directement dans les pieds de Poulsen!… https://t.co/1yafA8JUdA
2 Pour l'euro, je suis heureux qu'on soit déja qualifié #euro2021
3 Bonjour, je met en vente mon set volant / pedale et levier de vitesse g920 pour xbox et pc. Acheter il y a 1mois se… https://t.co/4EpfkpcKGK
4 L’Euro c’est le seul moment où on peut voir des grands inconnus marqués des buts de top player #RUSDEN
created_at user.screen_name in_reply_to_screen_name
0 Tue Jun 22 08:24:57 +0000 2021 FMK790 DidiDeschamps
1 Tue Jun 22 08:24:51 +0000 2021 20minutesOnline NaN
2 Tue Jun 22 08:24:48 +0000 2021 the_boys_situat NaN
3 Tue Jun 22 08:24:47 +0000 2021 TheFazzer NaN
4 Tue Jun 22 08:24:30 +0000 2021 bosterenzo NaN
text \
1495 Joachim Mununga : "Boyata, je pense que c’est le plus complémentaire pour jouer avec Vertonghen et Alderweireld" https://t.co/KO9dqI10pa
1496 Suarez qui égalise pendant que Benzema galère a mettre son premier à l’euro https://t.co/dfHBvWv3vY
1497 @LeRenar12908442 @CmoiCdo @BarbosaLXXV @ActuFoot_ @mohamedbouhafsi Je ne souhaite absolument pas la défaite de l’Ed… https://t.co/kLtrgd2bSc
1498 🇪🇸 @EURO2020 : ESPAGNE-POLOGNE 1-1 (GROUPE E) 🇵🇱\n\n➡️Retour sur le match !\n\n#GroupeE #BK #TeamBK #Soccer #Football… https://t.co/fDNouhMHmy
1499 Euro-2021 : la France qualifiée pour les huitièmes avant même son dernier match de poule https://t.co/IXJK8I9ZAt https://t.co/nT05ME2Wx3
created_at user.screen_name in_reply_to_screen_name
1495 Mon Jun 21 22:28:06 +0000 2021 RTBFsport NaN
1496 Mon Jun 21 22:28:02 +0000 2021 Brobbey01 NaN
1497 Mon Jun 21 22:27:51 +0000 2021 AlexDybala95 LeRenar12908442
1498 Mon Jun 21 22:27:41 +0000 2021 BillionKeys NaN
1499 Mon Jun 21 22:27:38 +0000 2021 F24videos NaN
Les 1500 tweets vont du lundi 21 juin 22h 27mn au mardi 22 8h 25mn en heure UTC, soit en heure francaise le mardi 22 entre 00h 27mn et 10h 25mn.
min()
dtf.created_at.max()
dtf.created_at.
= pd.to_datetime("2021/06/" + dtf.created_at.str.slice(8,19),
dtf.created_at format = "%Y/%m/%d %H:%M:%S") + dttm.timedelta(hours = 2)
'Mon Jun 21 22:27:38 +0000 2021'
'Tue Jun 22 08:24:57 +0000 2021'
4 Attributs d’un texte
On utilise deux des tweets pour illustrer les differents traitements NLP. Chaque tweet est transforme en la liste de ses composantes (= mots, ponctuations) et le modele qu’on a charge au debut de ce document permet de predire la nature des composantes (nom, verbe), les dependances syntaxiques, les entites nommees …
= nlp(dtf.text.iloc[5])
doc0 = nlp(dtf.text.iloc[68])
doc
doc0 doc
Euro: sept nouveaux qualifiés pour les huitièmes, dont la France․.. sans jouer! https://t.co/yZTcg4wtk1 via @nouvelliste
La Belgique va gagner l’Euro!! Regardez la puissance du MOUVEMENT HISTORIQUE 🏴☠️🇧🇪
Quelques attributs, cf https://spacy.io/usage/rule-based-matching.
for token in doc],
pd.DataFrame([[token.text, token._.is_emoji, token.is_punct, token.is_stop, token.shape_] = ['texte', 'emoji', 'ponctuation', 'stopword', 'forme'])
columns
for token in doc],
pd.DataFrame([[token.i, token.text, token.pos_, token.lemma_, token.dep_, token.head.text] = ['index', 'texte', 'nature', 'lemme', 'fontion', 'dependance'])
columns
for token in doc0],
pd.DataFrame([[token.text, token.like_url, token.is_alpha, token.like_num, token.is_space] = ['texte', 'url', 'alphanum', 'numerique', 'espace']) columns
texte emoji ponctuation stopword forme
0 La False False True Xx
1 Belgique False False False Xxxxx
2 va False False True xx
3 gagner False False False xxxx
4 l’ False False True x’
5 Euro False False False Xxxx
6 ! False True False !
7 ! False True False !
8 Regardez False False False Xxxxx
9 la False False True xx
10 puissance False False False xxxx
11 du False False True xx
12 MOUVEMENT False False False XXXX
13 HISTORIQUE False False False XXXX
14 🏴☠️ True False False 🏴☠️
15 🇧🇪 True False False 🇧🇪
index texte nature lemme fontion dependance
0 0 La DET le det Belgique
1 1 Belgique PROPN Belgique nsubj va
2 2 va VERB aller ROOT va
3 3 gagner VERB gagner xcomp va
4 4 l’ DET l’ det Euro
5 5 Euro NOUN euro obj gagner
6 6 ! PUNCT ! punct va
7 7 ! PUNCT ! punct va
8 8 Regardez ADP regardez ROOT Regardez
9 9 la DET le det puissance
10 10 puissance NOUN puissance obj Regardez
11 11 du ADP de case MOUVEMENT
12 12 MOUVEMENT NOUN mouvement nmod puissance
13 13 HISTORIQUE ADJ historique amod MOUVEMENT
14 14 🏴☠️ NOUN 🏴☠️ punct 🇧🇪
15 15 🇧🇪 NOUN 🇧🇪 ROOT 🇧🇪
texte url alphanum numerique espace
0 Euro False True False False
1 : False False False False
2 sept False True True False
3 nouveaux False True False False
4 qualifiés False True False False
5 pour False True False False
6 les False True False False
7 huitièmes False True False False
8 , False False False False
9 dont False True False False
10 la False True False False
11 France․ False False False False
12 .. False False False False
13 sans False True False False
14 jouer False True False False
15 ! False False False False
16 https://t.co/yZTcg4wtk1 True False False False
17 via False True False False
18 @nouvelliste False False False False
Pour des explications de certaines abreviations.
"nsubj")
spacy.explain("nmod")
spacy.explain("case")
spacy.explain("cop") spacy.explain(
'nominal subject'
'modifier of nominal'
'case marking'
'copula'
Les entites reconnues.
for ent in doc.ents], columns = ['entite', 'nature']) pd.DataFrame([[ent.text, ent.label_]
entite nature
0 Belgique LOC
1 l’Euro!! MISC
2 MOUVEMENT HISTORIQUE MISC
"LOC")
spacy.explain("PER")
spacy.explain("MISC") spacy.explain(
'Non-GPE locations, mountain ranges, bodies of water'
'Named person or family.'
'Miscellaneous entities, e.g. events, nationalities, products or works of art'
5 Similarites entre tweets
5.1 Matrice de distance
Calcul des distances entre deux tweets par la methode similarity :
- Chaque composante d’un tweet est transformee par un algorithme de type word2vec en un vecteur d’un espace de grande dimension (par exemple 300)
- Chaque tweet est transforme en le centre de gravite du nuage des vecteurs des composantes du tweet
- La distance “cosinus de l’angle” est appliquee aux 2 centres de gravite
= timeit.default_timer()
tic = [nlp(dtf.text[k]) for k in range(nb_tweets)]
docs = timeit.default_timer()
toc - tic
toc
= np.diag(np.ones(nb_tweets))
matrice
= timeit.default_timer()
tic for i in range(nb_tweets):
= docs[i]
x for j in range(i+1, nb_tweets):
= x.similarity(docs[j])
matrice[i,j] for j in range(i):
= matrice[j,i]
matrice[i,j] = timeit.default_timer()
toc - tic toc
20.120643400000006
10.2007476
Un exemple de vecteur.
8]
doc[8].vector doc[
Regardez
array([ 0.79321 , 0.95185 , -1.1069 , -0.45713 , 0.62773 ,
1.946 , 0.46065 , -1.6571 , -0.23949 , -0.18132 ,
-0.22801 , -0.22944 , 0.12723 , 0.088278 , 1.2252 ,
-1.0708 , 0.46161 , -1.1268 , -0.63179 , -0.36074 ,
-0.38548 , 0.24211 , 0.4949 , -0.17168 , 0.39266 ,
-0.71156 , -0.5419 , 1.0834 , 0.75061 , 0.52643 ,
0.11928 , 0.1293 , -0.23025 , -1.3616 , -0.29727 ,
1.2538 , -0.23961 , 1.1043 , 0.95408 , -1.3896 ,
-1.5353 , 0.0088851, 0.15345 , -0.47984 , 0.8927 ,
1.0114 , -2.0591 , -0.032777 , 0.51549 , 0.93054 ,
-0.9798 , -2.3503 , 0.13563 , -1.3584 , -1.1956 ,
0.20982 , -2.1452 , 0.42829 , -0.66647 , 0.069482 ,
-0.5313 , 1.7977 , -0.53366 , -0.33859 , -2.0559 ,
1.5184 , 0.30163 , -0.61943 , 0.46154 , 0.28295 ,
-0.81895 , 0.28327 , -0.020459 , -1.094 , 0.70109 ,
0.89367 , -0.12854 , -2.5037 , -0.57653 , 0.94503 ,
0.12362 , 0.99971 , 0.60756 , 2.651 , -0.21889 ,
-0.27665 , 0.89356 , -0.25228 , 0.13447 , 1.555 ,
-2.9365 , -0.32621 , 1.0715 , -0.60142 , -2.0295 ,
1.1299 , 0.096798 , 0.044715 , 0.53194 , -1.2044 ,
0.053932 , 0.27724 , 1.3245 , -0.35753 , 0.33475 ,
0.082249 , 0.42002 , -0.23329 , -0.043796 , 1.5891 ,
1.0969 , -1.1258 , -1.1286 , 1.8783 , 0.66122 ,
-0.60732 , 0.062721 , -0.9256 , -1.7266 , 0.65355 ,
-0.50595 , -1.7203 , 0.23784 , -0.45568 , -2.3789 ,
2.541 , 0.539 , 0.99548 , -1.1909 , -0.35436 ,
-0.066093 , 1.0007 , -0.23881 , -0.030909 , 0.18711 ,
0.12907 , 0.87953 , 0.50748 , 1.8736 , -0.40152 ,
-0.17476 , -1.0705 , -2.453 , 0.8858 , -0.23732 ,
0.42749 , 0.085325 , 1.51 , 1.5668 , -0.16242 ,
-0.011907 , 0.11196 , 0.52844 , -0.26746 , 0.19964 ,
-0.81189 , 0.75148 , 1.2699 , 0.59237 , -1.1861 ,
-0.96782 , 0.85239 , -0.16554 , 0.075135 , -0.25966 ,
0.33738 , 0.18222 , -1.3915 , 0.96295 , -0.7081 ,
1.6575 , -0.36899 , 0.54342 , -0.96251 , 1.5675 ,
1.1882 , 0.77105 , 0.1901 , 0.065428 , 1.3529 ,
-0.50661 , 1.5316 , 2.0397 , 0.86335 , -1.1311 ,
2.2651 , -0.14806 , 0.3397 , 2.3393 , 0.13634 ,
1.1003 , -0.48229 , 0.032001 , -2.0498 , -0.24062 ,
0.24028 , -0.60262 , 0.40457 , -0.7555 , -0.90226 ,
1.7338 , 0.28738 , 0.29228 , 1.04 , -2.517 ,
0.18479 , 0.053454 , -1.6878 , 0.66571 , -2.2276 ,
-0.91446 , 0.699 , 2.5515 , -1.2558 , -2.2668 ,
-0.77857 , -0.45876 , 0.45907 , 1.2054 , -1.0605 ,
-0.02248 , -0.6608 , 1.4666 , -1.3927 , -1.3929 ,
-0.64604 , 1.2649 , -0.81053 , 0.61458 , -0.12841 ,
-0.11389 , 0.14234 , 0.33481 , 1.1168 , 0.64395 ,
-1.0912 , -1.4982 , -0.41059 , 2.3496 , -2.5213 ,
-2.7719 , 1.1356 , -0.031763 , -1.0589 , -0.081639 ,
-0.37828 , 0.45378 , 0.92991 , 0.27377 , 0.050234 ,
1.6672 , -0.95015 , 0.29714 , -1.5286 , 1.2891 ,
-0.1645 , -1.3745 , 0.74126 , -1.2292 , 0.37063 ,
0.095942 , 0.7153 , -0.90496 , -0.76003 , 0.18375 ,
1.4571 , -0.53981 , -0.29554 , 0.67795 , 1.0498 ,
0.47981 , 0.7117 , -0.50379 , -1.2355 , 0.94785 ,
-0.91392 , 0.23728 , 0.64474 , -0.61146 , -0.63721 ,
1.8344 , 1.2227 , -0.46766 , 0.84134 , 0.70637 ,
-0.29943 , 0.076421 , -1.3627 , -0.12433 , -0.11689 ,
-0.32179 , 0.89101 , -0.51407 , -1.5856 , -0.80922 ,
-1.6279 , -0.21633 , 1.8286 , 0.83546 , -0.52643 ],
dtype=float32)
Tweets similaires ou tres differents.
max()
matrice.print("----------------------")
= np.unravel_index(matrice.argmax(), matrice.shape)
a, b
docs[a]print("----------------------")
docs[b]print("----------------------")
docs[a].similarity(docs[b])
# quelquefois des petits pbs d'arrondis qui font planter la suite
> 1] = 1 matrice[matrice
1.0000000800123225
----------------------
Le prix a augmenté 😁
Valeur actuelle du Bitcoin: €27508.54 à 09:47
Variation 📈 (+0.07%)
----------------------
Le prix a augmenté 😁
Valeur actuelle du Bitcoin: €27488.61 à 09:32
Variation 📈 (+0.36%)
----------------------
1.0000000800123225
min()
matrice.print("----------------------")
= np.unravel_index(matrice.argmin(), matrice.shape)
a, b
docs[a]print("----------------------")
docs[b]print("----------------------")
docs[a].similarity(docs[b])
-0.5968111558796538
----------------------
| #LastminuteAmeland #Ameland
| chalet Chalet T46
| 399 Euro
| vr 02/07 - vr 09/07
| https://t.co/YxGn4DIzDj https://t.co/188R6Sejtz
----------------------
@Manuctn10 Surtout qu’à un moment c’est pas comme si on avait fait un Euro enormissime incroyable on a surtout bien serré les fesses
----------------------
-0.5968111558796538
5.2 CAH
On choisit de separer les twets en 3 segments.
= linkage(squareform(1 - matrice, force = 'tovector', checks = False), 'ward')
link
# dendrogramme
# coupures a 3 clusters
=(25, 10))
plt.figure(figsize= dendrogram(link)
_
plt.show()
= 3
k = pd.Series(fcluster(link, k, criterion = 'maxclust'))
clusters "cluster"] = list(clusters)
dtf[
# taille des clusters
clusters.value_counts()
<Figure size 2500x1000 with 0 Axes>
3 703
1 632
2 165
dtype: int64
5.3 Description des segments
La repartition temporelle des tweets semble assez proche entre les differents segments.
= dtf.cluster, xlabelsize = 4, xrot = 0, layout = (4,1), bins = 20);
dtf.created_at.hist(by plt.show()
Mots-cles les plus frequents dans chaque cluster apres suppression des termes inutiles.
= pd.concat([pd.DataFrame([[ligne, clusters[ligne], token.lemma_, token._.is_emoji, token.like_url, token.is_punct, token.is_stop, token.is_space]
dtf_stats for token in docs[ligne] if (not token.is_stop and not token.is_punct and not token.like_url and not token._.is_emoji and not token.is_space)],
= ['tweet_id', 'cluster', 'lemme', 'emoji', 'url', 'ponctuation', 'stopword', 'espace']) for ligne in range(nb_tweets)])
columns
# on enleve les fils
"fil"] = dtf_stats.lemme.str.startswith("@")
dtf_stats[= dtf_stats[~dtf_stats["fil"]]
dtf_stats = dtf_stats[['tweet_id', 'cluster', 'lemme']]
dtf_stats
# dedup
= True)
dtf_stats.drop_duplicates(inplace
= dtf_stats.groupby(["cluster", "lemme"]).size().reset_index(name = 'freq').sort_values(['cluster', 'freq'], ascending = False)
top10 "cluster").head(10) top10.groupby(
cluster lemme freq
3404 3 euro 329
2744 3 Euro 194
2762 3 France 112
3450 3 finale 105
2602 3 2021 100
4000 3 qualifié 97
3560 3 huitième 96
2600 3 2020 75
3729 3 match 66
2670 3 Belgique 65
2287 2 euro 73
2536 2 valeur 41
2054 2 Euro 40
2021 2 Bitcoin 39
2167 2 actuel 39
2451 2 prix 39
2540 2 variation 39
1922 2 2020 31
2048 2 EURO 21
2185 2 augmenter 20
753 1 euro 402
172 1 Euro 47
1099 1 match 47
1767 1 y 43
849 1 gagner 32
1799 1 équipe 32
182 1 France 30
780 1 falloir 29
779 1 faire 28
1030 1 l 26
On obtient une premiere vision des segments
- cluster 1 de 632 tweets : il semble concerner principalement l’equipe de France
- cluster 2 de 165 tweets : il porte sur la monnaie “euro”, pas la competition “Euro”
- cluster 3 de 703 tweets : il est question de plusieurs equipes, de differentes annees et de plusieurs phases de la competition
Pour mieux interpreter les segments on tire aleatoirement 10 tweets de chaque segment.
# tirage aleatoire de chaque cluster
= dtf[["cluster", "user.screen_name", "text"]]
extraits
= extraits.groupby("cluster").sample(10, random_state = 1234)
extraits_tweets
"cluster " + extraits_tweets.cluster.astype(str) + " - user " + extraits_tweets[
'user.screen_name'] + " - " + extraits_tweets.text
1338 cluster 1 - user jisunglazed - et dire que l'année prochaine on pourra plus parler d'animes en cours d'anglais parce qu'elle arrête la section euro... déjà envie de crever
597 cluster 1 - user alyly_222 - J'entend un français dire que la Belgique est nulle parce qu'elle a pas gagné la Coupe du monde je lui rappelle qu… https://t.co/zJIxWhrrSO
1105 cluster 1 - user constantscln - @ledoumbe J’ai juré le duel Benzema-Giroud c’est plus important à leurs yeux qu’un titre à l’Euro, ils veulent une… https://t.co/GOyCEvD7uB
855 cluster 1 - user 20_times_I_Said - @CremDyl @FrereAmelie @ElevenSportsBEf ces phase de poule ne ressemblent à rien. pour faire ca autant qualifier plu… https://t.co/DENSmvNJB4
1017 cluster 1 - user Akrapo_ - français: je le parle déjà\ntechnologie: j'ai déjà un ordi\nhistoire: ils sont tous morts\ngéographie: j'ai un GPS\n mu… https://t.co/UeGG2ohr08
446 cluster 1 - user Elow__06340 - @leperedeCherrad @Yoyoslim1 Offensivement on a vu personne depuis le début de l’euro. Facile de tomber QUE sur Benz… https://t.co/iOmCbmA5Vy
1229 cluster 1 - user Joao92_Hds - Jsuis sur les champs la pendant 2sec j'ai cru l'Algérie avais gagner l'euro
1456 cluster 1 - user JuniorRvp - @midouly Ahh quand tu me parles de profil on peut être d’accord mais côté niveau il n’y a jamais eu débat. La seule… https://t.co/TL574i2Evz
437 cluster 1 - user antoinevidu - @JulienDenoel @adrienrenkin 95% de centres ratés alors que c'est le seul truc offensif qu'il essayait, c'est pas fo… https://t.co/L4FKxKxfID
616 cluster 1 - user CaptainArmorica - @kamastaw C'est vraiment un bel euro au contraire je trouve. Et même les plus petites nations sont intéressantes pa… https://t.co/7N5woYGizE
655 cluster 2 - user Nabilaouais - 0€ euro d’amende pendant confinement et couvre feu depuis ca existe ! Formidable hmdl
331 cluster 2 - user Network_Easy - EURO 2020 RUSSIE-DANEMARK Groupe B: Hello les amis me voila lance dans ma nouvelle aventure dans PES 21 nous voila… https://t.co/4HTJtmWklH
1418 cluster 2 - user AssohNicaise - 🥳🧐NE PANIQUER PLUS\n Logiciel : N-SOS , vient a votre secoure et vous sort de la…\n B N : les appareils ne… https://t.co/NL234f2Bxk
1219 cluster 2 - user euro_btc - Le prix a augmenté 😁\nValeur actuelle du Bitcoin: €26734.3 à 01:30\nVariation 📈 (+0.54%)
676 cluster 2 - user d3_ghie - Final de l'euro \nFrance 🇲🇫 vs L'Allemagne 🇩🇪\n#France #Allemagne #EURO2020
733 cluster 2 - user DussolAlexis - #Thaïlande | Un #lion prédit les #matchs de l'Euro du week-end | La Presse https://t.co/U3Oo2w3dvu
753 cluster 2 - user giuloc - Euro 2020 - La belle leçon d’italien du professeur Mancini - 20 minutes https://t.co/bGldIBm79E
708 cluster 2 - user MrGyzmo84 - @ID7Kun Si on reprend le contexte : \n\n- L'Euro se déroule dans plusieurs pays (c'est nul !!).\n- La chaleur.\n- La pa… https://t.co/5HEBLjSrdI
118 cluster 2 - user monnaiecollect1 - VALEUR 2 EURO ALLEMAGNE 2010 D 6 300 000 ? https://t.co/NWA5BUhhDM via @YouTube
640 cluster 2 - user monnaiecollect1 - POURQUOI cette Pièce de 1 EURO FRANCE 2001 vaut plus de 700€ ? https://t.co/jjOzESthWt via @YouTube
668 cluster 3 - user Jordydu59120 - Imaginez Lille gagne l’euro 🧐😂 https://t.co/L7LxXF0lIG
628 cluster 3 - user fhardes - @GegeStyle On a besoin de sommeil entre euro et nba. Première nuit de 8h!!
63 cluster 3 - user Thb_Caillet - Vegedream à la conquête de l'Europe! "Ramenez la coupe à la maison" dans le top 50 Shazam monde grâce à son émergen… https://t.co/eEXuwr2YwK
147 cluster 3 - user actudesbleus - K. Coman : Oh non Dembouz ne peut plus faire l’euro 😢😞 https://t.co/367ncsiFin
1483 cluster 3 - user LoopHaiti - Près de la moitié des places en huitièmes de finale ont été attribuées lundi, avec sept sélections qualifiées en un… https://t.co/MaX1pclsid
630 cluster 3 - user franceinfo - Euro 2021 : le difficile travail de mémoire du onze d’or hongrois, mythe déchu du football mondial… https://t.co/f3NmrU5k4h
370 cluster 3 - user sports_ouest - Euro 2021. Un hommage à Philousports avant France-Portugal ? https://t.co/uMPepLLdSX
1245 cluster 3 - user ImNotReda - @IssaLhd full maquillage aussi fake que la turquie a l’euro
40 cluster 3 - user 24matins_sport - Euro : l’Angleterre vise la tête, la Croatie joue la sienne https://t.co/s8oX4pOFxX #Foot #Croatie #Euro2020 https://t.co/daWtZDPYoA
874 cluster 3 - user MarieMarilou_ - Jeremy Doku sur son petit nuage après son premier match à l'Euro 2020: "Une bonne expérience pour moi" - RTL sport… https://t.co/inTZlRDbJu
dtype: object
On voit des differences plus precises entre segments
- cluster 1 : des tweets ecrits sur l’Euro par des particuliers, avec beaucoup de “je”
- cluster 2 : un melande de tweets sur l’Euro et de tweets qui parle d’argent (amende, prix, collectionneur de pieces)
- cluster 3 : des tweets sur l’Euro rediges par des journalistes (RTL sport, actudesbleus, franceinfo, sports_ouest, …) au ton plus neutre, “Euro 2021” et “Euro 2020” ressortent deux fois chacun
On a ainsi obtenu une segmentation de bonne qualite alors que la situation n’etait pas simple
- les textes sont tronques a 140 caracteres par l’API de Twitter
- les tweets peuvent etre tres mal orthographies : “Jsuis sur les champs la pendant 2sec j’ai cru …”
- on applique la methode de Ward, qui attend une metrique euclidienne, a des cosinus entre centres de gravite d’embeddings par reseaux de neurones !
- on n’a meme pas applique de pretraitement comme la suppression des mots inutiles
- les auteurs du module spaCy indiquent que la similarite entre documents est un sujet difficile en NLP : https://spacy.io/usage/linguistic-features#similarity-expectations
6 Clustering des tweets avec Top2vec
Cet algorithme effectue toutes les taches necessaires a un clustering sans aucun parametre a ajuster par l’utilisateur. Dans le cas des tweets le traitement s’avere malheureusement decevant. Pretraiter les tweets en enlevant les stopwords, url, et emoji et en lemmatisant les termes restants ameliore partiellement la situation.
Attention car l’algorithme n’est pas deterministe, cf https://github.com/ddangelov/Top2Vec/issues/86. Lancer plusieurs fois la commande Top2Vec donnera des nombres de clusters (=“topics”) differents ! Il faut donc sauvegarder le modele sur disque une fois cree.
Les tweets sans et avec pretraitement.
= list(dtf.text)
texte_brut
# ss les stopwords
= [" ".join([str(token.lemma_) for token in docs[ligne] if (
texte_nettoye not token.is_stop and not token.is_punct and not token.like_url and not token._.is_emoji and not token.is_space)]) for ligne in range(nb_tweets)]
0]
texte_brut[0] texte_nettoye[
'@DidiDeschamps tu as la meilleure équipe au monde tu as droit à remporter cet euro'
'@didideschamp meilleur équipe monde droit remporter euro'
Les deux modeles.
= Top2Vec(texte_brut)
modele_brut = Top2Vec(texte_nettoye) modele_nettoye
2021-08-03 17:44:40,535 - top2vec - INFO - Pre-processing documents for training
2021-08-03 17:44:40,638 - top2vec - INFO - Creating joint document/word embedding
2021-08-03 17:44:43,769 - top2vec - INFO - Creating lower dimension embedding of documents
2021-08-03 17:44:58,062 - top2vec - INFO - Finding dense areas of documents
2021-08-03 17:44:58,177 - top2vec - INFO - Finding topics
2021-08-03 17:44:58,195 - top2vec - INFO - Pre-processing documents for training
2021-08-03 17:44:58,256 - top2vec - INFO - Creating joint document/word embedding
2021-08-03 17:45:00,748 - top2vec - INFO - Creating lower dimension embedding of documents
2021-08-03 17:45:07,993 - top2vec - INFO - Finding dense areas of documents
2021-08-03 17:45:08,103 - top2vec - INFO - Finding topics
Nombre et taille de clusters (entre 2 et 8 clusters en general sur ces donnees).
= modele_brut.get_topic_sizes()
topic_sizes, topic_nums "num_cluster": topic_nums, "taille_cluster": topic_sizes})
pd.DataFrame({
= modele_nettoye.get_topic_sizes()
topic_sizes, topic_nums "num_cluster": topic_nums, "taille_cluster": topic_sizes}) pd.DataFrame({
num_cluster taille_cluster
0 0 585
1 1 466
2 2 449
num_cluster taille_cluster
0 0 826
1 1 674
Echantillons de tweets avec un score de representativite decroissant.
= pd.DataFrame(columns = ["num_cluster", "tweet", "score"])
tweets_brut
for i in range(modele_brut.get_num_topics()):
= modele_brut.search_documents_by_topic(topic_num = i, num_docs = 5)
documents, document_scores, document_ids = pd.concat([tweets_brut, pd.DataFrame({"num_cluster" : i, "tweet": documents, "score": document_scores})])
tweets_brut
"cluster " + tweets_brut.num_cluster.astype(str) + " - score " + tweets_brut[
'score'].astype(str) + " - " + tweets_brut.tweet
0 cluster 0 - score 0.31573457 - @L_BLK413 surprise de l’euro✨✨
1 cluster 0 - score 0.25234914 - @KobyCujoh @clementdetp Quand bien même la Belgique lapiderait la France à l'Euro y'aurait toujours 0 CDM et le tatouage sera encore valable
2 cluster 0 - score 0.23729327 - Euro 2021 : MON PRONO DANEMARK RUSSIE !: Voici mon pronostic pour le match entre le Danemark et la Russie ! Qui va… https://t.co/xhgoAsKvUL
3 cluster 0 - score 0.23663124 - Cet euro reste un bon exercice avant d'affronter la France, le 7 octobre en demi-finale de Nations League #belfra… https://t.co/YCI5nAjCQZ
4 cluster 0 - score 0.2331247 - Il a une carrière le pauvre !! Apres ouinil il a fait bcp d abus... Mais ca explique pas ttes ses blessures....\nBon… https://t.co/yrJxScmdTo
0 cluster 1 - score 0.33373678 - @Plumosaure2 @depaylegoat @Sashimig @AngeDiMarinho Quel raisonnement ne tient pas la route ? Si tu me dis que c'est… https://t.co/WBvyknw2xN
1 cluster 1 - score 0.30442056 - Euro 2021: le fond, mais pas la forme pour la Nati https://t.co/ecEolRF7AE @arcinfo
2 cluster 1 - score 0.3007415 - On a gagné la big cdm sans lui je doute qu’on ai besoin de lui a l’euro https://t.co/2Rlr3p5pf8
3 cluster 1 - score 0.27668858 - Finlande-Belgique Euro 2021: Les Belges finissent le boulot, la Finlande éliminée après avoir bien résisté... Le ma… https://t.co/PHc9JarNQj
4 cluster 1 - score 0.27665848 - @AfterRMC @DanielRiolo hé Daniel RIOLO au.lieu de te masturber sur le nom de neymar mbappé branle quoi à l'eu… https://t.co/coJduf2nmc
0 cluster 2 - score 0.320181 - @Itadorijj @ActuBarcaFR Télécharge l'application Star Times et tu regardes le match sans problème sans pub pendant… https://t.co/Uzy1OniOh2
1 cluster 2 - score 0.29392558 - L'UEFA interdit finalement au stade de Munich d'être éclairé aux couleurs LGBT+ pour le match Allemagne-Hongrie.\n\nhttps://t.co/J7Ow3E7fQe
2 cluster 2 - score 0.27663442 - Le prix a augmenté 😁\nValeur actuelle du Bitcoin: €27485.48 à 04:01\nVariation 📈 (+0.23%)
3 cluster 2 - score 0.27619216 - @migwa971 @Paristeamfr Il a déjà parlé et a dit clairement ce qu’il avait à dire et en ce moment il est concentré s… https://t.co/SIzJOBOfcE
4 cluster 2 - score 0.26978457 - @RMadridHebdo @SafSafizi28 @AlfredoPedulla J'aimerai tellement qu'il vienne cet été,personne d'autre si il y'a une… https://t.co/XkK2t2aAM0
dtype: object
= pd.DataFrame(columns = ["num_cluster", "tweet", "score"])
tweets_nettoye
for i in range(modele_nettoye.get_num_topics()):
= modele_nettoye.search_documents_by_topic(topic_num = i, num_docs = 5)
documents, document_scores, document_ids = pd.concat([tweets_nettoye, pd.DataFrame({"num_cluster" : i, "tweet": documents, "score": document_scores})])
tweets_nettoye
"cluster " + tweets_nettoye.num_cluster.astype(str) + " - score " + tweets_nettoye[
'score'].astype(str) + " - " + tweets_nettoye.tweet
0 cluster 0 - score 0.207342 - @sofoot @RougeDirect lamentable boycotton Hongrie Orban pays piétiner valeur fraternité
1 cluster 0 - score 0.203793 - prix diminuer valeur actuel Bitcoin euro 27363.39 03:30 variation -1.14
2 cluster 0 - score 0.1944428 - @marwan_xv @bigrom71289687 @l_interist vouloir cest quils favori gagner l
3 cluster 0 - score 0.18286806 - @OthFCB @franekfoot voir niveau euro pue merde rajouter équipe
4 cluster 0 - score 0.17554188 - @_befoot vouloir kanté ballon or saison demi teinte dire
0 cluster 1 - score 0.40033513 - euro-2021 danemark héroïque Autriche qualifié
1 cluster 1 - score 0.36257046 - @xima597 @NeymarLeRoiXMb7 Hein cite gros équipe jouer copa
2 cluster 1 - score 0.3499338 - Philippe clement Belgium EURO 2000
3 cluster 1 - score 0.3280332 - @zaaaklaklaaa attendre adversaire claquer euro faire avis debile ca f
4 cluster 1 - score 0.3107319 - @issalhd full maquillage fake turquie euro
dtype: object
Mots les plus frequents de chaque cluster.
= modele_brut.get_topics(modele_brut.get_num_topics())
topic_words, _, topic_nums "cluster " + pd.Series(topic_nums.astype(str)) + " - " + pd.Series(map(" ".join, topic_words))
0 cluster 0 - avant qualifies euro avec ils fait des un qu en danemark huitiemes le deja equipe que ce la dans on et les ou si co va via jouer gagne je meme plus au qualifiee fin...
1 cluster 1 - on sans fait via que une co pas va meme mais si qualifies deja ca dans le qualifiee vous apres france huitiemes equipe finale ne match de son danemark cet ai pour i...
2 cluster 2 - match dans deja euro qu vous au avant le se des tout ils de ce pas bleus mais via ne pour je finale apres les et avec son on belgique danemark si qui huitiemes qual...
dtype: object
= modele_nettoye.get_topics(modele_nettoye.get_num_topics())
topic_words, _, topic_nums "cluster " + pd.Series(topic_nums.astype(str)) + " - " + pd.Series(map(" ".join, topic_words))
0 cluster 0 - finale deja danemark equipe huitieme gagner euro qualifier qualifie match belgique jouer france
1 cluster 1 - jouer match gagner equipe danemark deja belgique huitieme euro qualifie finale qualifier france
dtype: object
Les nuages de mots. Sans pretraitement les clusters font ressortir trop de stopwords, avec pretraitement c’est mieux mais les differents clusters sont tres proches les uns des autres, leurs mots-cles etant “france, danemark, belgique, euro, equipe, match, huitieme, final, qualifier, jouer, gagner”.
for topic in range(modele_brut.get_num_topics()):
modele_brut.generate_topic_wordcloud(topic)
for topic in range(modele_nettoye.get_num_topics()):
modele_nettoye.generate_topic_wordcloud(topic)
= PdfPages(os.path.join(os.getcwd(), 'graphiques_NLP', 'nuage_brut.pdf'))
pp for i in range(modele_brut.get_num_topics()):
= modele_brut.generate_topic_wordcloud(i)
plot_topic
pp.savefig(plot_topic)
pp.close()
= PdfPages(os.path.join(os.getcwd(), 'graphiques_NLP', 'nuage_nettoye.pdf'))
pp for i in range(modele_nettoye.get_num_topics()):
= modele_nettoye.generate_topic_wordcloud(i)
plot_topic
pp.savefig(plot_topic) pp.close()
Le nuage de mots des tweets bruts.
Le nuage de mots des tweets lemmatises.
7 Reseau des tweetos
Preparation des donnees.
= dtf.copy()[["user.screen_name", "in_reply_to_screen_name"]]
reseau = True)
reseau.dropna(inplace
= reseau.groupby(["in_reply_to_screen_name", "user.screen_name"]).size().reset_index(name = "freq")
reseau = {"in_reply_to_screen_name": "Target",
reseau.rename(columns "user.screen_name": "Source",
"freq": "Weight"
= True)
}, inplace
# on exclut les aretes isolees du graphe
= reseau.groupby("Source").size().reset_index(name = "nb_target")
target_unique = target_unique[target_unique.nb_target == 1]
target_unique = reseau.groupby("Target").size().reset_index(name = "nb_source")
source_unique = source_unique[source_unique.nb_source == 1]
source_unique
= reseau[~(reseau.Source.isin(target_unique.Source) & reseau.Target.isin(source_unique.Target))] reseau
Construction du reseau.
= Network(directed = True, width = '100%')
G
G.force_atlas_2based()'dynamic')
G.set_edge_smooth(
for i in range(len(reseau)):
= reseau.Source.iloc[i]
src = reseau.Target.iloc[i]
dst # conversion numpy --> Python pour eviter plantage
= reseau.Weight.iloc[i].item()
w
if reseau.Target.iloc[i] == 'ActuFoot_':
= 'firebrick')
G.add_node(src, color = 'firebrick')
G.add_node(dst, color elif reseau.Target.iloc[i] == 'EspoirsduFoot':
= 'deeppink')
G.add_node(src, color = 'deeppink')
G.add_node(dst, color elif reseau.Target.iloc[i] == 'RMCsport':
= 'navy')
G.add_node(src, color = 'navy')
G.add_node(dst, color elif reseau.Target.iloc[i] == 'WilooFootball':
= 'blue')
G.add_node(src, color = 'blue')
G.add_node(dst, color elif reseau.Target.iloc[i] == 'WinamaxSport':
= 'orange')
G.add_node(src, color = 'orange')
G.add_node(dst, color elif reseau.Target.iloc[i] == '_BeFoot':
= 'red')
G.add_node(src, color = 'red')
G.add_node(dst, color else:
G.add_node(src)
G.add_node(dst)
= w)
G.add_edge(src, dst, value
'graphiques_NLP', 'visu_network.html')) G.write_html(os.path.join(os.getcwd(),
Le graphique montre que les principaux “influenceurs” sont ActuFoot_, EspoirsduFoot, RMCsport, WinamaxSport, WilooFootball et _BeFoot. On peut zoomer sur ce graphique et deplacer certains noeuds pour etudier de plus pres les relations entre certains auteurs des tweets.