Para practicar técnicas de procesamiento y análisis computacional de textos, vamos a trabajar con un dataset de letras de tango. La información fue recopilada a partir de las letras publicadas en el sitio Todo Tango, por el Dr. Germán Rosatti.

Nuestro datset de tangos

Vamos a usar dos paquetes: tidyverse como siempre, y tidytext para aprovechar sus funciones especializdas en análisis de texto. Si nos falta alguno, lo instalamos antes con la función instal.packages() y el nombre del paquete a instalar entre comillas: install.packages("tidytext")

library(tidyverse)
library(tidytext)

Cargamos los datos con los que vamos a trabajar:

tangos <- read.csv("https://github.com/gefero/tango_scrape/raw/master/Data/letras_final.csv",
                   stringsAsFactors = FALSE)

y echamos un vistazo

summary(tangos)
##      link              titulo             ritmo          
##  Length:10056       Length:10056       Length:10056      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##      ano               musica           compositor       
##  Length:10056       Length:10056       Length:10056      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##     letra          
##  Length:10056      
##  Class :character  
##  Mode  :character

Tenemos 10056 tangos, con 7 variables representadas, que lucen así:

head(tangos)
##                                                                link
## 1                http://www.todotango.com/musica/tema/130/A-bailar/
## 2      http://www.todotango.com/musica/tema/7866/A-bailar-el-tango/
## 3             http://www.todotango.com/musica/tema/7095/A-bailarlo/
## 4          http://www.todotango.com/musica/tema/3647/A-Beba-Bidart/
## 5     http://www.todotango.com/musica/tema/5634/A-Belisario-Roldan/
## 6 http://www.todotango.com/musica/tema/2864/A-bordo-de-mis-zapatos/
##                   titulo          ritmo   ano              musica
## 1               a bailar          tango  1943    domingo federico
## 2      a bailar el tango          tango  1962 leopoldo diaz velez
## 3             a bailarlo          tango vacio          jose dames
## 4          a beba bidart poema lunfardo vacio               vacio
## 5     a belisario roldan          tango vacio       enrique forte
## 6 a bordo de mis zapatos        milonga vacio        cesar isella
##            compositor
## 1     homero exposito
## 2 leopoldo diaz velez
## 3               vacio
## 4      alberto uemura
## 5               vacio
## 6       eugenio majul
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            letra
## 1                                                                                                                                        a bailar a bailar | que la orquesta se va | sobre el fino garabato | de un tango nervioso y lerdo | se ira borrando el recuerdo | a bailar a bailar | que la orquesta se va | el ultimo tango perfuma la noche | un tango dulce que dice adios | la frase callada se asoma a los labios | y canta el tango la despedida! | vamos! a bailar! | tal vez no vuelvas a verla nunca | y el ultimo tango perfuma la noche | y este es el tango que dice el adios | a bailar a bailar | que la orquesta se va! | quedara el salon vacio | con un monton de esperanzas | que iran camino al olvido | a bailar a bailar | que la orquesta se va!
## 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                           este tango nacio para bailarse | y asi hamacarse muy suavemente | oigan ustedes este compas… | es muy sencillo bailar el tango | un doble paso despues descanso | la media vuelta la vuelta entera | y siempre junto a la compañera | este tango nacio para bailarse | no hay que quedarse mirandolo
## 3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
## 4 nacio en la calle quito | entre boedo y colombres | barrio de tauras de hombres | de timbas y de garitos | mi recuerdo es muy estricto | de proscenio un corralon | modesto fue su blason | y la dulce purretita | se lavaba la carita | en el viejo pileton | amante del variete | soñaba con ser artista | comenzo como corista | hasta llegar a vedette | piernas tipo mistinguette | cintura bien contorneada | anatomia envidiada | y un rostro angelical | para que plumas y percal | lucieran como hermanadas | siempre causo sensacion | en cine radio y teatro; | se volco al dos por cuatro | con sentida emocion | triunfo en television | y nadie podra dudar | fue figura consular | en todos los escenarios | recogio aplausos a diario | se llamaba beba bidart
## 5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
## 6                                                                                                                                                                                                                            camine todas las calles\nde buenos aires y es cierto\na bordo de mis zapatos\nque siempre llegan a viejo\n\nnaci en palermo hace mucho\name y a veces me amaron\na bordo de mis zapatos\nvoy o no voy pero avanzo\n\ndos desencuentros o mas\nno del todo me curtieron\npor eso lloran a veces\nmis lagrimas del silencio\n\naunque una luz al instante\ncambia el paisaje de veras\na bordo de mis zapatos\nsigo gastando las suelas\n\nya encanecidas mis sienes\npero el verano en mis sueños\na bordo de mis zapatos\ncruzo la vida y la quiero

