Comprender la dinámica de Twitter con R y Gephi: análisis de texto y centralidad

Publicado: 2022-07-22

Este artículo amplía y profundiza el análisis presentado en la primera entrega de nuestra serie de análisis de redes sociales. Usamos el mismo conjunto de datos de Twitter y la misma red de interacción construida en el primer artículo. Sin embargo, esta vez, la idea es inferir mejor a los actores principales, identificar sus temas de discusión y entender cómo se propagan estos temas.

Centralidad de redes sociales

Para lograr nuestros objetivos, primero debemos introducir el concepto de centralidad . En ciencia de redes, la centralidad se refiere a los nodos que tienen una fuerte influencia en la red. La influencia es un concepto ambiguo; se puede entender de muchas maneras. ¿Es un nodo con muchos bordes más influyente que un nodo con menos pero más "importantes" bordes? ¿Qué constituye una ventaja importante en una red social?

Para abordar estas ambigüedades, los científicos de redes han desarrollado muchas medidas de centralidad. Aquí, discutimos cuatro medidas de uso común, aunque hay muchas más disponibles.

La licenciatura

La medida más común e intuitiva es la centralidad de grado. La idea detrás de la centralidad del grado es simple: medir la influencia por el grado del nodo. Puede tener variantes si la gráfica es dirigida; en ese caso, puede medir el grado de entrada y el de salida: el primero se conoce como puntaje central y el segundo como puntaje de autoridad.

En la primera entrega de esta serie, utilizamos el enfoque no dirigido. Esta vez, nos centramos en el enfoque de grado. Esto permite un análisis más preciso al enfatizar a los usuarios que son retuiteados por otros sobre los usuarios que simplemente retuitean con frecuencia.

Vector propio

La medida del vector propio se basa en la centralidad del grado. Cuanto más apuntan los nodos influyentes a un nodo determinado, mayor es su puntuación. Comenzamos con una matriz de adyacencia , donde las filas y columnas representan nodos, y usamos un 1 o un 0 para indicar si los nodos correspondientes de una fila y columna dadas están conectados. El cálculo principal estima los vectores propios de la matriz. El vector propio principal contendrá las medidas de centralidad que queremos, donde la posición i mantendrá la puntuación de centralidad del nodo i .

Rango de página

PageRank es la variación de la medida del vector propio en el núcleo de Google. Se desconoce el método exacto que usa Google, pero la idea general es que cada nodo comienza con una puntuación de 1, luego distribuye su puntuación en partes iguales a cada uno de sus bordes. Por ejemplo, si un nodo tiene tres bordes que se extienden desde él, "envía" un tercio de su puntuación a través de cada borde. Al mismo tiempo, el nodo se vuelve más importante por los bordes que apuntan hacia él. Esto da como resultado un sistema solucionable de N ecuaciones con N incógnitas.

intermediación

La cuarta medida, la intermediación , utiliza un enfoque muy diferente. Aquí, se dice que un nodo es influyente si está incluido en muchos caminos cortos entre otros nodos. Es decir, se encarga de comunicarse con muchos otros nodos, conectando “mundos diferentes”.

Por ejemplo, en el análisis de redes sociales, este tipo de nodos podrían entenderse como los tipos de personas que ayudan a otros a encontrar nuevos trabajos o establecer nuevas conexiones: son las puertas a círculos sociales previamente desconocidos.

¿Cuál debo usar?

La medida de centralidad adecuada depende del objetivo de su análisis. ¿Quieres saber qué usuarios son señalados con frecuencia por otros en términos de cantidad? La centralidad del grado probablemente sea su mejor opción. ¿O prefiere una medida de centralidad que considere la calidad? En ese caso, el vector propio o el PageRank darán mejores resultados. Si quieres saber qué usuarios funcionan más eficazmente como puentes entre diferentes comunidades, la intermediación es tu mejor opción.

Cuando utilice varias medidas similares, por ejemplo, vector propio y PageRank, puede estimarlas todas y ver si producen clasificaciones equivalentes. Si no, puede profundizar su análisis de las diferencias o generar una nueva medida combinando sus puntajes.

Otro enfoque utiliza el análisis de componentes principales para estimar qué medida le brinda más información sobre la influencia real de los nodos en su red.

Cálculo práctico de centralidad

Veamos cómo podemos calcular estas medidas usando R y RStudio. (También se pueden hacer con Gephi).

Primero, necesitamos cargar todas las bibliotecas que vamos a usar a lo largo de este artículo:

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

A continuación, eliminaremos los nodos aislados de los datos que usamos antes, ya que no son útiles para este análisis. Luego, usaremos las funciones igraph betweenness , centr_eigen , page_rank y degree para estimar las medidas de centralidad. Finalmente, almacenaremos las puntuaciones en el objeto igraph y en un marco de datos para ver qué usuarios fueron los más centrales.

 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)

Ahora podemos consultar los 10 usuarios más centrales por cada medida:

