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