Entendendo a dinâmica do Twitter com R e Gephi: análise de texto e centralidade

Publicados: 2022-07-22

Este artigo expande e aprofunda a análise apresentada na primeira parte de nossa série de análise de redes sociais. Usamos o mesmo conjunto de dados do Twitter e rede de interação construídos no primeiro artigo. Porém, desta vez, a ideia é inferir melhor os atores principais, identificar seus tópicos de discussão e entender como esses tópicos se espalham.

Centralidade da Rede Social

Para atingir nossos objetivos, primeiro precisamos introduzir o conceito de centralidade . Na ciência da rede, a centralidade refere-se a nós que têm uma forte influência na rede. A influência é um conceito ambíguo; pode ser entendido de muitas maneiras. Um nó com muitas arestas é mais influente do que um nó com menos arestas, porém mais “importantes”? O que constitui uma vantagem importante em uma rede social?

Para lidar com essas ambiguidades, os cientistas de rede desenvolveram muitas medidas de centralidade. Aqui, discutimos quatro medidas comumente usadas, embora muitas outras estejam disponíveis.

Grau

A medida mais comum e intuitiva é a centralidade de grau. A ideia por trás da centralidade de grau é simples: meça a influência pelo grau do nó. Pode ter variantes se o gráfico for direcionado; nesse caso, você pode medir o grau de entrada e o grau de saída — o primeiro é conhecido como pontuação de hub e o segundo como pontuação de autoridade.

Na primeira parte desta série, usamos a abordagem não direcionada. Desta vez, focamos na abordagem indegree. Isso permite uma análise mais precisa, enfatizando os usuários que são retweetados por outras pessoas sobre os usuários que apenas retuítam com frequência.

Vetor próprio

A medida de autovetor baseia-se na centralidade de grau. Quanto mais nós influentes apontarem para um determinado nó, maior será sua pontuação. Começamos com uma matriz de adjacência , onde linhas e colunas representam nós, e usamos 1 ou 0 para indicar se os nós correspondentes de uma determinada linha e coluna estão conectados. O cálculo principal estima os autovetores da matriz. O autovetor principal conterá as medidas de centralidade que queremos, onde a posição i conterá a pontuação de centralidade do nó i .

Ranking da página

PageRank é a variação da medida de autovetor no núcleo do Google. O método exato que o Google usa é desconhecido, mas a ideia geral é que cada nó comece com uma pontuação de 1 e depois distribua sua pontuação em partes iguais para cada uma de suas arestas. Por exemplo, se um nó tem três arestas que se estendem a partir dele, ele “envia” um terço de sua pontuação através de cada aresta. Ao mesmo tempo, o nó se torna mais importante pelas arestas que apontam para ele. Isso resulta em um sistema solucionável de N equações com N incógnitas.

Intermediação

A quarta medida, intermediação , usa uma abordagem muito diferente. Aqui, um nó é considerado influente se estiver incluído em muitos caminhos curtos entre outros nós. Ou seja, é responsável por se comunicar com muitos outros nós, conectando “diferentes mundos”.

Por exemplo, na análise de redes sociais, esses tipos de nós podem ser entendidos como os tipos de pessoas que ajudam os outros a encontrar novos empregos ou fazer novas conexões – eles são as portas para círculos sociais anteriormente desconhecidos.

Qual Devo Usar?

A medida de centralidade apropriada depende do objetivo de sua análise. Você quer saber quais usuários são frequentemente apontados por outros em termos de quantidade? A centralidade de grau provavelmente seria sua melhor opção. Ou você prefere uma medida de centralidade que considere a qualidade? Nesse caso, o eigenvector ou o PageRank produzirão melhores resultados. Se você quiser saber quais usuários funcionam de maneira mais eficaz como pontes entre diferentes comunidades, a intermediação é sua melhor opção.

Ao usar várias medidas semelhantes, por exemplo, autovetor e PageRank, você pode estimar todas elas e ver se elas geram classificações equivalentes. Caso contrário, você pode aprofundar sua análise das diferenças ou gerar uma nova medida combinando suas pontuações.

Outra abordagem usa a análise de componentes principais para estimar qual medida fornece mais informações sobre a influência real dos nós em sua rede.

Cálculo prático de centralidade

Vamos ver como podemos calcular essas medidas usando R e RStudio. (Eles também podem ser feitos com Gephi.)

Primeiro, precisamos carregar todas as bibliotecas que usaremos ao longo deste artigo:

 library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")

Em seguida, removeremos nós isolados dos dados que usamos anteriormente, pois eles não são úteis para esta análise. Em seguida, usaremos as funções igraph betweenness , centr_eigen , page_rank e degree para estimar as medidas de centralidade. Por fim, armazenaremos as pontuações no objeto igraph e em um data frame para ver quais usuários foram os mais centrais.

 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)