La licenciatura
 top_n(cent, 10 ,degr)%>% arrange(desc(degr))%>% select(degr)
Vector propio
 top_n(cent, 10 ,eig)%>% arrange(desc(eig))%>% select(eig)
Rango de página
 top_n(cent, 10 ,prank)%>% arrange(desc(prank))%>% select(prank)
intermediación
 top_n(cent, 10 ,bet)%>% arrange(desc(bet))%>% select(bet)

Los resultados:

La licenciatura Vector propio Rango de página intermediación
ESPNFC 5892 PSG_dentro 1 mundodabola 0.037 vistasdey 77704
TrollFútbol 5755 TripulacionesMat19 0.51 AleLiparoti 0.026 edmund oris 76425
PSG_dentro 5194 eh01195991 0.4 PSG_dentro 0.017 bala*****lla 63799
TripulacionesMat19 4344 mohammad135680 0.37 Roy Nemer 0.016 FranciscoGaius 63081
fútbol 4054 ActuFoot_ 0.34 TrollFútbol 0.013 Yemihazán 62534
PSG_español 3616 marttvall 0.34 ESPNFC 0.01 hashtag2weet 61123
IbaiFuera 3258 ESPNFC 0.3 PSG_español 0.007 Ángela_FCB 60991
ActuFoot_ 3175 fútbol 0.25 pie instantáneo 0.007 Zyyon_ 57269
FootyHumor 2976 SaylorMoonEjército 0.22 IbaiFuera 0.006 TripulacionesMat19 53758
mundodabola 2778 JohnsvillePat 0.2 2010MisterChip 0.006 MdeenOlawale 49572

Podemos ver que las primeras tres medidas comparten una cantidad de usuarios, como PSG_inside, ESPNFC, CrewsMat19 y TrollFootball. Podemos suponer que tuvieron una fuerte influencia en la discusión. La intermediación tiene un enfoque diferente para medir la centralidad y, por lo tanto, no muestra tanta superposición con las otras técnicas.

Nota: Las opiniones expresadas por las cuentas de Twitter mencionadas en este artículo no reflejan las de Toptal ni las del autor.

En las siguientes imágenes, puede ver nuestro gráfico de red en color original con dos superposiciones de etiquetas de usuario. En el primero, los nodos se destacan por sus puntajes de PageRank, y en el segundo, por sus puntajes de intermediación:

Una imagen que muestra un diagrama de PageRank en color, con los 10 usuarios principales y sus redes resaltadas. Los tres mayores usuarios son PSG_inside, TrollFootball y ESPNFC. ESPNFC está ubicado a la izquierda de la trama y está coloreado de púrpura, mientras que PSG_inside está ubicado a la derecha de la misma, de color rojo. TrollFootball se encuentra más arriba ya la derecha de ellos, entre los usuarios de color verde, azul y naranja.
Se destaca la discusión de Messi con los 10 mejores usuarios de PageRank

Una imagen que muestra un gráfico de intermediación en color, con los 10 usuarios principales y sus redes etiquetados y resaltados. Todos los 10 usuarios principales, que son más similares en tamaño que en la imagen anterior, se encuentran en la esquina inferior izquierda de la imagen, que es de color púrpura. Están agrupados estrechamente.
Destacada discusión de Messi con los 10 mejores usuarios de betweenness

Gephi se puede utilizar para reproducir estas imágenes. Puede estimar las puntuaciones de intermediación o de PageRank mediante el botón Diámetro de la red en el panel de estadísticas. Luego, puede mostrar los nombres de los nodos usando atributos como se demostró en la primera entrega de esta serie.

Análisis de texto: R y LDA

También podemos analizar las discusiones de las redes sociales para identificar de qué han estado hablando los usuarios. Hay múltiples formas de abordar esto. Haremos un modelado de temas a través de Latent Dirichlet Allocation (LDA), una técnica de aprendizaje automático no supervisado que nos permite estimar qué conjunto de palabras tienden a aparecer juntas. Entonces, a través de ese conjunto de palabras, podemos inferir el tema que se está tratando.

El primer paso es desinfectar el texto. Para ello, definimos la siguiente función:

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

También debemos eliminar las palabras vacías, los duplicados y las entradas vacías. A continuación, tenemos que convertir nuestro texto en una matriz de término de documento para ser procesado por LDA.

En este conjunto de datos, tenemos usuarios que hablan muchos idiomas (inglés, español, francés, etc.). LDA funciona mejor si nos enfocamos en un solo idioma. Lo vamos a aplicar sobre los usuarios de la mayor comunidad detectada en la primera entrega de esta serie, que se compone principalmente de cuentas con usuarios de habla 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))

Recuentos de temas y puntuaciones de coherencia

