El presente posteo presenta un procesamiento que realicé en el marco de las columnas de análisis de la Usina de Percepción Ciudadana en el programa La Letra Chica de TV Ciudad durante el año 2024.
El objetivo era analizar la violencia y mensajes ofensivos hacia las mujeres políticas en redes sociales, en particular en la red social X (ex-Twitter), en contexto de campaña electoral en Uruguay (2024). Existen antecedentes de estudios al respecto en Uruguay como es el trabajo de ONU MUjeres Cuantificación y análisis de la violencia contra las mujeres políticas en redes sociales - Uruguay y en Argentina un artículo reciente (2025) que analiza el discurso de odio hacia las mujeres políticas en Twitter: Promoters and targets in the Argentinean political context.
En base a dichos antecedentes, se utilizan para este caso modelos de análisis de sentimiento y detección de mensajes de odio (hate speech) basados en aprendizaje automático, y se presentan algunas visualizaciones que tan cuenta tanto de la cantidad como del contenido de dichos mensajes. Se disponibilizan las bases de datos para replicar o ampliar procesamientos.
1. Conformación de la muestra y scrapeo de datos
A efectos de realizar un análisis que pudiera ser comparable por género, necesitaba construir una muestra balanceada entre varones y mujeres políticos/as que tuvieran una actividad importante en la red social analizada (X). Además, necesitaba considerar políticos/as de los partidos con representación parlamentaria, por lo que la muestra estuvo constituida por 6 legisladores y 6 legisladoras y, en cada caso, considerando un balance entre partidos según peso parlamentario (2 FA, 2 PN 1 PC y 1 CA). Luego de esta definición, descargué vía scraping (usando Apify) los últimos posteos orgánicos de cada figura política y los comentarios para cada una, conformando una muestra balanceada de comentarios entre géneros: 2468 comentarios a mujeres y 2140 para varones de 451 conversaciones (posteos). Los posteos fueron realizados entre marzo y abril de 2024, en contexto de campaña política previa a las elecciones internas. A continuación la tabla resumen de la muestra seleccionada.
Nombre | Partido | Género | Comentarios | Conversaciones |
---|---|---|---|---|
Carmen Asiain | Partido Nacional | Mujer | 194 | 39 |
Gloria Rodriguez | Partido Nacional | Mujer | 219 | 26 |
Lucia Etcheverry | Frente Amplio | Mujer | 918 | 78 |
Ma Eugenia Rossello | Partido Colorado | Mujer | 60 | 12 |
Micaela Melgar | Frente Amplio | Mujer | 1028 | 69 |
Silvana Perez Bonavita | Cabildo Abierto | Mujer | 49 | 26 |
Daniel Caggiani | Frente Amplio | Varon | 540 | 37 |
Martin Sodano | Cabildo Abierto | Varon | 126 | 18 |
Ope Pasquet | Partido Colorado | Varon | 319 | 29 |
Rodrigo Goñi | Partido Nacional | Varon | 71 | 30 |
Sebastian Da Silva | Partido Nacional | Varon | 826 | 56 |
Sebastian Sabini | Frente Amplio | Varon | 258 | 31 |
2. Análisis de sentimiento con pysentimiento
Luego de tener la base de datos consolidada, utilicé la librería de Python pysentimiento: A Python toolkit for Sentiment Analysis and Social NLP tasks. Tiene algunas funcionalidades de preprocesamiento y dos ventajas principales: fue entrenada con Tweets y tiene una implementación del modelo para español, por lo que su elección se ajusta a los objetivos del trabajo. Además de el análisis de sentimiento y detección de mensajes de odio, que serán las dos tareas acá utilizadas, también reliza análisis de emociones, reconocimiento de entidades, etiquetado de POS (Part of speech), entre otras. Este procedimiento lo hice con código Python (no desde R) y el código que utilicé está en este notebook de Google Colab. Es muy sencillo!
3. Análisis de resultados
La librería pysentimiento usa modelos basados en transformers en lugar de enfoques basados en diccionarios o reglas, lo que le permite captar matices y contexto en los textos. Clasifica los textos en sentimiento positivo, negativo y neutral.
En primera instancia hice un análisis de sentimiento de los comentarios a tweets y desagregé entre si fueron reacciones a legisladoras o a legisladores. Encontramos que hay un 5% de diferencia entre ambos en cuanto a comentarios negativos, siendo más negativos aquellos que son dirigidos hacia mujeres, y 3% menos de comentarios positivos en el caso de las legisladoras.
load("Datos/base_sent.RData")
base_sent <- base_sent %>%
rename(
Fecha = createdAt,
Comentario = text,
ID_Conversacion = conversationId,
Vistas = viewCount,
Nombre = nombre,
Partido = partido,
Género = sexo
)
##
sentgen = base_sent %>%
group_by(Género, sentimiento)%>%
summarise(n = n()) %>%
mutate(per = round((n / sum(n))*100 ,1) )
p1=sentgen %>%
mutate(sexo = factor(Género, levels = c("Mujer", "Varon"))) %>%
ggplot(aes(x = Género, y = per, fill = sentimiento)) +
geom_col(width = 0.7) +
geom_text(aes(label = paste0(per, "%")),
size = 5, colour = "black", fontface = "bold",
position = position_stack(vjust = 0.5)) +
scale_fill_manual(values = c("#ff9999", "#ffcc66", "#99cc99"),
name = "Sentimiento",
labels = c("Negativo", "Neutral", "Positivo")) +
labs(title = "📊 Sentimiento en Comentarios de X (Twitter) a Políticos/as Uruguayos/as",
subtitle = "Distribución del sentimiento por género en comentarios políticos - 2024",
x = NULL, y = "",
fill = "Sentimiento") +
theme_minimal(base_family = "Fira Sans Condensed", base_size = 14) +
theme(
plot.title = element_text(size = 18, face = "bold", colour = "black"),
plot.subtitle = element_text(size = 12, colour = "grey40"),
axis.text = element_text(size = 12, colour = "black"),
legend.position = "top",
panel.grid.major = element_line(color = "grey85"),
panel.grid.minor = element_blank()
) +
coord_flip()
Luego, si vamos un poco y más e identificamos la proporción de comentarios que incluyen mensajes ofensivos y de odio, también vemos que es cercano al doble entre mujeres y varones.
load("Datos/base_hate.RData")
base_hate <- base_hate %>%
rename(
Fecha = createdAt,
Comentario = text,
ID_Conversacion = conversationId,
Vistas = viewCount,
Nombre = nombre,
Partido = partido,
Género = sexo
)
##
base_hate$hate_rec=ifelse(base_hate$hate=="[]","No","Si")
hatesex = base_hate %>%
group_by(Género, hate_rec)%>%
summarise(n = n()) %>%
mutate(per = round((n / sum(n))*100 ,1) )
p2 <- hatesex %>%
filter(hate_rec == "Si") %>%
mutate(Género = factor(Género, levels = c("Mujer", "Varon"))) %>%
ggplot(aes(x = Género, y = per, fill = hate_rec)) +
geom_col(width = 0.6) +
geom_text(aes(label = paste0(per, "%")),
size = 5, colour = "black", fontface = "bold",
position = position_stack(vjust = 0.5)) +
scale_fill_manual(values = c("#d73027"), # Rojo fuerte para destacar
name = "Mensaje de odio",
labels = "Sí") +
ylim(0, 15) +
labs(title = "💬 Mensajes de odio en Comentarios de X (Twitter) a Políticos/as Uruguayos/as",
subtitle = "Proporción de mensajes de odio según el género del/la destinatario/a - 2024",
x = NULL, y = "",
fill = "Mensaje de odio",
caption = "") +
theme_minimal(base_family = "Fira Sans Condensed", base_size = 14) +
theme(
plot.title = element_text(size = 18, face = "bold", colour = "black"),
plot.subtitle = element_text(size = 12, colour = "grey40"),
plot.caption = element_text(size = 10, colour = "grey40"),
axis.text = element_text(size = 12, colour = "black"),
legend.position = "top",
panel.grid.major = element_line(color = "grey85"),
panel.grid.minor = element_blank()
) +
coord_flip()
Luego, interesaba indagar sobre el contenido de los comentarios a posteos que habían sido identificados como que incluían mensajes de odio e hice un análisis de las palabras descalificativas u ofensivas que aparecen desagregadas por género de la figura política que había realizado el posteo inicial. Se encuentran diferencias significativas, cuantitativas y cualitativas, ya que aparecen más adjetivos y términos ofensivos en el caso de las mujeres políticas. Sobre el contenido, en estas últimas se encuentra más centrado en la persona (adjetivos como rancia, boba, feminazi, borracha, estúpida), al cuerpo ( gorda, bagre, desprolija) y apelación al silencio ( callate, calladita shhhh), mientras que en el caso de los varones se centra en mayor medida en lo ideológico ( oligarca, anglosionista, traidor, delincuente). El procesamiento fue hecho con la librería de R para análisis de datos textuales quanteda y generación de nubes de palabras con wordcloud2.
library(wordcloud2)
#mujer
hate_mujer_mas = subset(base_hate,base_hate$hate=="['hateful', 'targeted', 'aggressive']" & base_hate$sexo=="Mujer")
dfm =quanteda::dfm(quanteda::tokens(hate_mujer_mas$text_proc,remove_punct = TRUE,remove_numbers = TRUE),
tolower=TRUE,verbose = FALSE) %>%
quanteda::dfm_remove(pattern = c(quanteda::stopwords("spanish")),min_nchar=3)%>%
quanteda::dfm_trim(min_termfreq = 1)
a=as.data.frame(topfeatures(dfm,423))
a$palabra=rownames(a)
dfm_select =quanteda::dfm(quanteda::tokens(hate_mujer_mas$text_proc,remove_punct = TRUE,remove_numbers = TRUE),tolower=TRUE,verbose = FALSE) %>%dfm_select(palabras$palabra)
f <- colSums(dfm)
a=wordcloud2(data.frame(names(f), f),size = 1, ellipticity = 1, shuffle = FALSE, shape = "circle",rotateRatio = 0) + WCtheme(1)
library(htmlwidgets)
saveWidget(a,"hate_mujer.html",selfcontained = F)
##varon
hate_varon_mas =subset(base_hate,(base_hate$hate=="['hateful', 'targeted', 'aggressive']" |
base_hate$hate=="['hateful', 'aggressive']" |
base_hate$hate=="['hateful', 'targeted']")
& base_hate$sexo=="Varon")
dfm_varon =quanteda::dfm(quanteda::tokens(hate_varon_mas$text_proc,remove_punct = TRUE,remove_numbers = TRUE),
tolower=TRUE,verbose = FALSE) %>%
quanteda::dfm_remove(pattern = c(quanteda::stopwords("spanish"),stopES),min_nchar=3)%>%
quanteda::dfm_trim(min_termfreq = 1)
f <- colSums(dfm_varon)
a=wordcloud2(data.frame(names(f), f),size = 1, ellipticity = 1, shuffle = FALSE, shape = "circle",rotateRatio = 0) + WCtheme(1)
saveWidget(a,"hate_varon.html",selfcontained = F)
Por último, centré el foco entre las mujeres políticas según partido, FA y Coalición, y se observa más cantidad de términos ofensivos entre las primeras, incluso cuando en la muestra son minoría, y diferencias en el tipo de palaras y adjetivos utilizados. Se presenta la visualización a continuación.
FIN! Bases, código y otros recursos disponibles para replicar o complementar el análisis, como siempre, en mi GitHub.