Hace poco compré el libro Text Analysis with R for Students of Literature de Matthew L. Jockers1 para aprender un poco sobre análisis de texto con R con una perspectiva más enfocada a la literatura y no estadística como había visto hasta ahora. Estoy siguiendo el libro poco a poco y realizando sus ejercicios, y he pensado que tal vez a alguien le puede venir bien así que me he decidido a escribir una serie de post sobre mis progresos con el libro.

Hace un par de años que vengo aprendiendo R por mi cuenta. Es un programa que me fascina además de su versatilidad, poder de cálculo y de ser gratuito, por que se nutre del trabajo “altruista” de gente “anónima” (y por el interés de empresas, no seamos inocentes). Antes de este blog comencé uno enfocado en R pero al final lo dejé colgado por éste y no tenía muy claro si mezclar temas, pero al final ha ganado la vagancia de no tener que mantener dos blogs.

Al tema!

Comencemos esta serie de blogs sobre Análisis de texto con R que por supuesto no reemplaza el libro en el que se basa (ya me gustaría!) y que ni siquiera seguiré al pie de la letra. He escogido para ello The Dunwich Horror (El Horror de Dunwich) de H.P. Lovecraft, uno de mis autores favoritos, aunque, por supuesto, se podría escoger cualquiera. El texto lo he descargado – aquí – de Proyect Gutenberg una web que recoge textos libres de copyright.

Descargamos el texto e indicamos a R nuestra carpeta de trabajo, donde se encuentra el texto y donde el programa guardará los datos de nuestro script. Luego cargamos el texto como un vector:

# Directorio de trabajo
setwd("~/Documentos/PlantillasR/The-Dunwich-Horror/")
# Cargamos el texto como vector (.v)
text.v <- scan ("~/Documentos/PlantillasR/The-Dunwich-Horror/data/horror.txt", what="character", sep="\n")

Un primer vistazo al texto:

# Muestra la primera linea
text.v[1]
## [1] "The Project Gutenberg EBook of The Dunwich Horror, by H. P. Lovecraft"

El texto descargado tiene 1862 lineas que incluye partes (metadata) que no son propiamente de la novela y que para el análisis vamos a guardar en una variable a parte (para analizar más adelante). En este caso, mirando el archivo de texto vemos que la obra comienza en la línea 15 (el título) y termina en la 1555.

# Inicio
text.v[15]
## [1] "                       The Dunwich Horror"
# Final
text.v[1555]
## [1] "his twin brother, but it looked more like the father than he did.\""

Usamos which para indicar en nueva variable –metadata.v– el texto (antes del título y después de la última palabra) que forma parte del metadata y las lineas que pertenecen a la novela (entre la línea 15 y la 1555), que guardamos en la variable novela.v. Yo he considerado que la obra comienza en el título pero tal vez sería mejor establecer que el inicio es con la primera frase de la novela como tal.

# Inicio del texto
start.text <- which(text.v=="                       The Dunwich Horror")
# Final del texto
end.text <- which(text.v=="his twin brother, but it looked more like the father than he did.\"")
# Metadata
metadata.v <- c(text.v[1:start.text-1],text.v[end.text+1:length(text.v)])
# Novela
novela.v <- text.v[start.text:end.text]

Vemos las cinco primeras lineas de nuestra variable:

novela.v[1:5]
## [1] "                       The Dunwich Horror"                           
## [2] "                        by H. P. LOVECRAFT"                          
## [3] "     \"Gorgons, and Hydras, and Chimeras--dire stories of Celæno and"
## [4] "     the Harpies--may reproduce themselves in the brain of"          
## [5] "     superstition--but they were there before. They are transcripts,"

Comprobamos con length que la nueva variable –novela.v– contiene menos datos que text.v-.

# El texto antes y después
length(text.v)
## [1] 1862
length(novela.v)
## [1] 1541

El siguiente paso es quitar los saltos de línea con paste que concatena las cadenas de letras quitando los espacios. Cómo vemos todo el texto de la obra está guardado en un vector de una sola línea.

