Comprendre la dynamique de Twitter avec R et Gephi : analyse de texte et centralité
Publié: 2022-07-22Cet article développe et approfondit l'analyse présentée dans le premier volet de notre série d'analyses des réseaux sociaux. Nous utilisons le même ensemble de données Twitter et le même réseau d'interaction construits dans le premier article. Mais cette fois, l'idée est de mieux déduire les principaux acteurs, d'identifier leurs sujets de discussion, et de comprendre comment ces sujets se sont propagés.
Centralité des réseaux sociaux
Pour atteindre nos objectifs, nous devons d'abord introduire le concept de centralité . En science des réseaux, la centralité fait référence aux nœuds qui ont une forte influence sur le réseau. L'influence est un concept ambigu ; il peut être compris de plusieurs façons. Un nœud avec de nombreuses arêtes est-il plus influent qu'un nœud avec moins d'arêtes mais plus « importantes » ? Qu'est-ce qui constitue un avantage important sur un réseau social ?
Pour répondre à ces ambiguïtés, les scientifiques des réseaux ont développé de nombreuses mesures de centralité. Ici, nous discutons de quatre mesures couramment utilisées, bien que de nombreuses autres soient disponibles.
Diplôme
La mesure la plus courante et la plus intuitive est la centralité des degrés. L'idée derrière la centralité des degrés est simple : mesurez l'influence par le degré du nœud. Il peut avoir des variantes si le graphe est orienté ; dans ce cas, vous pouvez mesurer le degré d'entrée et le degré de sortie - le premier est connu comme le score du hub et le second comme le score d'autorité.
Dans le premier volet de cette série, nous avons utilisé l'approche non dirigée. Cette fois, nous nous concentrons sur l'approche indegree. Cela permet une analyse plus précise en mettant l'accent sur les utilisateurs qui sont retweetés par d'autres par rapport aux utilisateurs qui se contentent de retweeter fréquemment.
Vecteur propre
La mesure du vecteur propre s'appuie sur la centralité du degré. Plus les nœuds influents pointent vers un nœud donné, plus son score est élevé. Nous commençons avec une matrice de contiguïté , où les lignes et les colonnes représentent les nœuds, et nous utilisons un 1 ou un 0 pour indiquer si les nœuds correspondants d'une ligne et d'une colonne données sont connectés. Le calcul principal estime les vecteurs propres de la matrice. Le vecteur propre principal contiendra les mesures de centralité que nous voulons, où la position i contiendra le score de centralité du nœud i .
Classement
Le PageRank est la variation de la mesure du vecteur propre au cœur de Google. La méthode exacte utilisée par Google est inconnue, mais l'idée générale est que chaque nœud commence avec un score de 1, puis distribue son score en parts égales à chacun de ses bords. Par exemple, si un nœud a trois arêtes qui s'étendent à partir de lui, il "envoie" un tiers de son score à travers chaque arête. En même temps, le nœud est rendu plus important par les arêtes qui pointent vers lui. Il en résulte un système résoluble de N équations à N inconnues.
Intermédiaire
La quatrième mesure, l'intermédiarité , utilise une approche très différente. Ici, un nœud est dit influent s'il est inclus dans de nombreux chemins courts entre d'autres nœuds. C'est-à-dire qu'il est responsable de la communication avec de nombreux autres nœuds, connectant "des mondes différents".
Par exemple, dans l'analyse des réseaux sociaux, ces types de nœuds pourraient être compris comme les types de personnes qui aident les autres à trouver de nouveaux emplois ou à établir de nouvelles relations - ce sont les portes vers des cercles sociaux jusque-là inconnus.
Lequel dois-je utiliser ?
La mesure de centralité appropriée dépend de l'objectif de votre analyse. Voulez-vous savoir quels utilisateurs sont fréquemment pointés du doigt par d'autres en termes de quantité ? La centralité des degrés serait probablement votre meilleure option. Ou préférez-vous une mesure de centralité qui tient compte de la qualité ? Dans ce cas, le vecteur propre ou le PageRank donneront de meilleurs résultats. Si vous voulez savoir quels utilisateurs fonctionnent le plus efficacement comme ponts entre différentes communautés, l'intermédiarité est votre meilleure option.
Lorsque vous utilisez plusieurs mesures similaires, par exemple vecteur propre et PageRank, vous pouvez toutes les estimer et voir si elles donnent des classements équivalents. Sinon, vous pouvez approfondir votre analyse des différences ou générer une nouvelle mesure en combinant leurs scores.
Une autre approche utilise l'analyse en composantes principales pour estimer quelle mesure vous donne plus d'informations sur l'influence réelle des nœuds sur votre réseau.
Calcul pratique de la centralité
Voyons comment nous pouvons calculer ces mesures en utilisant R et RStudio. (Ils peuvent également être réalisés avec Gephi.)
Tout d'abord, nous devons charger toutes les bibliothèques que nous allons utiliser tout au long de cet article :
library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")
Ensuite, nous supprimerons les nœuds isolés des données que nous avons utilisées auparavant, car ils ne sont pas utiles à cette analyse. Ensuite, nous utiliserons les fonctions igraph
betweenness
, centr_eigen
, page_rank
et degree
pour estimer les mesures de centralité. Enfin, nous stockerons les scores sur l'objet igraph
et sur une trame de données pour voir quels utilisateurs étaient les plus centraux.
load("art1_tweets.RData") Isolated = which(degree(net)==0) net_clean = delete.vertices(net, Isolated) cent<-data.frame(bet=betweenness(net_clean),eig=centr_eigen(net_clean)$vector,prank=(page_rank(net_clean)$vector),degr=degree(net_clean, mode="in")) cent <- cbind(account = rownames(cent), cent)
Nous pouvons maintenant vérifier les 10 utilisateurs les plus centraux pour chaque mesure :
Diplôme | |
Vecteur propre | |
Classement | |
Intermédiaire | |
Les résultats:
Diplôme | Vecteur propre | Classement | Intermédiaire | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_inside | 1 | mundodabola | 0,037 | vuesdey | 77704 |
Le football troll | 5755 | ÉquipagesMat19 | 0,51 | AleLiparoti | 0,026 | EdmundOris | 76425 |
PSG_inside | 5194 | eh01195991 | 0,4 | PSG_inside | 0,017 | ba*****lla | 63799 |
ÉquipagesMat19 | 4344 | Mohamed135680 | 0,37 | Roy Nemer | 0,016 | FranciscoGaius | 63081 |
brfootball | 4054 | ActuFoot_ | 0,34 | Le football troll | 0,013 | Yemihazan | 62534 |
PSG_espanol | 3616 | marttvall | 0,34 | ESPNFC | 0,01 | hashtag2weet | 61123 |
IbaiOut | 3258 | ESPNFC | 0,3 | PSG_espanol | 0,007 | Angela_FCB | 60991 |
ActuFoot_ | 3175 | brfootball | 0,25 | InstantFoot | 0,007 | Zyyon_ | 57269 |
FootyHumour | 2976 | SaylorLuneArmée | 0,22 | IbaiOut | 0,006 | ÉquipagesMat19 | 53758 |
mundodabola | 2778 | JohnsvillPat | 0,2 | 2010MisterChip | 0,006 | MdeenOlawale | 49572 |
Nous pouvons voir que les trois premières mesures partagent un certain nombre d'utilisateurs, tels que PSG_inside, ESPNFC, CrewsMat19 et TrollFootball. On peut supposer qu'ils ont eu une forte influence sur la discussion. L'intermédiarité a une approche différente pour mesurer la centralité et ne montre donc pas autant de chevauchement avec les autres techniques.
Remarque : les opinions exprimées par les comptes Twitter mentionnés dans cet article ne reflètent pas celles de Toptal ou de l'auteur.
Dans les images suivantes, vous pouvez voir notre graphique de réseau coloré original avec deux superpositions d'étiquettes d'utilisateur. Dans le premier, les nœuds sont mis en évidence par leurs scores PageRank, et dans le second, par leurs scores d'intermédiarité :
Gephi peut être utilisé pour reproduire ces images. Vous pouvez estimer l'intermédiarité ou les scores PageRank à l'aide du bouton Diamètre du réseau dans le panneau des statistiques. Ensuite, vous pouvez afficher les noms de nœuds à l'aide d'attributs, comme illustré dans le premier épisode de cette série.
Analyse de texte : R et LDA
Nous pouvons également analyser les discussions sur les réseaux sociaux pour identifier ce dont les utilisateurs ont parlé. Il y a plusieurs façons d'aborder cela. Nous ferons de la modélisation thématique par latent Dirichlet Allocation (LDA), une technique d'apprentissage automatique non supervisée qui nous permet d'estimer quel ensemble de mots a tendance à apparaître ensemble. Ensuite, à travers cet ensemble de mots, nous pouvons déduire le sujet en cours de discussion.
La première étape consiste à assainir le texte. Pour cela, nous définissons la fonction suivante :
# This function normalizes text by removing Twitter-related terms and noisy characters sanitize_text <- function(text) { # Convert to ASCII to remove accented characters: text <- iconv(text, to = "ASCII", sub = " ") # Move to lower case and delete RT word (this is added by Twitter) text <- gsub("rt", " ", tolower(text)) # Delete links and user names: text <- gsub("@\\w+", " ", gsub("http.+ |http.+$", " ", text)) # Delete tabs and punctuation: text <- gsub("[ |\t]{2,}", " ", gsub("[[:punct:]]", " ", text)) text <- gsub("amp", " ", text) # Remove HTML special character # Delete leading and lagging blanks: text <- gsub("^ ", "", gsub(" $", "", text)) text <- gsub(" +", " ", text) # Delete extra spaces return(text) }
Nous devons également supprimer les mots vides, les doublons et les entrées vides. Ensuite, nous devons convertir notre texte en une matrice de termes de document à traiter par LDA.

