Las palabras del presidente


Las palabras son un campo de disputa, están situadas, se conectan con un pasado y construyen realidad. En esta entrada me interesa ensayar, con el objetivo de que sirva de ejemplo para mis cursos, algunas prácticas de recuperación de discursos políticos, en particular, declaraciones del presidente de la República Lacalle Pou y posterior análisis con técnicas de minería de texto usando R.

Intentaremos responder lo siguiente: ¿Cuales son las palabras que son más mencionadas por el presidente en sus declaraciones? ¿y las frases? ¿esto cambia en el tiempo? ¿qué temas se desprenden del análisis de las palabras? ¿cómo se asocian entre sí?


El ensayo se estructura en dos partes fundamentales: (1) recuperación y (2) procesamiento con visualizaciones asociadas. Para asegurar la replicabilidad del análisis se presenta el código utilizado (desplegable) y se disponibilizan en mi repositorio de GitHub las bases utilizadas para reproducir o ampliar los procesamientos.


1. Recuperación: scraping de declaraciones del presidente en YouTube

El primer desafío para cumplir con los objetivos que me había planteado es recuperar las declaraciones de Lacalle Pou en formato textual y procesable. Para ello, desde algún tiempo la plataforma YouTube permite descargar los subtítulos de los videos para diferentes idiomas por lo que esa podía ser una vía para conseguir esos discursos o declaraciones.

Para focalizar y construir un corpus de datos textuales exhaustivo de las declaraciones del presidente tenía algunas posibilidades. En primer lugar, lograr recopilar todas las url de las declaraciones haciendo una búsqueda simple en YouTube (por ej.: declaraciones+“lacalle pou”) y copiándolas a una hoja de cálculo, pero me llevaría mucho tiempo por lo que opté por intentar automatizar la descargas de url con la librería rvest de tidyverse en múltiples páginas de resultado (tal como hice y está documentado en este posteo de scraping parlamentario). Esta solución, aunque viable, no me permitía recuperar fácilmente alguna metadata que me interesaba para el análisis (fecha, resumen, canal, etc.) por lo que finalmente utilicé una herramienta de pago que se llama Apify que te da un crédito gratis suficiente para hacer varias búsquedas o scrapeos en diferentes plataformas de redes sociales (YouTube, Facebook, Instagram, Twitter) que puede ser interesante para usar como fuente en investigaciones sociales.

Esta herramienta permite descargar las url (y alguna metadata) para luego poder ejecutar las transcripciones desde R, por lo que con una búsqueda específica (declaraciones+“lacalle pou”) en 2 horas y 13 minutos me recuperó 575 resultados en formato tabulado que coincidían. Al analizar la base veo que es bastante precisa pero hay muchas declaraciones repetidas ya que existe una declaración oficial y los diferentes medios de comunicación la “levantan” y suben en su propio canal de YouTube por lo que también existían intervenciones de periodistas que me “ensuciaban” el análisis. A partir de esta constatación, opté por quedarme con las declaraciones oficiales desde la asunción del presidente (01/03/2020), subidas al canal oficial de YouTube de Presidencia de la República. Luego de hacer ese filtro inicial, me quedo con 205 enlaces y corroboro que todos los videos sean del presidente, a partir de lo cual descarto tres casos que eran de otras personas y no relevantes para mi análisis, me quedo con 202 url a videos.

Para realizar la descarga de los subtitulos de los videos uso la librería youtubecaption que trabaja sobre la librería de Python1 youtube-transcript-api y que que recupera la transcripción de forma tabulada y ordenada para cada secuencia del video, por lo que luego es necesario agrupar por el identificador y recuperar la metadata original. Hago este proceso iterando sobre mi vector de url con purrr y la función possibly() para saltar errores.

remotes::install_github("jooyoungseo/youtubecaption") #instalo
library(youtubecaption) #cargo librerías
library(dplyr)

