Comprendere le dinamiche di Twitter con R e Gephi: analisi e centralità del testo
Pubblicato: 2022-07-22Questo articolo amplia e approfondisce l'analisi presentata nella prima puntata della nostra serie di analisi sui social network. Utilizziamo lo stesso set di dati Twitter e la stessa rete di interazione costruita nel primo articolo. Tuttavia, questa volta, l'idea è di dedurre meglio gli attori principali, identificare i loro argomenti di discussione e capire come questi argomenti si diffondono.
Centralità dei social network
Per raggiungere i nostri obiettivi, dobbiamo prima introdurre il concetto di centralità . Nella scienza delle reti, la centralità si riferisce ai nodi che hanno una forte influenza sulla rete. L'influenza è un concetto ambiguo; può essere compreso in molti modi. Un nodo con molti bordi è più influente di un nodo con meno bordi ma più "importanti"? Cosa costituisce un vantaggio importante su un social network?
Per affrontare queste ambiguità, gli scienziati della rete hanno sviluppato molte misure di centralità. Qui discutiamo di quattro misure comunemente utilizzate, anche se molte altre sono disponibili.
Livello
La misura più comune e intuitiva è la centralità del grado. L'idea alla base della centralità del grado è semplice: misurare l'influenza in base al grado del nodo. Può avere varianti se il grafico è diretto; in tal caso, puoi misurare l'indegree e l'outdegree: il primo è noto come punteggio hub e il secondo come punteggio di autorità.
Nella prima puntata di questa serie, abbiamo utilizzato l'approccio non orientato. Questa volta, ci concentriamo sull'approccio indegree. Ciò consente un'analisi più accurata enfatizzando gli utenti che vengono ritwittati da altri rispetto agli utenti che si limitano a ritwittare frequentemente.
Autovettore
La misura dell'autovettore si basa sulla centralità del grado. Più i nodi influenti puntano a un dato nodo, maggiore è il suo punteggio. Iniziamo con una matrice di adiacenza , dove righe e colonne rappresentano i nodi, e usiamo un 1 o 0 per indicare se i nodi corrispondenti di una data riga e colonna sono collegati. Il calcolo principale stima gli autovettori della matrice. L'autovettore principale conterrà le misure di centralità desiderate, dove la posizione i conterrà il punteggio di centralità del nodo i .
PageRank
PageRank è la variazione della misura dell'autovettore alla base di Google. Il metodo esatto utilizzato da Google è sconosciuto, ma l'idea generale è che ogni nodo inizia con un punteggio di 1, quindi distribuisce il proprio punteggio in parti uguali su ciascuno dei suoi bordi. Ad esempio, se un nodo ha tre spigoli che si estendono da esso, "invia" un terzo del suo punteggio attraverso ciascun arco. Allo stesso tempo, il nodo è reso più importante dai bordi che lo puntano. Ciò si traduce in un sistema risolvibile di N equazioni con N incognite.
Frammezzo
La quarta misura, Betweenness , utilizza un approccio molto diverso. Qui, si dice che un nodo è influente se è incluso in molti percorsi brevi tra altri nodi. Cioè, è responsabile della comunicazione con molti altri nodi, collegando "mondi diversi".
Ad esempio, nell'analisi dei social network, questi tipi di nodi possono essere intesi come i tipi di persone che aiutano gli altri a trovare un nuovo lavoro o a stabilire nuove connessioni: sono le porte di circoli sociali precedentemente sconosciuti.
Quale dovrei usare?
La misura di centralità appropriata dipende dall'obiettivo dell'analisi. Vuoi sapere quali utenti vengono frequentemente individuati da altri in termini di quantità? La centralità della laurea sarebbe probabilmente la tua migliore opzione. Oppure preferisci una misura di centralità che tenga conto della qualità? In tal caso, autovettore o PageRank produrranno risultati migliori. Se vuoi sapere quali utenti funzionano in modo più efficace come ponti tra diverse comunità, la condivisione è la tua migliore opzione.
Quando si utilizzano più misure simili, ad esempio autovettore e PageRank, è possibile stimarle tutte e vedere se producono classifiche equivalenti. In caso contrario, puoi approfondire la tua analisi delle differenze o generare una nuova misura combinando i loro punteggi.
Un altro approccio utilizza l'analisi delle componenti principali per stimare quale misura fornisce maggiori informazioni sulla reale influenza dei nodi sulla rete.
Calcolo pratico della centralità
Vediamo come possiamo calcolare queste misure usando R e RStudio. (Si possono fare anche con Gephi.)
Innanzitutto, dobbiamo caricare tutte le librerie che utilizzeremo in questo articolo:
library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")
Successivamente, rimuoveremo i nodi isolati dai dati che abbiamo utilizzato in precedenza, poiché non sono utili per questa analisi. Quindi, utilizzeremo le funzioni igraph
betweenness
, centr_eigen
, page_rank
e degree
per stimare le misure di centralità. Infine, memorizzeremo i punteggi sull'oggetto igraph
e su un data frame per vedere quali utenti sono stati i più centrali.
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)
Ora possiamo controllare i 10 utenti più centrali per ogni misura:
Livello | |
Autovettore | |
PageRank | |
Frammezzo | |
I risultati:
Livello | Autovettore | PageRank | Frammezzo | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_dentro | 1 | mundodabola | 0,037 | viewdey | 77704 |
TrollFootball | 5755 | CrewsMat19 | 0,51 | Ale Liparoti | 0,026 | Edmund Oris | 76425 |
PSG_dentro | 5194 | eh01195991 | 0.4 | PSG_dentro | 0,017 | ba*****lla | 63799 |
CrewsMat19 | 4344 | maometto135680 | 0,37 | Roy Nemer | 0,016 | Francisco Gaius | 63081 |
brcalcio | 4054 | ActuFoot_ | 0,34 | TrollFootball | 0,013 | Yemihazan | 62534 |
PSG_espanol | 3616 | marttvall | 0,34 | ESPNFC | 0.01 | hashtag2weet | 61123 |
Ibai Out | 3258 | ESPNFC | 0.3 | PSG_espanol | 0,007 | Angela_FCB | 60991 |
ActuFoot_ | 3175 | brcalcio | 0,25 | InstantFoot | 0,007 | Zyyon_ | 57269 |
Footy Humour | 2976 | Saylor Moon Army | 0,22 | Ibai Out | 0.006 | CrewsMat19 | 53758 |
mundodabola | 2778 | Johnsvill Pat | 0.2 | 2010 Mister Chip | 0.006 | MdeenOlawale | 49572 |
Possiamo vedere che le prime tre misure condividono un numero di utenti, come PSG_inside, ESPNFC, CrewsMat19 e TrollFootball. Possiamo presumere che abbiano avuto una forte influenza sulla discussione. Betweenness ha un approccio diverso alla misurazione della centralità e quindi non mostra tante sovrapposizioni con le altre tecniche.
Nota: le opinioni espresse dagli account Twitter menzionati in questo articolo non riflettono quelle di Toptal o dell'autore.
Nelle immagini seguenti, puoi vedere il nostro grafico di rete colorato originale con due sovrapposizioni di etichette utente. Nel primo, i nodi sono evidenziati dai loro punteggi di PageRank, e nel secondo, dai loro punteggi di interconnessione:
Gephi può essere utilizzato per riprodurre queste immagini. Puoi stimare i punteggi di Betweenness o PageRank utilizzando il pulsante Network Diameter nel pannello delle statistiche. Quindi, puoi mostrare i nomi dei nodi usando gli attributi come mostrato nella prima puntata di questa serie.
Analisi del testo: R e LDA
Possiamo anche analizzare le discussioni sui social network per identificare di cosa hanno parlato gli utenti. Ci sono diversi modi per avvicinarsi a questo. Effettueremo la modellazione degli argomenti attraverso l'allocazione di Dirichlet latente (LDA), una tecnica di apprendimento automatico non supervisionata che ci consente di stimare quale insieme di parole tendono ad apparire insieme. Quindi, attraverso quell'insieme di parole, possiamo dedurre l'argomento in discussione.
Il primo passo è sanificare il testo. Per fare ciò, definiamo la seguente funzione:
# 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) }
Dobbiamo anche rimuovere le parole di arresto, i duplicati e le voci vuote. Successivamente, dobbiamo convertire il nostro testo in una matrice di termini di documento da elaborare da LDA.
In questo set di dati, abbiamo utenti che parlano molte lingue (inglese, spagnolo, francese, ecc.). LDA funziona meglio se ci concentriamo su una singola lingua. Lo applicheremo agli utenti della più grande community rilevata nella prima puntata di questa serie, composta principalmente da account con utenti di lingua inglese.
# 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))
Conteggio degli argomenti e punteggi di coerenza
L'iperparametro principale che dobbiamo definire in LDA è il numero (k) di argomenti che vogliamo stimare. Tuttavia, come possiamo saperlo in anticipo? Un approccio comune consiste nell'addestrare modelli LDA su diversi k valori e misurare la coerenza di ciascuno. Lo faremo per k valori da 3 a 20, poiché i valori al di fuori di questo intervallo non valgono la pena di controllare, secondo la mia esperienza:
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"))
Quindi, tracciamo graficamente il valore di coerenza di ciascuno:
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")
Un valore di coerenza elevato mostra una migliore segmentazione del testo in argomenti:
Raggiungiamo il nostro punteggio di coerenza di picco con k = 13, quindi utilizzeremo il modello LDA addestrato con 13 argomenti. Attraverso la funzione GetTopTerms, possiamo vedere le 10 parole principali per ogni argomento e stimare la semantica dell'argomento attraverso di esse:
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
La tabella seguente descrive in dettaglio i cinque argomenti più importanti rilevati e le 10 parole principali che li esemplificano:
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | messi | messi | messi | messi | messi |
2 | lionel | lega | est | psg | |
3 | lionel_messi | inviare | vincita | I l | Leo |
4 | psg | milioni | obiettivi | au | leo_messi |
5 | Madrid | piace | cap | versare | ora |
6 | vero | spo | ioni | pas | compa |
7 | Barcellona | capra | ch_ioni | avec | va |
8 | Parigi | psg | ucl | du | ser |
9 | Real Madrid | sbarra | pallone | qui | jugador |
10 | mbapp | più grande | mondo | je | maggiore |
Sebbene la maggior parte degli utenti di questa comunità parli inglese, ci sono ancora un certo numero di persone che parlano francese e spagnolo (t_4 e t_5 nella tabella). Possiamo dedurre che il primo argomento si riferisce alla squadra precedente di Messi (FC Barcelona), il secondo argomento riguarda il post di Messi su Instagram e il terzo argomento si concentra sui risultati di Messi.
Ora che abbiamo gli argomenti, possiamo prevedere quale di essi è stato il più discusso. Per fare ciò, concateneremo prima i tweet degli utenti (di nuovo, dalla comunità più grande):
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 = " "))
Quindi, sanifichiamo il testo come prima e creiamo il DTM. Successivamente, chiamiamo la funzione di predict
utilizzando il nostro modello LDA e il DTM come argomenti. Inoltre, abbiamo impostato il metodo su Gibbs per migliorare il tempo di calcolo perché abbiamo molto testo da analizzare:
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)
Ora, nel frame di dati com1.users.topics
, vediamo quanto ogni utente ha parlato di ciascun argomento:
Account | t_1 | t_2 | t_3 | t_4 | t_5 | […] |
---|---|---|---|---|---|---|
___99° | 0.02716049 | 0.86666666 | 0.00246913 | 0.00246913 | 0.00246913 | |
Capo__ | 0.05185185 | 0.84197530 | 0.00246913 | 0.00246913 | 0.00246913 | |
Menfi | 0.00327868 | 0.00327868 | 0.03606557 | 0.00327868 | 0.00327868 | |
___Alex1 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | |
[…] |
Infine, con queste informazioni, possiamo creare un nuovo attributo sul grafico del nodo per definire quale argomento è stato maggiormente trattato da quale utente. Quindi possiamo creare un nuovo file GML per visualizzarlo in 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")
Dedurre argomenti importanti e applicare la centralità dei social network
Nella prima puntata di questa serie, abbiamo imparato come ottenere dati da Twitter, creare il grafico di interazione, tracciarlo tramite Gephi e rilevare comunità e utenti importanti. In questa puntata, abbiamo ampliato questa analisi dimostrando l'uso di criteri aggiuntivi per rilevare utenti influenti. Abbiamo anche dimostrato come rilevare e dedurre ciò di cui stavano parlando gli utenti e tracciarlo nella rete.
Nel prossimo articolo continueremo ad approfondire questa analisi mostrando come gli utenti possono rilevare la piaga dei social media: spambot e troll.
Ulteriori letture sul blog di Toptal Engineering:
- Analisi dei social network con Power BI e R: una guida agli elementi visivi personalizzati