Agora podemos verificar os 10 usuários mais centrais por medida:

Grau
 top_n(cent, 10 ,degr)%>% arrange(desc(degr))%>% select(degr)
Vetor próprio
 top_n(cent, 10 ,eig)%>% arrange(desc(eig))%>% select(eig)
Ranking da página
 top_n(cent, 10 ,prank)%>% arrange(desc(prank))%>% select(prank)
Intermediação
 top_n(cent, 10 ,bet)%>% arrange(desc(bet))%>% select(bet)

Os resultados:

Grau Vetor próprio Ranking da página Intermediação
ESPNFC 5892 PSG_dentro 1 mundodabola 0,037 vista 77704
Futebol Troll 5755 CrewsMat19 0,51 Ale Liparoti 0,026 Edmundo Oris 76425
PSG_dentro 5194 eh01195991 0,4 PSG_dentro 0,017 ba*****lla 63799
CrewsMat19 4344 mohammad135680 0,37 Roy Nemer 0,016 Francisco Caio 63081
futebol 4054 ActuFoot_ 0,34 Futebol 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 Ângela_FCB 60991
ActuFoot_ 3175 futebol 0,25 Pé Instantâneo 0,007 Zyyon_ 57269
FootyHumour 2976 Saylor Moon Army 0,22 IbaiOut 0,006 CrewsMat19 53758
mundodabola 2778 Johnsvill Pat 0,2 2010Mister Chip 0,006 Mdeen OlawaleGenericName 49572

Podemos ver que as três primeiras medidas compartilham vários usuários, como PSG_inside, ESPNFC, CrewsMat19 e TrollFootball. Podemos supor que eles tiveram uma forte influência sobre a discussão. A intermediação tem uma abordagem diferente para medir a centralidade e, portanto, não mostra tanta sobreposição com as outras técnicas.

Nota: As opiniões expressas pelas contas do Twitter mencionadas neste artigo não refletem as da Toptal ou do autor.

Nas imagens a seguir, você pode ver nosso gráfico de rede colorido original com duas sobreposições de rótulos de usuário. No primeiro, os nós são destacados por suas pontuações de PageRank e, no segundo, por suas pontuações de intermediação:

Uma imagem mostrando um gráfico de PageRank colorido, com os 10 principais usuários e suas redes em destaque. Os três maiores usuários são PSG_inside, TrollFootball e ESPNFC. O ESPNFC está localizado à esquerda do gráfico e colorido em roxo, enquanto o PSG_inside está localizado à direita dele, colorido em vermelho. O TrollFootball está localizado mais alto e à direita deles, entre os usuários das cores verde, azul e laranja.
Discussão de Messi com os 10 principais usuários do PageRank em destaque

Uma imagem mostrando um gráfico de intermediação colorido, com os 10 principais usuários e suas redes marcados e destacados. Todos os 10 principais usuários, que são mais semelhantes em tamanho do que na imagem anterior, estão localizados no canto inferior esquerdo da imagem, na cor roxa. Eles são agrupados firmemente.
Discussão de Messi com os 10 principais usuários de intermediação em destaque

Gephi pode ser usado para reproduzir essas imagens. Você pode estimar as pontuações de intermediação ou PageRank usando o botão Network Diameter no painel de estatísticas. Em seguida, você pode mostrar nomes de nós usando atributos conforme demonstrado na primeira parte desta série.

Análise de Texto: R e LDA

Também podemos analisar as discussões nas redes sociais para identificar sobre o que os usuários estão falando. Existem várias maneiras de abordar isso. Faremos a modelagem de tópicos por meio da Alocação de Dirichlet Latente (LDA), uma técnica de aprendizado de máquina não supervisionada que nos permite estimar qual conjunto de palavras tende a aparecer juntas. Então, através desse conjunto de palavras, podemos inferir o tema que está sendo discutido.

O primeiro passo é higienizar o texto. Para isso, definimos a seguinte função:

 # 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) }

Também precisamos remover palavras de parada, duplicatas e entradas vazias. Em seguida, temos que converter nosso texto em uma matriz documento-termo para ser processado pelo LDA.

Neste conjunto de dados, temos usuários falando em vários idiomas (inglês, espanhol, francês etc.). O LDA funciona melhor se nos concentrarmos em um único idioma. Vamos aplicá-lo sobre os usuários da maior comunidade detectada na primeira parte desta série, composta principalmente por contas com usuários de língua inglesa.

 # 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))

Contagens de tópicos e pontuações de coerência

