Zrozumienie dynamiki Twittera za pomocą R i Gephi: analiza i centralność tekstu
Opublikowany: 2022-07-22Ten artykuł rozwija i pogłębia analizę przedstawioną w pierwszej części naszej serii analiz sieci społecznościowych. Korzystamy z tego samego zestawu danych i sieci interakcji Twittera, które zostały skonstruowane w pierwszym artykule. Jednak tym razem chodzi o lepsze wywnioskowanie głównych aktorów, zidentyfikowanie ich tematów do dyskusji i zrozumienie, w jaki sposób te tematy się rozprzestrzeniają.
Centralna sieć społecznościowa
Aby osiągnąć nasze cele, najpierw musimy wprowadzić pojęcie centralności . W nauce o sieciach centralność odnosi się do węzłów, które mają silny wpływ na sieć. Wpływ jest pojęciem niejednoznacznym; można to rozumieć na wiele sposobów. Czy węzeł z wieloma krawędziami ma większy wpływ niż węzeł z mniejszą liczbą, ale bardziej „ważnych” krawędzi? Co stanowi ważną przewagę w sieci społecznościowej?
Aby rozwiązać te niejasności, naukowcy sieci opracowali wiele miar centralności. W tym miejscu omówimy cztery powszechnie stosowane miary, choć dostępnych jest znacznie więcej.
Stopień
Najbardziej powszechną i intuicyjną miarą jest centralność stopni. Idea centralności stopni jest prosta: zmierz wpływ stopniem węzła. Może mieć warianty, jeśli wykres jest skierowany; w takim przypadku możesz zmierzyć stopień wejściowy i stopień wyjściowy — pierwszy jest znany jako wynik centralny, a drugi jako wynik autorytetu.
W pierwszej odsłonie tej serii zastosowaliśmy podejście nieukierunkowane. Tym razem skupiamy się na podejściu stopniowym. Pozwala to na dokładniejszą analizę, kładąc nacisk na użytkowników, którzy są retweetowani przez innych, na użytkowników, którzy po prostu retweetują często.
Wektor własny
Miara wektora własnego opiera się na centralności stopnia. Im więcej wpływowych węzłów wskazuje na dany węzeł, tym wyższy jest jego wynik. Zaczynamy od macierzy sąsiedztwa , w której wiersze i kolumny reprezentują węzły i używamy 1 lub 0, aby wskazać, czy odpowiednie węzły danego wiersza i kolumny są połączone. Główne obliczenie szacuje wektory własne macierzy. Główny wektor własny będzie zawierał miary centralności, które chcemy, gdzie pozycja i będzie zawierała wynik centralności węzła i .
PageRank
PageRank to odmiana miary wektora własnego w rdzeniu Google. Dokładna metoda stosowana przez Google jest nieznana, ale ogólna idea polega na tym, że każdy węzeł zaczyna się z wynikiem 1, a następnie rozdziela swój wynik w równych częściach na każdą ze swoich krawędzi. Na przykład, jeśli węzeł ma trzy wystające z niego krawędzie, „przesyła” jedną trzecią swojego wyniku przez każdą krawędź. Jednocześnie węzeł jest ważniejszy przez krawędzie, które na niego wskazują. Daje to rozwiązywalny układ N równań z N niewiadomymi.
Pomiędzy
Czwarta miara, pośredniość , wykorzystuje zupełnie inne podejście. Tutaj mówi się, że węzeł ma wpływ, jeśli znajduje się na wielu krótkich ścieżkach między innymi węzłami. Oznacza to, że odpowiada za komunikację z wieloma innymi węzłami, łącząc „różne światy”.
Na przykład w analizie sieci społecznościowych tego rodzaju węzły można rozumieć jako typy ludzi, którzy pomagają innym znaleźć nową pracę lub nawiązać nowe kontakty — są to drzwi do nieznanych wcześniej kręgów społecznych.
Którego powinienem użyć?
Odpowiednia miara centralności zależy od celu analizy. Chcesz wiedzieć, którzy użytkownicy są często wyróżniani przez innych pod względem ilości? Centralność stopnia prawdopodobnie byłaby najlepszą opcją. A może wolisz centralną miarę uwzględniającą jakość? W takim przypadku wektor własny lub PageRank dadzą lepsze wyniki. Jeśli chcesz wiedzieć, którzy użytkownicy działają najskuteczniej jako pomosty między różnymi społecznościami, najlepszą opcją jest pośrednictwo.
Używając wielu podobnych miar, np. wektora własnego i PageRank, możesz oszacować je wszystkie i sprawdzić, czy dają równoważne rankingi. Jeśli nie, możesz pogłębić analizę różnic lub wygenerować nową miarę, łącząc ich wyniki.
Inne podejście wykorzystuje analizę głównych komponentów do oszacowania, która miara daje więcej informacji o rzeczywistym wpływie węzłów na twoją sieć.
Praktyczne obliczanie centralności
Zobaczmy, jak możemy obliczyć te miary za pomocą R i RStudio. (Można to również zrobić za pomocą Gephi.)
Najpierw musimy załadować wszystkie biblioteki, których będziemy używać w tym artykule:
library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")
Następnie usuniemy izolowane węzły z danych, których używaliśmy wcześniej, ponieważ nie są one przydatne do tej analizy. Następnie użyjemy funkcji igraph
betweenness
, centr_eigen
, page_rank
, i degree
do oszacowania miar centralności. Na koniec przechowamy wyniki w obiekcie igraph
i w ramce danych, aby zobaczyć, którzy użytkownicy byli najbardziej centralni.
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)
Teraz możemy sprawdzić 10 najbardziej centralnych użytkowników według każdej miary:
Stopień | |
Wektor własny | |
PageRank | |
Pomiędzy | |
Wyniki:
Stopień | Wektor własny | PageRank | Pomiędzy | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_wewnątrz | 1 | mundodabola | 0,037 | widokidey | 77704 |
TrollPiłka Nożna | 5755 | ZałogiMat19 | 0,51 | AleLiparoti | 0,026 | Edmund Oris | 76425 |
PSG_wewnątrz | 5194 | eh01195991 | 0,4 | PSG_wewnątrz | 0,017 | ba*****lla | 63799 |
ZałogiMat19 | 4344 | mohammad135680 | 0,37 | RoyNemer | 0,016 | Francisco Gaius | 63081 |
brfutbol | 4054 | AktuStopa_ | 0,34 | TrollPiłka Nożna | 0,013 | Jemihazan | 62534 |
PSG_espanol | 3616 | marttvall | 0,34 | ESPNFC | 0,01 | hashtag2weet | 61123 |
IbaiOut | 3258 | ESPNFC | 0,3 | PSG_espanol | 0,007 | Angela_FCB | 60991 |
AktuStopa_ | 3175 | brfutbol | 0,25 | Natychmiastowa stopa | 0,007 | Zjon_ | 57269 |
FootyHumour | 2976 | SaylorMoonArmy | 0,22 | IbaiOut | 0,006 | ZałogiMat19 | 53758 |
mundodabola | 2778 | JohnsvillPat | 0,2 | 2010MisterChip | 0,006 | MdeenOlawale | 49572 |
Widzimy, że pierwsze trzy miary mają wspólną liczbę użytkowników, takich jak PSG_inside, ESPNFC, CrewsMat19 i TrollFootball. Można przypuszczać, że mieli duży wpływ na dyskusję. Pomiędzy ma inne podejście do mierzenia centralności i dlatego nie wykazuje tak dużego pokrywania się z innymi technikami.
Uwaga: Poglądy wyrażane przez konta na Twitterze wymienione w tym artykule nie odzwierciedlają poglądów Toptala ani autora.
Na poniższych obrazach możesz zobaczyć nasz oryginalny kolorowy wykres sieci z dwiema nakładkami etykiet użytkownika. W pierwszym węzły są wyróżnione przez ich wyniki PageRank, a w drugim przez ich wyniki pośrednie:
Gephi można wykorzystać do odtworzenia tych obrazów. Możesz oszacować wyniki pośrednie lub PageRank za pomocą przycisku Średnica sieci w panelu statystyk. Następnie możesz pokazać nazwy węzłów za pomocą atrybutów, jak pokazano w pierwszej części z tej serii.
Analiza tekstu: R i LDA
Możemy również analizować dyskusje w sieciach społecznościowych, aby zidentyfikować, o czym rozmawiali użytkownicy. Można do tego podejść na wiele sposobów. Zajmiemy się modelowaniem tematów za pomocą Latent Dirichlet Allocation (LDA), nienadzorowanej techniki uczenia maszynowego, która pozwala nam oszacować, który zestaw słów pojawia się razem. Następnie za pomocą tego zestawu słów możemy wywnioskować omawiany temat.
Pierwszym krokiem jest oczyszczenie tekstu. W tym celu definiujemy następującą funkcję:
# 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) }
Musimy również usunąć słowa stop, duplikaty i puste wpisy. Następnie musimy przekonwertować nasz tekst na macierz dokumentu-terminów, która ma być przetworzona przez LDA.