x <- base$url ##vector de url
declara <- purrr::map(x,purrr::possibly(~get_caption(.x,language = "es"),
otherwise = NULL))%>%
bind_rows() #itero, agrupo y uno lista
Table 1: Muestra de transcripciones de videos de YouTube con youtubecaption
segment_id text start duration vid
1 reclamando una pérdida según salarial es 3.020 5.770 ZG2nJ4NsV_k
2 un tema que estamos tratando el artículo 6.930 5.629 ZG2nJ4NsV_k
3 4 del presupuesto se está hablando con 8.790 6.890 ZG2nJ4NsV_k
4 economía se estaba hablando con 12.559 5.351 ZG2nJ4NsV_k
5 presidencia de la república con delegado 15.680 6.340 ZG2nJ4NsV_k
6 de presidencia se está hablando con los 17.910 6.450 ZG2nJ4NsV_k


Luego, agrupo por identificador y pego todas las transcripciones de cada secuencia de los videos, y recupero la metadata inicial para armar mi base final. Cabe aclarar que hay 17 casos que no se recuperaron los subtítulos porque eran imágenes del presidente sin audio.

declara_final = declara %>% 
  group_by(vid) %>% 
  mutate(declara = paste0(text, collapse = " ")) %>% #pego declaraciones por identificador
  distinct(vid,declara)%>%
  left_join(base,by=c("vid"="id"))%>% ##le pego la metadata
  mutate(anio=substr(date,1,4)) #armo variable de año

2. Análisis de los datos textuales

Como punto de partida para el análisis, luego de hacer la recuperación del apartado anterior, cuento con un corpus de datos con 185 declaraciones presidenciales transcriptas que comprenden el período 2020 a 20232. En primera instancia me voy a centrar en el análisis de las palabras (frecuencia, peso relativo, evolución, correlación), para luego ir más allá, recuperando su categoría gramatical e indagar en lo que eso nos permite.

En términos de visualizaciones de los videos en YouTube, considerando todo el universo, vemos que los tres de mayor relevancia fueron las declaraciones en el marco de la cumbre de la Celac (2023) y la Cumbre de Presidentes del Mercosur (2022), que tienen un carácter internacional y por lo tanto mayor impacto, y en tercer lugar, declaraciones vinculadas al caso de corrupción de Astesiano en noviembre de 2022.

Table 2: Frecuencia de declaraciones recuperadas por año
vid title text date viewCount
-Is2R6ubWOc Palabras del presidente Lacalle Pou en la cumbre de la Celac En el marco de la VII Cumbre de Estados Latinoamericanos y Caribeños (Celac), que se realiza en la ciudad de Buenos Aires, Argentina, se expresó el presidente de la República, Luis Lacalle Pou 2023-01-24 443091
iyOWq8yPrKQ Palabras del presidente de Uruguay, Luis Lacalle Pou Este martes 6, en Montevideo, el mandatario de Uruguay, Luis Lacalle Pou, abrió la Cumbre de Presidentes de los Estados Partes del Mercosur y Estados Asociados. 2022-12-06 202972
ZMIH-HQMM2I Declaraciones del presidente de la República, Luis Lacalle Pou Tras participar en una actividad en Maltería Oriental SA, el presidente de la República, Luis Lacalle Pou, realizó declaraciones a la prensa. 2022-11-30 90095

Palabras


Como primer acercamiento me interesa focalizarme en algunas palabras específicas y observar la evolución entre los años de los cuales tenemos declaraciones, en un mapa de calor. Para ello, calculo el peso relativo de cada palabra en el total de palabras mencionadas por el presidente ese año (ya que tenemos menos declaraciones de 2023) y comparo. Tiene sentido que en los primeros años (2020 y 2021) tenga más peso las palabras pandemia y salud, mientras que social y economía empiezan a tener más relevancia desde 2022. Las palabras corrupción, sindicato e ideología tienen un peso relativo menor con respecto a las demás.