Agregamos una columna con la década de cada tanto, usando un truquito aritmético. Si a cualquier año se le sustrae el resto de una división entera por 10, el resultado es la década correspondiente a ese año. Por ejemplo, el resto de 1928 / 10 es 8 , y 1928 - 8 = 1920. En informática, la función que devuelve el resto de una división entera suele llamarse módulo. En R, el módulo se invoca con %%.

Creamos nuestra columna de décadas

tangos <- tangos %>% 
    mutate(ano = as.integer(ano),
           decada = ano - ano %% 10)

Notése que en la mayoría de los casos no se registra el año del tango, por lo que tampo obtenemos la década.

tangos %>% 
    count(decada, sort = TRUE)
## # A tibble: 14 x 2
##    decada     n
##     <dbl> <int>
##  1     NA  7179
##  2   1940   593
##  3   1930   541
##  4   1920   435
##  5   1950   256
##  6   2000   197
##  7   1960   194
##  8   2010   166
##  9   1980   162
## 10   1970   122
## 11   1910   101
## 12   1990    92
## 13   1900    17
## 14   1890     1

Graficamos la cantidad de tangos encontrados por década

ggplot(tangos) +
    geom_bar(aes(decada)) +
    scale_x_continuous(breaks = seq(1890, 2010, by = 10)) +
    labs(title = "Tangos por década", 
         x = "década",
         y = "cantidad") 

Sabiendo poco de tango, voy a asumir que de los años ’20 a los ’40 transcurrió la era dorada del género.

También podemos revisar la distribución por género.

tangos %>% 
    count(ritmo, sort = TRUE) 
## # A tibble: 93 x 2
##    ritmo              n
##    <chr>          <int>
##  1 tango           7208
##  2 vals             919
##  3 milonga          733
##  4 poema lunfardo   273
##  5 cancion          214
##  6 none              88
##  7 ranchera          72
##  8 zamba             61
##  9 candombe          53
## 10 arr en tango      42
## # ... with 83 more rows

Y realizar un ranking de los compositores más prolíficos (o al menos, los más representados en el dataset). Nuevamente se trata de una variable con mayoría de datos faltantes.

tangos %>% 
    count(compositor, sort = TRUE)
## # A tibble: 1,669 x 2
##    compositor                   n
##    <chr>                    <int>
##  1 vacio                     2035
##  2 enrique cadicamo           203
##  3 celedonio flores           118
##  4 homero manzi               113
##  5 carlos bahr                102
##  6 catulo castillo             93
##  7 francisco garcia jimenez    91
##  8 hector negro                85
##  9 horacio ferrer              85
## 10 reinaldo yiso               83
## # ... with 1,659 more rows

Preparando los datos para análisis de texto: la “tokenización”

En la jerga del análisis computacional de texto se le llama “tokenizar” (por tokenize en inglés) al acto de extraer unidades de análisis a partir de un cuerpo de escritos. Cualquiera sea el material analizado (frases, discursos o libros completos) se le extrae las palabras individuales que lo componen (“tokens”) para luego cuantificar de algún modo sus atributos.

En nuestro dataset la letra de los tangos aparece en un sólo campo, con las oraciones separadas a veces por uan barra vertical (“|”) y en ocasiones por la secuencia “”. Podemos usar separate_rows, una función que permite reorganizar una variable de modo que cada observacion se distribuya en múltiples filas, separando el valor original en base a un delimitador especificado. Usamos entonces sep_rows, con el parámetro “sep” indicando cuales son los separadores, y luego agregamos una variable nueva que ennumere las líneas de cada tango:

tangos <- separate_rows(tangos, letra, sep = "\\\\n | \\|")

tangos <- tangos %>%
    group_by(link) %>% 
    mutate(linea = row_number()) %>% 
    ungroup()