W tym zestawie danych mamy użytkowników mówiących w wielu językach (angielskim, hiszpańskim, francuskim itp.). LDA działa najlepiej, jeśli skupimy się na jednym języku. Zamierzamy ją zastosować na użytkownikach największej społeczności wykrytej w pierwszej odsłonie tej serii, która składa się głównie z kont użytkowników anglojęzycznych.
# 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))
Liczba tematów i wyniki spójności
Głównym hiperparametrem, który musimy zdefiniować w LDA, jest liczba (k) tematów, które chcemy oszacować. Skąd jednak możemy to wiedzieć wcześniej? Jednym z powszechnych podejść jest trenowanie modeli LDA na różnych wartościach k i mierzenie spójności każdej z nich. Zrobimy to dla wartości k od 3 do 20, ponieważ z mojego doświadczenia wynika, że wartości spoza tego zakresu nie są warte sprawdzania:
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"))
Następnie wykreślamy wartość koherencji każdego z nich:
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")
Wysoka wartość spójności świadczy o lepszej segmentacji tekstu na tematy:
Szczytowy wynik koherencji osiągamy przy k = 13, więc użyjemy modelu LDA wytrenowanego na 13 tematach. Dzięki funkcji GetTopTerms możemy zobaczyć 10 głównych słów dla każdego tematu i oszacować za ich pomocą semantykę tematu:
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
W poniższej tabeli wyszczególniono pięć najważniejszych wykrytych tematów i 10 głównych słów, które je ilustrują:
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | Messi | Messi | Messi | Messi | Messi |
2 | lionel | liga | oszacować | psg | |
3 | Lionel Messi | Poczta | wygrać | il | Lew |
4 | psg | milion | cele | Au | Leo Messi |
5 | Madryt | lubi | ch | wlać | ahora |
6 | prawdziwy | spo | jony | pierwszeństwo | kompania |
7 | Barcelona | koza | ch_jony | avec | wa |
8 | Paryż | psg | ucl | du | ser |
9 | Real Madryt | bar | balon | Qui | jugador |
10 | mbapp | większy | świat | je | mejor |
Chociaż większość użytkowników w tej społeczności to osoby posługujące się językiem angielskim, nadal jest wielu użytkowników języka francuskiego i hiszpańskiego (t_4 i t_5 w tabeli). Możemy wywnioskować, że pierwszy temat dotyczy poprzedniego zespołu Messiego (FC Barcelona), drugi dotyczy posta Messiego na Instagramie, a trzeci temat skupia się na osiągnięciach Messiego.
Teraz, gdy mamy tematy, możemy przewidzieć, który z nich był najczęściej omawiany. Aby to zrobić, najpierw połączymy tweety użytkowników (ponownie z największej społeczności):
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 = " "))
Następnie oczyszczamy tekst jak poprzednio i tworzymy DTM. Następnie wywołujemy funkcję predict
, używając naszego modelu LDA i DTM jako argumentów. Ponadto ustawiamy metodę na Gibbs, aby skrócić czas obliczeń, ponieważ mamy dużo tekstu do analizy:
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)
Teraz w ramce danych com1.users.topics
widzimy, ile każdy użytkownik mówił na każdy temat:
Rachunek | t_1 | t_2 | t_3 | t_4 | t_5 | […] |
---|---|---|---|---|---|---|
___99. | 0,02716049 | 0.86666666 | 0,00246913 | 0,00246913 | 0,00246913 | |
Szef__ | 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 | |
[…] |
Na koniec, korzystając z tych informacji, możemy utworzyć nowy atrybut na wykresie węzła, aby zdefiniować, o którym temacie najczęściej rozmawiał dany użytkownik. Następnie możemy stworzyć nowy plik GML, aby zwizualizować go w 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")
Wnioskowanie ważnych tematów i stosowanie centralnych sieci społecznościowych
W pierwszej odsłonie tej serii dowiedzieliśmy się, jak pozyskiwać dane z Twittera, tworzyć wykres interakcji, wykreślać go za pośrednictwem Gephi oraz wykrywać społeczności i ważnych użytkowników. W tej części rozszerzyliśmy tę analizę, demonstrując zastosowanie dodatkowych kryteriów do wykrywania wpływowych użytkowników. Pokazaliśmy również, jak wykryć i wnioskować, o czym mówią użytkownicy, i nakreślić to w sieci.
W następnym artykule będziemy nadal pogłębiać tę analizę, pokazując, w jaki sposób użytkownicy mogą wykryć plagę mediów społecznościowych: spamboty i trolle.
Dalsza lektura na blogu Toptal Engineering:
- Analiza sieci społecznościowych za pomocą Power BI i R: przewodnik po niestandardowych wizualizacjach