O principal hiperparâmetro que precisamos definir no LDA é o número (k) de tópicos que queremos estimar. No entanto, como podemos sabê-lo de antemão? Uma abordagem comum é treinar modelos LDA sobre diferentes valores de k e medir a coerência de cada um. Faremos isso para valores k de 3 a 20, pois valores fora desse intervalo não valem a pena verificar, na minha experiência:

 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"))

Em seguida, fazemos o gráfico do valor de coerência de cada um:

 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")

Um alto valor de coerência mostra uma melhor segmentação do texto em tópicos:

Um gráfico mostrando a pontuação de coerência para diferentes tópicos. A pontuação de coerência varia de pouco mais de 0,05 em seis a sete tópicos, com três a 12 tópicos com pontuação abaixo de 0,065. A pontuação de repente atinge um pico de cerca de 0,105 para 13 tópicos. Em seguida, fica abaixo de 0,06 para 17 tópicos, até quase 0,09 para 19 tópicos e termina um pouco acima de 0,07 para 20 tópicos.

Atingimos nossa pontuação máxima de coerência com k = 13, então usaremos o modelo LDA treinado com 13 tópicos. Através da função GetTopTerms, podemos ver as 10 palavras principais de cada tópico e estimar a semântica do tópico através delas:

 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

A tabela a seguir detalha os cinco tópicos mais importantes detectados e as 10 principais palavras que os exemplificam:

t_1 t_2 t_3 t_4 t_5
1 messi messi messi messi messi
2 leão Instagram liga Husa PSG
3 Lionel Messi publicar ganhar il leão
4 PSG milhão metas au leo_messi
5 madri gosta CH derramar agora
6 real spo íons pas compa
7 barcelona cabra ch_ions avec vai
8 Paris PSG ucl du Ser
9 real Madrid bar balão qui jugador
10 mbapp Maior mundo je melhor

Embora a maioria dos usuários desta comunidade seja falante de inglês, ainda há vários falantes de francês e espanhol (t_4 e t_5 na tabela). Podemos inferir que o primeiro tópico se refere ao time anterior de Messi (FC Barcelona), o segundo tópico é sobre a postagem de Messi no Instagram e o terceiro tópico se concentra nas conquistas de Messi.

Agora que temos os tópicos, podemos prever qual deles foi o mais discutido. Para fazer isso, primeiro vamos concatenar os tweets dos usuários (mais uma vez, da maior comunidade):

 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 = " "))

Em seguida, higienizamos o texto como antes e criamos o DTM. Depois disso, chamamos a função de predict usando nosso modelo LDA e o DTM como argumentos. Além disso, definimos o método para Gibbs para melhorar o tempo de computação porque temos muito texto para analisar:

 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)

Agora, no data frame com1.users.topics , vemos o quanto cada usuário falou sobre cada tópico:

Conta t_1 t_2 t_3 t_4 t_5 […]
___99º 0,02716049 0,86666666 0,00246913 0,00246913 0,00246913
Chefe__ 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
[…]

Por fim, com essas informações, podemos criar um novo atributo no gráfico de nós para definir qual tópico foi mais falado por qual usuário. Então podemos criar um novo arquivo GML para visualizá-lo no 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") 

Um gráfico de nó colorido gerado usando Gephi, mostrando o ESPNFC como o usuário mais bem classificado pela centralidade do PageRank. O ESPNFC está localizado próximo à parte inferior da imagem, com muitos nós roxos agrupados abaixo dela.
Maior comunidade de discussão sobre Messi colorida por tópico e com usuários destacados pela centralidade do PageRank

Uma imagem mostrando a porcentagem de usuários destacados por cada cor usada no gráfico, sendo o roxo "t 6" a cor mais usada (40,53% de todos os usuários no gráfico), seguido pelo verde "t 13" em 11,02 % e azul/ciano "t 10" a 9,68%. Um "NA" cinza, na penúltima posição desta lista de 11, representa 2,25%.
Rótulos de tópicos e porcentagem de usuários para cada cor usada no gráfico

Inferindo tópicos importantes e aplicando a centralidade da rede social

Na primeira parte desta série, aprendemos como obter dados do Twitter, criar o gráfico de interação, plotá-lo através do Gephi e detectar comunidades e usuários importantes. Nesta edição, expandimos essa análise demonstrando o uso de critérios adicionais para detectar usuários influentes. Também demonstramos como detectar e inferir sobre o que os usuários estavam falando e plotar isso na rede.

Em nosso próximo artigo, continuaremos aprofundando essa análise mostrando como os usuários podem detectar o flagelo das mídias sociais: spambots e trolls.

Leitura adicional no Blog da Toptal Engineering:

  • Análise de rede social usando Power BI e R: um guia de recursos visuais personalizados