head(tangos)
## # A tibble: 6 x 9
##   link        titulo  ritmo   ano musica  compositor letra    decada linea
##   <chr>       <chr>   <chr> <int> <chr>   <chr>      <chr>     <dbl> <int>
## 1 http://www… a bail… tango  1943 doming… homero ex… a baila…   1940     1
## 2 http://www… a bail… tango  1943 doming… homero ex… " que l…   1940     2
## 3 http://www… a bail… tango  1943 doming… homero ex… " sobre…   1940     3
## 4 http://www… a bail… tango  1943 doming… homero ex… " de un…   1940     4
## 5 http://www… a bail… tango  1943 doming… homero ex… " se ir…   1940     5
## 6 http://www… a bail… tango  1943 doming… homero ex… " a bai…   1940     6

Extrayendo tokens

Ahora si, “tokenizamos”. Usamos la función unnest_tokens(), provista por el paquete tidytext, para realizar una operación similar a la anterior: ahora dividiremos cada línea de texto en múltiples filas dentro de nuestro dataframe, una por cada palabra. Esto hace posible analizar el texto de forma muy cómoda luego.

tangos_tokenizado <- tangos %>% 
    unnest_tokens(word, letra)

head(tangos_tokenizado)
## # A tibble: 6 x 9
##   link          titulo  ritmo   ano musica   compositor decada linea word 
##   <chr>         <chr>   <chr> <int> <chr>    <chr>       <dbl> <int> <chr>
## 1 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     1 a    
## 2 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     1 bail…
## 3 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     1 a    
## 4 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     1 bail…
## 5 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     2 que  
## 6 http://www.t… a bail… tango  1943 domingo… homero ex…   1940     2 la

Eliminando stopwords

El siguiente paso, común a cualquier análisis de texto, es eliminar las “stopwords”. Estas son palabras muy frecuentes que no agregan significado al texto, sino que funcionan como estructura: por ejemplo “de”, “la”, “en”, “a”, etc. Al eliminarlas del texto a analizar nos aseguramos de que no aparezcan siempre entre las palabras más frecuentes encontradas, y a cambio no perdemos nada; en la práctica, las stopwords nunca son necesarias para detectar el tema de un texto.

Descargamos una lista de stopwords del lenguaje español basada en http://snowball.tartarus.org/algorithms/spanish/stop.txt

stopwords_es <- read.csv("https://bitsandbricks.github.io/data/stopwords_es.csv",
                      stringsAsFactors = FALSE)

head(stopwords_es)
##   STOPWORD
## 1       de
## 2       la
## 3      que
## 4       el
## 5       en
## 6        y

Y con un “anti-join” eliminamos todas las palabras en el dataset de tangos que también aparecen en la lista. (El anti-join compara dos sets de datos en base a un campo en común, y elimina del primero todas las filas con valores que aparecen en el segundo)

tangos_tokenizado <- tangos_tokenizado %>% 
   anti_join(stopwords_es, by = c("word" = "STOPWORD"))

identificando las palabras más frecuentes

Habiéndonos librados de las stopwords, ya podemos hacer una lista de los términos más frecuentes en nuestra colección de letras de tango:

tangos_tokenizado %>% 
    count(word, sort = TRUE)
## # A tibble: 41,289 x 2
##    word        n
##    <chr>   <int>
##  1 amor     5491
##  2 mas      4452
##  3 vida     3557
##  4 corazon  3482
##  5 hoy      2247
##  6 vos      2195
##  7 tango    2079
##  8 alma     1957
##  9 siempre  1941
## 10 tan      1913
## # ... with 41,279 more rows

Visualizemos la palabra más frecuente por década. Usamos slice() para “cortar” una porción de la data de cada grupo. En este caso, sólo la primera fila, que por el orden establecido en el paso anterior, es la que tiene la palabra más frecuente:

tangos_tokenizado %>% 
    filter(!is.na(decada)) %>% 
    group_by(decada) %>% 
    count(word, sort = TRUE) %>% 
    slice(1) %>% 
    ggplot() +
    geom_bar(aes(x = word, weight = n, fill = word)) +
    coord_flip() +
    facet_wrap(~decada) +
    labs(title = "Letras de tango",
         subtitle = "Palabra más frecuente por década",
         x = "palabra",
         y = "veces hallada")

Análisis de sentimiento