El principal hiperparámetro que necesitamos definir en LDA es el número (k) de temas que queremos estimar. Sin embargo, ¿cómo podemos saberlo de antemano? Un enfoque común es entrenar modelos LDA sobre diferentes valores de k y medir la coherencia de cada uno. Haremos esto para valores de k de 3 a 20, ya que no vale la pena verificar los valores fuera de este rango, en mi experiencia:

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

A continuación, graficamos el valor de coherencia de cada uno:

 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 alto valor de coherencia muestra una mejor segmentación del texto en temas:

Un gráfico que muestra la puntuación de coherencia para diferentes temas. La puntuación de coherencia varía de un poco más de 0,05 en seis a siete temas, con tres a 12 temas con una puntuación inferior a 0,065. El puntaje de repente alcanza un máximo de alrededor de 0.105 para 13 temas. Luego va por debajo de 0,06 para 17 temas, hasta casi 0,09 para 19 temas y termina justo por encima de 0,07 para 20 temas.

Alcanzamos nuestro puntaje máximo de coherencia con k = 13, por lo que usaremos el modelo LDA entrenado con 13 temas. A través de la función GetTopTerms, podemos ver las 10 palabras principales de cada tema y estimar la semántica del tema a través de ellas:

 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 siguiente tabla detalla los cinco temas más importantes detectados y las 10 palabras principales que los ejemplifican:

t_1 t_2 t_3 t_4 t_5
1 messi messi messi messi messi
2 leonel instagram liga est psg
3 Lionel Messi correo victoria Illinois León
4 psg millón metas es Leo Messi
5 Madrid gustos ch verter ahora
6 real spo iones paso compa
7 Barcelona cabra ch_iones avec Virginia
8 París psg ucl du ser
9 Real Madrid bar globo qui jugador
10 mbapp más grande mundo jeje mejor

Aunque la mayoría de los usuarios de esta comunidad son angloparlantes, todavía hay algunos que hablan francés y español (t_4 y t_5 en la tabla). Podemos inferir que el primer tema se relaciona con el equipo anterior de Messi (FC Barcelona), el segundo tema se trata de la publicación de Messi en Instagram y el tercer tema se centra en los logros de Messi.

Ahora que tenemos los temas, podemos predecir cuál de ellos fue el más discutido. Para hacer eso, primero concatenaremos los tweets de los usuarios (nuevamente, de la comunidad más 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 = " "))

Luego, desinfectamos el texto como antes y creamos el DTM. Después de eso, llamamos a la función de predict utilizando nuestro modelo LDA y el DTM como argumentos. Además, configuramos el método en Gibbs para mejorar el tiempo de cálculo porque tenemos mucho texto para analizar:

 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)

Ahora, en el marco de datos com1.users.topics , vemos cuánto habló cada usuario sobre cada tema:

Cuenta t_1 t_2 t_3 t_4 t_5 […]
___99 0.02716049 0.86666666 0.00246913 0.00246913 0.00246913
Jefe__ 0.05185185 0.84197530 0.00246913 0.00246913 0.00246913
Menfis 0.00327868 0.00327868 0.03606557 0.00327868 0.00327868
___Alex1 0.00952380 0.00952380 0.00952380 0.00952380 0.00952380
[…]

Finalmente, con esta información, podemos crear un nuevo atributo en el gráfico de nodos para definir qué tema fue más hablado por qué usuario. Luego podemos crear un nuevo archivo GML para visualizarlo en 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") 

Un gráfico de nodos de colores generado con Gephi, que muestra a ESPNFC como el usuario de mayor rango según la centralidad de PageRank. ESPNFC se encuentra cerca de la parte inferior de la imagen, con muchos nodos morados agrupados debajo.
La comunidad más grande de discusión de Messi coloreada por tema y con usuarios destacados por la centralidad de PageRank

Una imagen que muestra el porcentaje de usuarios destacados por cada color utilizado en el gráfico, siendo el violeta "t 6" el color más utilizado (40,53 % de todos los usuarios en el gráfico), seguido del verde "t 13" a las 11:02 % y azul/cian "t 10" al 9,68 %. Una "NA" gris, en la penúltima posición de esta lista de 11, representa el 2,25 %.
Etiquetas de temas y porcentaje de usuarios para cada color utilizado en el gráfico

Inferir temas importantes y aplicar la centralidad de la red social

En la primera entrega de esta serie, aprendimos cómo obtener datos de Twitter, crear el gráfico de interacción, trazarlo a través de Gephi y detectar comunidades y usuarios importantes. En esta entrega, ampliamos este análisis demostrando el uso de criterios adicionales para detectar usuarios influyentes. También demostramos cómo detectar e inferir de qué estaban hablando los usuarios y representarlo en la red.

En nuestro próximo artículo seguiremos profundizando en este análisis mostrando cómo los usuarios pueden detectar el flagelo de las redes sociales: los spambots y los trolls.

Lecturas adicionales en el blog de ingeniería de Toptal:

  • Análisis de redes sociales con Power BI y R: una guía de elementos visuales personalizados