anotado %>%
  left_join(.,declara_final%>%select(anio),by=c("doc_id"="vid"))%>%
  filter(upos%in%quanteda::stopwords(language = "es")==F)%>%
  group_by(anio,lemma) %>%
  summarise(n = n()) %>%
  mutate(prop = round((n / sum(n)*100),3))%>%
  filter(lemma %in% c("libertad","ideología","pandemia","economía","social","salud",
                      "coalición","corrupción","argentina","brasil","china","reforma",
                      "sindicato"))%>% ##palabras que selecciono
group_by(anio)%>%
ggplot(aes(x = anio , y = reorder(lemma, prop))) +
  geom_tile(aes(fill = prop), color = "white", size = 1) +
  scale_fill_gradient(high = "#C77CFF",low = "white") +
  theme_bw() +
  theme(axis.ticks = element_blank(),
        panel.background = element_blank(),
        axis.title = element_blank())


Un ejercicio que podría hacer al respecto del análisis de las palabras tiene que ver con buscar la correlación en una matriz de términos (dfm), con la función textstat_simil de quanteda, de unas palabras con otras en términos de co-ocurrencia en cada una de las declaraciones públicas de Lacalle Pou. Como se observa, la palabra libertad en sus 144 menciones se asocia principalmente a nivel discursivo con la religión, las creencias, así como con otros términos que la complementan (suprema, ejercicio, demostración).

library(quanteda)

quanteda::dfm(tokens(declara_final$declara,remove_punct = T,
remove_numbers = T),tolower=TRUE,verbose = FALSE) %>%
  quanteda::dfm_remove(pattern = c(quanteda::stopwords("spanish")),
min_nchar=3)%>%
quanteda.textstats::textstat_simil(selection = "libertad",
method = "correlation",margin = "features")%>%
as.data.frame()%>%
arrange(-correlation)%>%
head(15)
Table 3: Correlación entre palabras
feature1 feature2 correlation
demostraron libertad 0.5253191
religiosas libertad 0.5244921
aseguran libertad 0.5244921
creencias libertad 0.5244921
apelar libertad 0.5192013
suprema libertad 0.5141596
país libertad 0.5117357


Como otro aspecto, me interesa indagar sobre qué peso tienen los principales temas a los que se refiere Lacalle Pou utilizando diccionarios temáticos que me van a dar información al respecto 3. Con este objetivo, armo un diccionario considerando palabras y términos asociados a 6 dimensiones (social, economía, salud, seguridad, vivienda y educación) y observo su peso general y evolución por año 4.

Se observa, como es de esperar, una predominancia de los temas vinculados a salud como consecuencia de la pandemia en los primeros años (2020-2021), mientras que la economía y la educación, esta sobre todo en el último año, cobra relevancia en sus declaraciones.

dfm=quanteda::dfm(tokens(declara_final$declara,remove_punct = T,
                         remove_numbers = T),tolower=TRUE,verbose = FALSE) %>%
  quanteda::dfm_remove(pattern = c(quanteda::stopwords("spanish")),
                       min_nchar=3)

docnames(dfm)= declara_final$vid
midic <- dictionary(list(Social = c("soci*","politica social","politicas sociales", "plan social",
                                    "planes sociales","pobreza"),
                         Economia = c("econo*","empleo", "desempleo", "crisis","fiscal","dolar*","inflación",
                                      "moned*","diner*","deuda","déficit"),
                         Seguridad=c("seguridad","robo","delincuente","inseguridad","homicidio",
                                     "rapiña","delito"),
                         Vivienda = c("vivienda","habitacional","asentamiento*"),
                         Educacion = c("educ*","transformación educativa","escuela","liceo","utu",
                                       "universi*"),
                         Salud = c("salu*","sanitari*","pandemia","vacuna*","salud pública",
                                       "mutualista","ministerio de salud")))