novela.v <- paste(novela.v, collapse = " ")
length(novela.v)
## [1] 1

Para un mejor análisis convertimos todas las letras del texto en minúsculas (tolower).

novela.v <- tolower(novela.v)

Ahora, convertimos nuestro texto a una lista de elementos, con la función strsplit por lo que cada palabra, espacios o símbolos ocupan un lugar determinado.

novela.l <- strsplit(novela.v, "\\W")
# Vemos la estructura de la nueva variable
str(novela.l)
## List of 1
##  $ : chr [1:20813] "" "" "" "" ...
#novela.l[1:5]

Volvemos a vectorizar el texto (con unlist), pero ahora, tras la conversión anterior en lista, el vector está indexado. Es decir cada palabra, espacio o símbolo tiene asignada una posición dentro del vector.

horror.v <- unlist (novela.l)
horror.v[1:10]
##  [1] "" "" "" "" "" "" "" "" "" ""

Vamos a quitar los espacios de nuevo, esta vez, creamos un vector con las palabras que no son espacios usando which y reescribimos la variable horror.v con él.

# Creamos una nueva variable con las posiciones que tienen palabras
not.blank.v <- which(horror.v!="")
# Reescribimos la variable con esta variable
horror.v <- horror.v[not.blank.v]
horror.v[1:10]
##  [1] "the"       "dunwich"   "horror"    "by"        "h"        
##  [6] "p"         "lovecraft" "gorgons"   "and"       "hydras"

Ya tenemos nuestra novela sin signos de puntuación y espacios. Como he comentado antes, cada palabra tiene asignado un indice, así que podemos averiguar que palabra se encuentra en una determinada posición. Por ejemplo, en la posición 1000 de nuestro vector encontramos la palabra “legends”.

horror.v[1000]
## [1] "legends"

También, podemos averiguar cuantas veces aparece en el vector una determinada palabra. Por ejemplo, cuantas veces aparece “Dunwich”, la ciudad donde sucede la novela corta, el “simpático” primigenio “Cthulhu” o la familia protagonista “Whateley”.

which(horror.v=="cthulhu")
## [1] 6207
which(horror.v=="dunwich")
##  [1]     2   700   703   783   997  1054  1381  1545  1599  1961  2263
## [12]  2814  3578  3851  3972  4182  4383  5104  5549  6308  6678  6852
## [23]  6938  6977  7099  8551  8574  9592  9879 10066 11133 13045 13114
## [34] 13154 13232 13329 13738 16696
which(horror.v=="whateley")
##  [1]  1574  1675  1827  1841  1855  1953  2188  2231  2275  2350  2961
## [12]  3016  3404  3525  3891  3936  3956  4047  4113  4210  4507  4659
## [23]  4681  4805  5336  5398  5446  6316  6577  6634  6800  6870  6932
## [34]  6952  7096  7126  8486  8516  8592  8631  8824  9104  9164  9190
## [45]  9251  9435  9469  9529  9565  9644  9695  9891 10451 10876 11173
## [56] 11271 11356 11773 12886 13020 14208 14416 14604 14979 15455 15473
## [67] 15856 15988 17222 17404 17509 17540 17562 17608 17692

Curioso, o no, que el simpático Cthulhu sólo aparezca una vez (soy un fanboy:D). Podemos conocer en que posición o posiciones se encuentran estas palabras en el vector.

horror.v[which(horror.v=="cthulhu")]
## [1] "cthulhu"
horror.v[which(horror.v=="dunwich")]
##  [1] "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich"
##  [8] "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich"
## [15] "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich"
## [22] "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich"
## [29] "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich" "dunwich"
## [36] "dunwich" "dunwich" "dunwich"
horror.v[which(horror.v=="whateley")]
##  [1] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
##  [7] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [13] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [19] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [25] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [31] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [37] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [43] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [49] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [55] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [61] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [67] "whateley" "whateley" "whateley" "whateley" "whateley" "whateley"
## [73] "whateley" "whateley" "whateley"

