Înțelegerea dinamicii Twitter cu R și Gephi: analiza textului și centralitatea
Publicat: 2022-07-22Acest articol extinde și aprofundează analiza prezentată în prima parte a seriei noastre de analize a rețelelor sociale. Folosim același set de date Twitter și aceeași rețea de interacțiune construită în primul articol. Cu toate acestea, de data aceasta, ideea este de a deduce mai bine actorii principali, de a identifica subiectele lor de discuție și de a înțelege cum se răspândesc aceste subiecte.
Centralitatea rețelei sociale
Pentru a ne atinge obiectivele, mai întâi trebuie să introducem conceptul de centralitate . În știința rețelelor, centralitatea se referă la nodurile care au o influență puternică asupra rețelei. Influența este un concept ambiguu; poate fi înțeles în multe feluri. Este un nod cu multe margini mai influent decât un nod cu margini mai puține, dar mai „importante”? Ce reprezintă un avantaj important pe o rețea de socializare?
Pentru a aborda aceste ambiguități, oamenii de știință din rețea au dezvoltat multe măsuri de centralitate. Aici, discutăm patru măsuri frecvent utilizate, deși multe altele sunt disponibile.
grad
Cea mai comună și intuitivă măsură este gradul de centralitate. Ideea din spatele centralității gradului este simplă: Măsurați influența prin gradul nodului. Poate avea variante dacă graficul este direcționat; în acest caz, puteți măsura indegree și outdegree — primul este cunoscut sub numele de scor central și al doilea ca scor de autoritate.
În prima parte a acestei serii, am folosit abordarea nedirecționată. De data aceasta, ne concentrăm pe abordarea indegree. Acest lucru permite o analiză mai precisă, punând accent pe utilizatorii care sunt retweetate de către alții față de utilizatorii care pur și simplu retweetează frecvent.
Vector propriu
Măsura vectorului propriu se bazează pe gradul de centralitate. Cu cât nodurile influente indică mai mult către un anumit nod, cu atât scorul acestuia este mai mare. Începem cu o matrice de adiacență , în care rândurile și coloanele reprezintă noduri și folosim un 1 sau 0 pentru a indica dacă nodurile corespunzătoare ale unui rând și coloană date sunt conectate. Calculul principal estimează vectorii proprii ai matricei. Vectorul propriu principal va conține măsurile de centralitate pe care le dorim, unde poziția i va deține scorul de centralitate al nodului i .
PageRank
PageRank este variația măsurării vectorului propriu din centrul Google. Metoda exactă pe care o folosește Google este necunoscută, dar ideea generală este că fiecare nod începe cu un scor de 1, apoi își distribuie scorul în părți egale la fiecare dintre marginile sale. De exemplu, dacă un nod are trei margini care se extind de la el, acesta „trimite” o treime din scorul său prin fiecare margine. În același timp, nodul devine mai important de marginile care indică el. Rezultă un sistem rezolvabil de N ecuații cu N necunoscute.
Între mijloc
A patra măsură, interness , folosește o abordare foarte diferită. Aici, se spune că un nod este influent dacă este inclus în multe căi scurte între alte noduri. Adică, este responsabil pentru comunicarea cu multe alte noduri, conectând „lumi diferite”.
De exemplu, în analiza rețelelor sociale, aceste tipuri de noduri ar putea fi înțelese ca tipuri de oameni care îi ajută pe alții să găsească noi locuri de muncă sau să facă noi conexiuni - sunt ușile către cercuri sociale necunoscute anterior.
Pe care ar trebui să folosesc?
Măsura adecvată a centralității depinde de scopul analizei dvs. Doriți să știți ce utilizatori sunt evidențiați frecvent de alții în ceea ce privește cantitatea? Centralitatea gradului ar fi probabil cea mai bună opțiune. Sau preferați o măsură de centralitate care să ia în considerare calitatea? În acest caz, vectorul propriu sau PageRank vor da rezultate mai bune. Dacă doriți să știți care utilizatori funcționează cel mai eficient ca punți între diferite comunități, interacțiunea este cea mai bună opțiune.
Când utilizați mai multe măsuri similare, de exemplu, vector propriu și PageRank, puteți să le estimați pe toate și să vedeți dacă dau clasamente echivalente. Dacă nu, puteți aprofunda analiza diferențelor sau puteți genera o nouă măsură combinând scorurile acestora.
O altă abordare utilizează analiza componentelor principale pentru a estima care măsură vă oferă mai multe informații despre influența reală a nodurilor în rețeaua dumneavoastră.
Calcul practic al centralității
Să vedem cum putem calcula aceste măsuri folosind R și RStudio. (Se pot face și cu Gephi.)
Mai întâi, trebuie să încărcăm toate bibliotecile pe care le vom folosi în acest articol:
library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")
În continuare, vom elimina nodurile izolate din datele pe care le-am folosit anterior, deoarece acestea nu sunt utile pentru această analiză. Apoi, vom folosi funcțiile igraph
betweenness
, centr_eigen
, page_rank
și degree
pentru a estima măsurile de centralitate. În cele din urmă, vom stoca scorurile pe obiectul igraph
și pe un cadru de date pentru a vedea care utilizatori au fost cei mai 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)
Acum putem verifica cei 10 cei mai centrali utilizatori după fiecare măsură:
grad | |
Vector propriu | |
PageRank | |
Între mijloc | |
Rezultatele:
grad | Vector propriu | PageRank | Între mijloc | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_înăuntru | 1 | mundodabola | 0,037 | vederidey | 77704 |
TrollFotbal | 5755 | CrewsMat19 | 0,51 | AleLiparoti | 0,026 | EdmundOris | 76425 |
PSG_înăuntru | 5194 | eh01195991 | 0,4 | PSG_înăuntru | 0,017 | ba*****lla | 63799 |
CrewsMat19 | 4344 | mohammad135680 | 0,37 | RoyNemer | 0,016 | FranciscoGaius | 63081 |
brfotbal | 4054 | ActuFoot_ | 0,34 | TrollFotbal | 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 | brfotbal | 0,25 | instantFoot | 0,007 | Zyyon_ | 57269 |
FootyHumour | 2976 | SaylorMoonArmy | 0,22 | IbaiOut | 0,006 | CrewsMat19 | 53758 |
mundodabola | 2778 | JohnsvillPat | 0,2 | 2010 MisterChip | 0,006 | MdeenOlawale | 49572 |
Putem vedea că primele trei măsuri au în comun un număr de utilizatori, cum ar fi PSG_inside, ESPNFC, CrewsMat19 și TrollFootball. Putem presupune că au avut o influență puternică asupra discuției. Betweenness are o abordare diferită pentru măsurarea centralității și, prin urmare, nu arată atât de multă suprapunere cu celelalte tehnici.
Notă: opiniile exprimate de conturile de Twitter menționate în acest articol nu reflectă cele ale Toptal sau ale autorului.
În imaginile următoare, puteți vedea graficul nostru original de rețea colorat cu două suprapuneri de etichete de utilizator. În primul, nodurile sunt evidențiate prin scorurile lor PageRank, iar în al doilea, prin scorurile lor de întreținere:
Gephi poate fi folosit pentru a reproduce aceste imagini. Puteți estima scorurile intermediare sau PageRank folosind butonul Network Diameter din panoul de statistici. Apoi, puteți afișa numele nodurilor folosind atribute așa cum este demonstrat în prima tranșă din această serie.
Analiza textului: R și LDA
De asemenea, putem analiza discuțiile din rețelele sociale pentru a identifica despre ce au vorbit utilizatorii. Există mai multe moduri de a aborda acest lucru. Vom face modelarea subiectelor prin Latent Dirichlet Allocation (LDA), o tehnică de învățare automată nesupravegheată care ne permite să estimăm ce set de cuvinte tind să apară împreună. Apoi, prin acel set de cuvinte, putem deduce subiectul discutat.
Primul pas este igienizarea textului. Pentru a face acest lucru, definim următoarea funcție:
# 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) }
De asemenea, trebuie să eliminăm cuvintele oprite, duplicatele și intrările goale. În continuare, trebuie să ne convertim textul într-o matrice document-termen care să fie procesată de LDA.
În acest set de date, avem utilizatori care vorbesc în multe limbi (engleză, spaniolă, franceză etc.). LDA funcționează cel mai bine dacă ne concentrăm pe o singură limbă. O vom aplica asupra utilizatorilor celei mai mari comunități detectate în prima tranșă a acestei serii, care este compusă în principal din conturi cu utilizatori vorbitori de limba engleză.
# 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))
Numărul subiectelor și scorurile de coerență
Principalul hiperparametru pe care trebuie să-l definim în LDA este numărul (k) de subiecte pe care dorim să le estimăm. Totuși, cum putem ști asta dinainte? O abordare comună este antrenarea modelelor LDA pe diferite valori k și măsurarea coerenței fiecăruia. Vom face acest lucru pentru k valori de la 3 la 20, deoarece valorile din afara acestui interval nu merită verificate, din experiența mea:
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"))
În continuare, graficăm valoarea de coerență a fiecăruia:
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")
O valoare mare de coerență arată o mai bună segmentare a textului pe subiecte:
Atingem scorul de coerență maxim cu k = 13, așa că vom folosi modelul LDA antrenat cu 13 subiecte. Prin funcția GetTopTerms, putem vedea cele 10 cuvinte principale pentru fiecare subiect și putem estima semantica subiectului prin ele:
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
Următorul tabel detaliază cele mai importante cinci subiecte detectate și cele 10 cuvinte principale care le exemplifica:
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | messi | messi | messi | messi | messi |
2 | lionel | ligă | EST | psg | |
3 | lionel_messi | post | victorie | il | Leu |
4 | psg | milion | obiective | au | leo_messi |
5 | Madrid | îi place | cap | se toarnă | acum |
6 | real | spo | ionii | pas | compa |
7 | Barcelona | capră | ch_ioni | cu | va |
8 | Paris | psg | ucl | du | ser |
9 | real Madrid | bar | balon | qui | jucător |
10 | mbapp | mai mare | lume | je | mai bine |
Deși majoritatea utilizatorilor din această comunitate sunt vorbitori de engleză, există încă un număr de vorbitori de franceză și spaniolă (t_4 și t_5 în tabel). Putem deduce că primul subiect se referă la echipa anterioară a lui Messi (FC Barcelona), al doilea subiect este despre postarea lui Messi pe Instagram, iar al treilea subiect se concentrează pe realizările lui Messi.
Acum că avem subiectele, putem prezice care dintre ele a fost cel mai discutat. Pentru a face asta, mai întâi vom concatena tweet-urile utilizatorilor (din nou, din cea mai mare comunitate):
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 = " "))
Apoi, dezinfectăm textul ca înainte și creăm DTM-ul. După aceea, numim funcția de predict
folosind modelul nostru LDA și DTM-ul ca argumente. De asemenea, am setat metoda la Gibbs pentru a îmbunătăți timpul de calcul, deoarece avem mult text de analizat:
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)
Acum, în cadrul de date com1.users.topics
, vedem cât de mult a vorbit fiecare utilizator despre fiecare subiect:
Cont | t_1 | t_2 | t_3 | t_4 | t_5 | […] |
---|---|---|---|---|---|---|
___99 | 0,02716049 | 0,86666666 | 0,00246913 | 0,00246913 | 0,00246913 | |
șeful__ | 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 | |
[…] |
În cele din urmă, cu aceste informații, putem crea un nou atribut pe graficul nodului pentru a defini despre ce subiect s-a vorbit cel mai mult de către care utilizator. Apoi putem crea un nou fișier GML pentru a-l vizualiza în 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")
Deducerea subiectelor importante și aplicarea centralității rețelei sociale
În prima parte a acestei serii, am învățat cum să obținem date de pe Twitter, să creăm graficul de interacțiune, să-l trasăm prin Gephi și să detectăm comunități și utilizatori importanți. În această tranșă, am extins această analiză demonstrând utilizarea unor criterii suplimentare pentru a detecta utilizatorii influenți. De asemenea, am demonstrat cum să detectăm și să deducem despre ce vorbeau utilizatorii și cum să trasăm asta în rețea.
În următorul nostru articol, vom continua să aprofundăm această analiză, arătând cum utilizatorii pot detecta flagelul rețelelor sociale: spamboții și trolii.
Citiți suplimentare pe blogul Toptal Engineering:
- Analiza rețelelor sociale folosind Power BI și R: un ghid vizual personalizat