midic_result<-dfm_lookup(dfm,dictionary=midic)
midic_result=convert(midic_result, to = "data.frame") %>%
left_join(.,declara_final%>% dplyr::select(anio),by=c("doc_id"="vid"))
  
midic_result %>%
  dplyr::select(-doc_id)%>%
  group_by(anio)%>%
  summarise(across(where(is.numeric), ~ sum(.x, na.rm = TRUE)))%>%
  pivot_longer(-anio)%>%
  group_by(anio)%>%
  mutate(prop = round((value / sum(value)*100),1))%>%
  ggplot(aes(x = anio , y = prop,fill=name)) +
  geom_bar(position="fill", stat="identity")+
  theme_bw() + scale_y_continuous(labels = scales::percent_format(accuracy = 1))+
  theme(axis.ticks = element_blank(),
        panel.background = element_blank(),
        axis.title = element_blank(),
        legend.title = element_blank())


Categorías gramaticales


En un segundo momento, voy a ir más allá de las palabras y buscar identificar sus categorías gramaticales lo cual le aportan al análisis la capacidad, por ejemplo, de identificar secuencias y extraer a partir de ello palabras claves o frases. También, como veremos, posibilita focalizar el análisis en algunos términos específicos (adjetivos, nombres propios) y no en todas las palabras o términos que sean mencionados en los discursos.

Por lo tanto, necesito alguna herramienta que logre identificar qué categoría corresponde a cada palabra que forma parte de mi corpus, y para ello voy a utilizar un modelo pre-entrenado para español que logra hacer ese etiquetado5. Hay algunos paquetes de R para dicho fin (ej. spacyr) en base a la librería de Python spacy, pero en este caso vamos a usar udpipe con el cual obtuve mejores resultados y parece tener un mejor rendimiento en español para nuestro objetivo6. Dicho modelo definirá qué etiqueta corresponde a cada palabra según categorías universales.

Al comparar la frecuencia simple entre categorías, como se ve en el gráfico, se observa que los adjetivos que predominan son de carácter positivo ( importante, bueno, mejor, etc.), mientras que los países que más se mencionan son brasil, argentina y paraguay respectivamente y el verbo más mencionado es tener. Aunque no entra en las categorías gramaticales que estamos viendo ya que es un adverbio, la palabra obviamente es una de las más mencionadas por parte del presidente, la menciona 506 veces, es decir, más de 2.5 veces por declaración.

install.packages("udpipe")
library(udpipe)
#Descargo el modelo en una ruta determinada
modeloES <- udpipe_download_model(language = "ruta/spanish-gsd")
#Cargo el modelo desde esa ruta
modeloES <- udpipe_load_model(language = "spanish-gsd")
anotado <- udpipe_annotate(modeloES, x = declara_final$declara,
doc_id = declara_final$vid)%>%
as.data.frame()

##Gráfico según categoría gramatical

anotado %>%
  filter(upos %in% c("NOUN","PROPN","ADJ","VERB"))%>%
  mutate(upos = recode(upos, NOUN = 'Sustantivos', PROPN = 'Nombres propios', 
  ADJ =  'Adjetivos',VERB =  'Verbos' ))%>%
  filter(upos%in%quanteda::stopwords(language = "es")==F)%>%
  group_by(lemma,upos) %>%
  summarize(n=n())%>%
  group_by(upos)%>%
  top_n(15, abs(n)) %>%
  ggplot(aes(reorder(lemma, n), n,fill=upos)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~ upos, scales = "free") +
  xlab("") +
  ylab("") +
  theme_bw() +
  coord_flip()