Usando lenght podemos conocer cuantas veces se repiten las anteriores palabras.

length(horror.v[which(horror.v=="cthulhu")])
## [1] 1
length(horror.v[which(horror.v=="dunwich")])
## [1] 38
length(horror.v[which(horror.v=="whateley")])
## [1] 75

Calculando la longitud del vector con length podemos saber que el número total de palabras que contiene “The Dunwich Horror” son 17.878 palabras.

total.words <- length(horror.v)
total.words
## [1] 17878

Conociendo la cantidad de palabras que contiene nuestro vector y las veces que se repite una determinada palabra podemos averiguar el porcentaje de veces que aparece una palabra en concreto en la novela. Siguiendo el ejemplo anterior, “dunwich” aparece en un porcentaje de 0,21%.

rep.dunwich <- length(horror.v[which(horror.v=="dunwich")])
(rep.dunwich/total.words)*100
## [1] 0.2125517

¿Que porcentaje de palabras hay en la novela que aparezcan una única vez?

# Porcentaje de palabras únicas hay (que se usan una sola vez) ==> (22.47%)
length(unique(horror.v))/total.words*100
## [1] 22.22844

Ahora, vamos a crear una tabla (con la función table) con las frecuencias de cada palabra ordenadas de forma decreciente.

# Frecuencias de cada palabra
horror.freq.t <- table(horror.v)
#horror.freq.t[1:10]
# Ordenamos por orden decreciente
sorted.horror.freq.t <- sort(horror.freq.t, decreasing = TRUE)

Usando esta tabla, realizamos un gráfico con las 10 primeras palabras más utilizadas en la novela.

# Plot con las 10 palabras más frecuentes
plot(sorted.horror.freq.t[1:10], xlab="Palabras más utilizadas",
     ylab="Frecuencia", main = "The Dunwich Horror")

words-horror

Tenemos todas las palabras ordenadas por su frecuencia en una tabla y como en nuestro vector, todas están indexadas y podemos buscar la frecuencia de una determinada palabra. Por ejemplo, el nombre del profesor Henry Armitage aparece en 52 ocasiones en la novela.

sorted.horror.freq.t["armitage"]
## armitage 
##       52

Podemos también comparar el uso de dos palabras, dividiendo sus frecuencias. Así, la palabra “whateley” es usada 1,44 veces más que “armitage”.

sorted.horror.freq.t["whateley"]/sorted.horror.freq.t["armitage"]
## whateley 
## 1.442308

Bueno, sabemos cuantas veces se repite una palabra en el texto, pero si queremos comparar textos (que lo dejo para más adelante) es mejor conocer las frecuencias relativas de cada palabra. Para ello, realizamos una sencilla operación, dividiendo cada palabra por la suma de las frecuencias de la tabla.

# Frecuencias relativas de cada palabra
sorted.horror.rel.freq.t <- 100*(sorted.horror.freq.t/sum(sorted.horror.freq.t))
sorted.horror.rel.freq.t[1:5]
## horror.v
##      the       of      and        a       to 
## 7.265913 3.456763 3.372860 2.270948 2.086363

Y realizamos el plot correspondiente para ver el resultado con las 10 con mayor porcentaje:

# Plot frecuencias relativas
plot(sorted.horror.rel.freq.t[1:10], type="b", xlab="Palabras más utilizadas",
     ylab="Porcentaje sobre el total del texto", xaxt="n", main = "The Dunwich Horror")
axis(1,1:10, labels = names(sorted.horror.rel.freq.t[1:10]))

rel-words-horror

Se acabó el primer capítulo. Por comodidad es mejor guardar los datos que hemos empleado. Lo podemos hacer dando al botón de guardar en la ventana de Environment o desde la consola escribiendo save.image(“tu/carpeta/”):

save.image("~/Documentos/PlantillasR/The-Dunwich-Horror/Dunwich-horror.RData")

Por cierto, el código de este post se encuentra para descargar en mi gist


  1. Jockers, Matthew (2014).Text Analysis with R for Students of Literature. Ed. Springer. 
Anuncios