Para asignar un “sentimiento” a cada palabra utilizaremos SDAL, un léxico de 2880 palabras. El SDAL fue producido por el Grupo del Procesamiento del Habla de la Facultad de Ciencias Exactas y Naturales (FCEyN), parte de la Universidad de Buenos Aires

Las palabras contenidas en el léxico han sido “puntuadas” manualmente asignando su valor según tres dimensiones afectivas:

  • agrado (agradable / neutra / desagradable)
  • activación (activa / neutra / pasiva)
  • imaginabilidad (fácil de imaginar / neutra / difícil de imaginar)

Los detalles al respecto de del léxico pueden encontrarse en: Agustín Gravano & Matías Dell’ Amerlina Ríos, “Spanish DAL: A Spanish Dictionary of Affect in Language”, Reporte Técnico, Departamento de Computación, FCEyN-UBA, Febrero 2014.

Cargamos una versión adaptada del léxico:

lexico <- read.csv("https://bitsandbricks.github.io/data/sdal.csv", 
                   stringsAsFactors = FALSE)

head(lexico)
##     palabra media_agrado media_activacion media_imaginabilidad
## 1 población         -0.2              0.0                  0.6
## 2      obra          0.2              0.4                  0.4
## 3    dormir          1.0             -0.6                  1.0
## 4       dar          0.8              1.0                  1.0
## 5  realizar          0.6              1.0                  0.8
## 6     haber          0.0             -0.2                 -0.2

Cuantificando sentimiento

Vamos a compararcuatro tangos entre sí, asignandoles una polaridad de sentimiento (de positivo a negativo) línea por línea. Nuestra guía será el valor de “agrado” para cada palabra hallada en el léxico SDAL. Un agrado alto será asociado a un sentimiento positivo, y uno bajo a sentimiento negativo.

Seleccionemos éstos clásicos:

  • El día que me quieras
  • Por una cabeza
  • Naranjo en flor
  • garufa
clasicos <- c("el dia que me quieras",
              "por una cabeza",
              "naranjo en flor",
              "garufa")

Para asignar un sentimiento a cada línea de los tangos, simplemente hacemos un join con el léxico, lo cual asocia a cada palabra su valor de sentimiento. Y luego sumamos los valores para obtener el sentimiento general de cada línea:

sentimiento_clasicos <- tangos_tokenizado %>% 
    filter(titulo %in% clasicos) %>% 
    inner_join(lexico, by = c("word" = "palabra")) %>% 
    group_by(titulo, linea) %>% 
    summarise(sentimiento = sum(media_agrado))

La gracia de analizar el sentimiento línea a línea es que podemos visualizar la progresión a medida que transcurre el tango:

ggplot(sentimiento_clasicos) +
  geom_bar(aes(linea, sentimiento, fill = sentimiento),
           stat = "identity", show.legend = FALSE) +
  facet_wrap(~titulo, ncol = 2, scales = "free_x") +
    scale_fill_distiller(type = "div")

“El día que me quieras” alcanza sus picos de sentimiento positivo en las líneas 18 y 40. Para saber cuales son, lo consultamos con el dataset original de tangos, el que no hemos divididos letra por letra:

tangos %>% 
    filter(titulo == "el dia que me quieras", linea == 18 | linea == 40) %>% 
    select(letra)
## # A tibble: 2 x 1
##   letra                     
##   <chr>                     
## 1 " desde el azul del cielo"
## 2 " desde el azul del cielo"

De algún modo, la combinación de palabras “desde el azul del cielo” rankea muy alto de acuerdo al léxico de sentimiento que estamos usando.

“Naranjo en flor”, por su parte, muestra siempre un sentimiento positivo… excepto en los versos 24 y 25. ¿Qué contienen?

tangos %>% 
    filter(titulo == "naranjo en flor", linea == 24 | linea == 25) %>% 
    select(letra)
## # A tibble: 2 x 1
##   letra                     
##   <chr>                     
## 1 " tanto dolor?"           
## 2 " dolor de vieja arboleda"

… ¡“dolor”!

Para profundizar en el tema

Para seguir leyendo, recomiendo “Text Mining with R”. Hasta aquí sólo hemos raspado la superficie del análisis computacional de textos. Ya sabemos hacer sentiment analysis, pero quedan pendientes otras técnicas cómo tf-idf, n-grams y topic modeling.