Si quiero extraer los términos claves que menciona Lacalle Pou en sus declaraciones, me quedo únicamente con los sustantivos, adjetivos y nombres propios (sub categoría de los sustantivos), y utilizo la función keywords_rake del mismo paquete udpipe que construye una métrica ( ranke) que surge de combinar la frecuencia de aparición de una palabra y la co-ocurrencia con otras en un documento determinado, en este caso, en cada declaración. Como criterio general, voy a considerar términos claves de hasta tres palabras y opto por quedarme con el lema de cada una, el cual corresponde a la forma común (ej. público) a todas las formas que puede adquirir una palabra (públicos, pública, pública, etc.) y así evitar repeticiones.

Las palabras claves nos dan una idea de las temáticas que trata el presidente en sus declaraciones públicas y su jerarquización en términos de presencia mayoritaria de ciertos términos. Lo sanitario aparece en primer lugar, vinculado a la pandemia, seguido por la ley de urgente consideración, así como tópicos sobre impuestos, aranceles y comercio exterior.

udpipe::keywords_rake(x = anotado %>%filter(lemma %in%quanteda::stopwords(language = "es")==F),term = "lemma", group = "doc_id", 
relevant = anotado %>%filter(lemma %in%quanteda::stopwords(language = "es")==F)%>% pull(upos) %in% c("NOUN", "ADJ","PROPN"),
ngram_max = 3)%>% # Sólo sustantivos, adjetivos y nombres propios
filter(freq>4)%>%
  arrange(rake) %>%
  top_n(30, abs(rake)) %>%
  ggplot(aes(reorder(keyword, rake), rake )) +
  geom_bar(stat = "identity", show.legend = FALSE,fill= "#00bfc4") +
   scale_y_continuous(limits = c(0,NA)) +
  xlab("") +
  ylab("") +
  theme_bw() +
  coord_flip()


Otra forma de acercarnos a lo que se menciona en las declaraciones, tiene que ver con la identificación de frases que se construyen a partir de un sustantivo como núcleo. Esto lo puedo identificar utilizando expresiones regulares que me permiten extraer frases según una secuencia o patrón que combina las formas gramaticales previamente etiquetadas. Con ese fin, uso la función keywords_phrases.

library(udpipe)
anotado$phrase_tag <- udpipe::as_phrasemachine(anotado$upos, type = "upos")
anotado$phrase_tag[anotado$upos=="PRON"] <- "O"
anotado$phrase_tag[anotado$upos=="NUM"] <- "O"
udpipe::keywords_phrases(x = anotado$phrase_tag, term = tolower(anotado$token),
pattern = "(A|N)*N(P+D*(A|N)*N)*",
is_regex = TRUE, detailed = FALSE)%>%
filter(ngram > 1 & freq > 3)%>%
arrange(freq) %>%
top_n(30, abs(freq)) %>%
ggplot(aes(reorder(keyword, freq), freq ,fill="#00BFC4")) +
geom_bar(stat = "identity", show.legend = FALSE) +
xlab("") +
ylab("") +
theme_bw() +
coord_flip()


También podríamos indagar un poco más el contexto de aparición de ciertas palabras o expresiones con la función kwic ( keyword in context) del paquete quanteda. Esta técnica nos permite focalizar en una palabra o frase y analizar el contexto en que se menciona en un determinado texto, definiendo una ventana (cantidad de palabras anteriores y posteriores), pudiendo también constituir un nuevo corpus de datos textuales procesable.

library(quanteda)

kwic=quanteda::kwic(declara_final$declara,phrase("sindi*"),window = 15)%>% as.data.frame() ##el * hace que busque todas la variantes con una determinada raíz
Table 4: Contexto de palabra clave
from to pre keyword post
351 351 que empezaron las clases presidente comenzó con un par de actividades por parte de los sindicatos de Montevideo sobre todo de secundaria de usted teme que esta situación de los sindicatos
366 366 sindicatos de Montevideo sobre todo de secundaria de usted teme que esta situación de los sindicatos frenen algo en la transformación educativa y la postura que están teniendo muy crítica temor
1189 1189 desalojar como establece la ley Así que el juego de la Democracia es este los sindicatos tienen la defensa no sé si todos los docentes o todo el personal no docente
1518 1518 trabajo en conjunto el Ministerio de economía la anep el Ministerio de trabajo y los sindicatos se inició la recuperación salarial para docentes y funcionarios se terminaba en la desigualdades históricas
1768 1768 reglamentación del artículo 57 de la Constitución que es la personería jurídica de las organizaciones sindicales yo creo que eso va a generar un funcionamiento mucho más garantista el año pasado