Dans cet ensemble de données, nous avons des utilisateurs parlant plusieurs langues (anglais, espagnol, français, etc.). LDA fonctionne mieux si nous nous concentrons sur une seule langue. Nous allons l'appliquer sur les utilisateurs de la plus grande communauté détectée dans le premier volet de cette série, composée principalement de comptes avec des utilisateurs anglophones.
# Detect communities: my.com.fast <-cluster_louvain(as.undirected(simplify(net))) largestCommunities <- order(sizes(my.com.fast), decreasing=TRUE)[1:3] # Save the usernames of the biggest community: community1 <- names(which(membership(my.com.fast) == largestCommunities[1])) # Sanitize the text of the users of the biggest community: text <- unique(sanitize_text(tweets.df[which(tweets.df$screen_name %in% community1),]$text)) text = text[text!=''] # Delete empty entries stopwords_regex = paste(stopwords('es'), collapse = '\\b|\\b') stopwords_regex = paste0('\\b', stopwords_regex, '\\b') # Remove English stopwords: text = stringr::str_replace_all(text, stopwords_regex, '') # Create the document term matrix: dtm <- CreateDtm(text, doc_names = seq(1:length(text)), ngram_window = c(1, 2))
Nombre de sujets et scores de cohérence
Le principal hyperparamètre que nous devons définir dans LDA est le nombre (k) de sujets que nous voulons estimer. Cependant, comment pouvons-nous le savoir à l'avance? Une approche courante consiste à former des modèles LDA sur différentes valeurs de k et à mesurer la cohérence de chacun. Nous ferons cela pour les valeurs k de 3 à 20, car les valeurs en dehors de cette plage ne valent pas la peine d'être vérifiées, d'après mon expérience :
tf <- TermDocFreq(dtm = dtm) # Remove infrequent words: tf_trimmed = tf$term[ tf$term_freq > 1 & tf$doc_freq < nrow(dtm) / 2 ] # Create a folder to store trained models: model_dir <- paste0("models_", digest::digest(tf_trimmed, algo = "sha1")) if (!dir.exists(model_dir)) dir.create(model_dir) # Define a function to infer LDA topics: train_lda_model <- function(number_of_topics){ filename = file.path(model_dir, paste0(number_of_topics, "_topics.rda")) # Check if the model already exists: if (!file.exists(filename)) { # To get exactly the same output on each run, use a constant seed: set.seed(12345) lda_model = FitLdaModel(dtm = dtm, k = number_of_topics, iterations = 500) lda_model$k = number_of_topics lda_model$coherence = CalcProbCoherence(phi = lda_model$phi, dtm = dtm, M = 5) save(lda_model, file = filename) } else { load(filename) } lda_model } # The number of topics that we are going to infer in each LDA training run: topic_count = seq(3, 20, by = 1) # Train through the TmParallelApply function models = TmParallelApply(X = topic_count, FUN = train_lda_model, export = c("dtm", "model_dir"))
Ensuite, nous traçons graphiquement la valeur de cohérence de chacun :
coherence_by_topics_quantity = data.frame( topic_number = sapply(models, function(model_instance) nrow(model_instance$phi)), score_coherence = sapply(models, function(model_instance) mean(model_instance$coherence)), stringsAsFactors = FALSE) ggplot(coherence_by_topics_quantity, aes(x = topic_number, y = score_coherence)) + geom_point() + geom_line(group = 1) + ggtitle("Coherence by Topic") + theme_minimal() + scale_x_continuous(breaks = seq(1,20,1)) + ylab("Coherence Score") + xlab("Number of topics")
Une valeur de cohérence élevée montre une meilleure segmentation du texte en thèmes :
Nous atteignons notre score de cohérence maximal avec k = 13, nous allons donc utiliser le modèle LDA formé avec 13 sujets. Grâce à la fonction GetTopTerms, nous pouvons voir les 10 mots principaux pour chaque sujet et estimer la sémantique du sujet à travers eux :
best_model <- models[which.max(coherence_by_topics_quantity$score_coherence)][[ 1 ]] # Most important terms by topic: best_model$top_terms <- GetTopTerms(phi = best_model$phi, M = 20) top10 <- as.data.frame(best_model$top_terms) top10
Le tableau suivant détaille les cinq sujets les plus importants détectés et les 10 principaux mots qui les illustrent :
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | messi | messi | messi | messi | messi |
2 | lionel | ligue | est | psg | |
3 | lionel_messi | Publier | gagner | il | Leo |
4 | psg | million | Buts | au | leo_messi |
5 | Madrid | aime | ch | verser | maintenant |
6 | réel | spo | des ions | pas | compagnie |
sept | Barcelone | chèvre | ch_ions | avec | Virginie |
8 | Paris | psg | ucl | du | ser |
9 | Real Madrid | bar | ballon | qui | jugador |
dix | mbapp | plus gros | monde | je | majeur |
Bien que la plupart des utilisateurs de cette communauté soient anglophones, il existe encore un certain nombre de francophones et d'espagnols (t_4 et t_5 dans le tableau). Nous pouvons en déduire que le premier sujet concerne l'équipe précédente de Messi (le FC Barcelone), le deuxième sujet concerne la publication de Messi sur Instagram et le troisième sujet se concentre sur les réalisations de Messi.
Maintenant que nous avons les sujets, nous pouvons prédire lequel d'entre eux a été le plus discuté. Pour ce faire, nous allons d'abord concaténer les tweets des utilisateurs (encore une fois, de la plus grande communauté) :
tweets.df.com1 = tweets.df[which(tweets.df$screen_name %in% community1),] users_text <- ddply(tweets.df.com1, ~screen_name, summarise, text = paste(text, collapse = " "))
Ensuite, nous assainissons le texte comme précédemment et créons le DTM. Après cela, nous appelons la fonction de predict
en utilisant notre modèle LDA et le DTM comme arguments. De plus, nous avons paramétré la méthode sur Gibbs pour améliorer le temps de calcul car nous avons beaucoup de texte à analyser :
users_text$text <- sanitize_text(users_text$text) # Get rid of duplicates stopwords_regex = paste(stopwords('en'), collapse = '\\b|\\b') stopwords_regex = paste0('\\b', stopwords_regex, '\\b') users_text$text = stringr::str_replace_all(users_text$text, stopwords_regex, '') dtm.users.com1 <- CreateDtm(users_text$text, doc_names = users_text$screen_name, ngram_window = c(1, 2)) com1.users.topics = predict(best_model, dtm.users.com1, method="gibbs", iterations=100)
Maintenant, dans le bloc de données com1.users.topics
, nous voyons combien chaque utilisateur a parlé de chaque sujet :
Compte | t_1 | t_2 | t_3 | t_4 | t_5 | […] |
---|---|---|---|---|---|---|
___99e | 0,02716049 | 0.86666666 | 0,00246913 | 0,00246913 | 0,00246913 | |
Chef__ | 0,05185185 | 0.84197530 | 0,00246913 | 0,00246913 | 0,00246913 | |
Memphis | 0.00327868 | 0.00327868 | 0,03606557 | 0.00327868 | 0.00327868 | |
___Alex1 | 0,00952380 | 0,00952380 | 0,00952380 | 0,00952380 | 0,00952380 | |
[…] |
Enfin, avec ces informations, nous pouvons créer un nouvel attribut sur le graphe de nœuds pour définir quel sujet a été le plus parlé par quel utilisateur. Ensuite, nous pouvons créer un nouveau fichier GML pour le visualiser dans Gephi :
# Get the subgraph of the first community: net.com1 = induced_subgraph(net,community1) # Estimate the topic with the max score for each user: com1.users.maxtopic = cbind(users_text$screen_name, colnames(com1.users.topics)[apply(com1.users.topics, 1, which.max)]) # Order the users topic data frame by the users' order in the graph: com1.users.maxtopic = com1.users.maxtopic[match(V(net.com1)$name, com1.users.maxtopic[,1]),] # Create a new attr of the graph by the topic most discussed by each user: V(net.com1)$topic = com1.users.maxtopic[,2] # Create a new graph: write_graph(simplify(net.com1), "messi_graph_topics.gml", format = "gml")
Déduire des sujets importants et appliquer la centralité des réseaux sociaux
Dans le premier volet de cette série, nous avons appris à obtenir des données de Twitter, à créer le graphique d'interaction, à le tracer via Gephi et à détecter les communautés et les utilisateurs importants. Dans cet article, nous avons développé cette analyse en démontrant l'utilisation de critères supplémentaires pour détecter les utilisateurs influents. Nous avons également montré comment détecter et déduire ce dont les utilisateurs parlaient et tracer cela dans le réseau.
Dans notre prochain article, nous continuerons d'approfondir cette analyse en montrant comment les utilisateurs peuvent détecter le fléau des réseaux sociaux : les spambots et les trolls.
Lectures complémentaires sur le blog Toptal Engineering :
- Analyse des réseaux sociaux à l'aide de Power BI et R : un guide de visuels personnalisés