Es posible analizar, por ejemplo, qué adjetivos o verbos menciona el presidente en torno a la palabras “sindicato” (podrían también ser términos multi-palabra como “reforma de la seguridad social” o “transformación educativa”).

library(ggplot2)
b=quanteda::kwic(declara_final$declara,phrase("sindi*"),window = 30)
b$texto=paste(b$pre,b$post)

sindi <- udpipe_annotate(modeloES,x = b$texto,
                           doc_id = b$docname)
sindi <- as.data.frame(sindi)
sindi %>%
  filter(upos %in% c("ADJ","VERB"))%>%
  mutate(upos = recode(upos, ADJ =  'Adjetivos',VERB =  'Verbos' ))%>%
  filter(upos%in%quanteda::stopwords(language = "es")==F)%>%
  group_by(lemma,upos) %>%
  summarize(n=n())%>%
  group_by(upos)%>%
  top_n(12, abs(n)) %>%
  ggplot(aes(reorder(lemma, n), n,fill=upos)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~ upos, scales = "free",ncol = 2) +
  xlab("") +
  ylab("") +
  theme_bw() +
  coord_flip()



Hasta aquí algunos de los ensayos de recuperación y análisis de los datos textuales. Las técnicas que explora el posteo resultan útiles para realizar un análisis de contenido y reducción de texto inicial y rápido de los principales tópicos, términos y sus combinaciones, agrupamientos de palabras, con visualizaciones ilustrativas que ayudan a interpretar los resultados. Pueden complementarse o combinarse con un análisis e interpretación en profundidad de los datos textuales, por ejemplo, de forma asistida con la herramienta libre de análisis cualitativo RQDA, lo cual puede retroalimentar el análisis, a partir de identificar categorías teóricas de forma sistemática que se integren y oficien de variables de corte para las técnicas y procesamientos presentados.



  1. Para conectar R con Python existen algunas posibilidades, en este caso usamos la librería reticulate el cual permite la instalación de miniconda o la interfaz Anaconda para gestionar los paquetes.↩︎

  2. Para el año 2020 se recuperaron sólo 5 casos por lo que, cuando existan cruces por año, opto presentarlos en conjunto con los de 2021. Esto puede deberse a que se registró una baja en las declaraciones en situación de pandemia, existiendo una comunicación más centralizada, y en algunos casos, no aparecía la palabra declaraciones por lo que el filtro sub registró esos casos al automatizar la búsqueda.↩︎

  3. En español hay poco o nulo desarrollo de diccionarios de temas y es una línea importante a trabajar. Existe un proyecto interesante denominado Comparative Agendas Project que disponibiliza un diccionario de al momento de 21 temas y 220 subtemas para inglés y holandés.↩︎

  4. El utilizado es un diccionario preliminar que podría ampliarse y mejorarse para obtener mejores resultados e interpretaciones analíticas.↩︎

  5. Ver métricas de comparación entre las dos librerías en: https://www.bnosac.be/index.php/blog/75-a-comparison-between-spacy-and-udpipe-for-natural-language-processing-for-r-users↩︎

  6. En el área de Procesamiento de Lenguaje Natural (PLN) estas técnicas se utilizan para analizar el contexto, identificando dependencias y hacer predicciones a partir de esto. En este caso simplemente analizaremos las palabras más mencionadas asociadas a cada categoría gramatical.↩︎

Avatar
Elina Gómez
Socióloga. MSc, PhD(c)

Socióloga

Relacionado