El objetivo de estas notas es presentar de manera breve las herramientas y funciones necesarias del paquete estadístico R, para clasificar documentos en formato pdf. Para generar nuestros documentos de “práctica” simplemente buscamos películas para niños en Wikipedia y salvamos cada una de las páginas encontradas en formato pdf. En nuestro caso, usaremos los siguientes documentos:
my_dir <- "/Users/carloserwin/Dropbox/Peliculas/"
files <- list.files(path = my_dir, pattern = "pdf$")
files # listamos los archivos con formato pdf
## [1] "Bee_Movie.pdf" "Buscando_ a_Dory.pdf"
## [3] "Buscando_ a_Nemo.pdf" "Cars_2.pdf"
## [5] "Cars_3.pdf" "Cars.pdf"
## [7] "Cómo_entrenar_a_tu_dragón_2.pdf" "Cómo_entrenar_a_tu_dragón_3.pdf"
## [9] "Cómo_entrenar_a_tu_dragón.pdf" "Kung_Fu_Panda_2.pdf"
## [11] "Kung_Fu_Panda_3.pdf" "Kung_Fu_Panda.pdf"
## [13] "Los_Increíbles_2.pdf" "Los_Increíbles.pdf"
## [15] "Madagascar_2.pdf" "Madagascar.pdf"
## [17] "Monsters_Inc.pdf" "Monsters_University.pdf"
## [19] "Ratatouille.pdf" "Shrek_2.pdf"
## [21] "Shrek.pdf" "Toy_Story_2.pdf"
## [23] "Toy_Story_3.pdf" "Toy_Story.pdf"
Necesitaremos cargar las librerías:
tm
es el llamado Corpus, que es el texto de los documentos en el que se basará nuestro análisis.Cargamos las librerías mencionadas (después de haberlas instalado)
## Loading required package: NLP
A partir de los documentos en pdf, necesitaremos generar un Corpus, esto se hace de la siguiente manera
setwd(my_dir)
corp <- Corpus(URISource(files, encoding = "latin1"),
readerControl = list(reader = readPDF, language = "es-419"))
# summary(corp)
ndocs <- length(corp)
ndocs # número de documentos leídos
## [1] 24
En R, el Corpus es una lista de listas. En el primer nivel se tienen los documentos, y en cada documento la primera entrada tiene el texto y la segunda tiene información del texto de ese documento, por ejemplo: corp[[1]][[1]]
nos daría todo el texto del primer documento (Bee_Movie.pdf), mientras que corp[[1]][[2]]
nos da el título del documento, formato, etc. Si corremos corp[[2]][[1]]
obtendríamos el texto del segundo documento (Buscando_a_Dory.pdf), etc
Para realizaer el análisis usaremos frecuencias, básicamente construiremos una tabla de contingencia de documentos (renglones) y palabras (columnas). Para calcular frecuencias, conviene antes “homologar” todo el texto, así que es conveniente
corp <- tm_map(corp, content_transformer(tolower))
corp <- tm_map(corp, content_transformer(removePunctuation))
corp <- tm_map(corp, content_transformer(removeNumbers))
corp <- tm_map(corp, removeWords, stopwords("spanish"))
mywords <- c("the", "film", "why")
corp <- tm_map(corp, removeWords, mywords)
corp <- tm_map(corp, stripWhitespace)
La instrucción para convertir el Corpus de cada documento en una tabla de contingencia es DocumentTermMatrix
. Sin embargo, para robustecer nuestro análisis consideraremos únicamente palabras de entre 4 y 15 letras de longitud, no consideraremos palabras que aparezcan únicamente en \(1\%\) de los documentos (palabras extrañas) ni tampoco palabras demasiado comunes que aparezcan en más del \(50\%\) de los documentos (en nuestro caso, por ejemplo: wikipedia, título, guión, película, etc)
# Ignorar palabras extrañas
minTermFreq <- ceiling(ndocs*0.1)
# Ignorar palabras muy comunes
maxTermFreq <- floor(ndocs*0.5)
dtm <- DocumentTermMatrix(corp,
control = list(
language = "es-419",
wordLengths = c(4, 15),
bounds = list(global = c(minTermFreq, maxTermFreq))
))
Para explorar rápidamente nuestra tabla de contingencia, usamos la instrucción inspec
, que nos da la siguiente información:
## <<DocumentTermMatrix (documents: 24, terms: 2474)>>
## Non-/sparse entries: 11719/47657
## Sparsity : 80%
## Maximal term length: 15
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs cars dragón hipo juguetes mate mcqueen
## Buscando_ a_Nemo.pdf 0 0 0 0 0 2
## Cars_2.pdf 52 0 0 1 114 129
## Cars_3.pdf 47 0 0 2 5 31
## Cars.pdf 40 0 0 4 15 58
## Cómo_entrenar_a_tu_dragón.pdf 0 79 67 0 0 0
## Los_Increíbles.pdf 7 0 0 2 0 0
## Ratatouille.pdf 3 0 0 0 0 0
## Toy_Story_2.pdf 0 0 0 37 0 0
## Toy_Story_3.pdf 2 0 0 67 0 0
## Toy_Story.pdf 0 0 0 27 0 0
## Terms
## Docs nemo panda shrek woody
## Buscando_ a_Nemo.pdf 80 0 0 0
## Cars_2.pdf 0 0 0 0
## Cars_3.pdf 0 0 0 0
## Cars.pdf 2 0 0 1
## Cómo_entrenar_a_tu_dragón.pdf 0 0 3 0
## Los_Increíbles.pdf 6 0 0 0
## Ratatouille.pdf 3 0 0 0
## Toy_Story_2.pdf 0 0 0 51
## Toy_Story_3.pdf 0 0 0 44
## Toy_Story.pdf 1 0 0 32
Si quisiéramos una tabla de contingencia que tuviera palabras que aparecen en aproximadamente \(40\%\) de los documentos.
## <<DocumentTermMatrix (documents: 24, terms: 131)>>
## Non-/sparse entries: 1431/1713
## Sparsity : 54%
## Maximal term length: 12
## Weighting : term frequency (tf)
## Sample :
## Terms
## Docs andrew bonnie buscando dreamworks equipo
## Buscando_ a_Dory.pdf 11 0 26 0 0
## Cars_2.pdf 0 3 0 0 10
## Cars.pdf 1 3 2 0 5
## Cómo_entrenar_a_tu_dragón_2.pdf 0 2 0 6 0
## Cómo_entrenar_a_tu_dragón.pdf 0 1 0 10 0
## Los_Increíbles.pdf 2 0 5 0 7
## Ratatouille.pdf 0 0 3 0 1
## Toy_Story_2.pdf 6 0 0 0 1
## Toy_Story_3.pdf 2 23 1 0 1
## Toy_Story.pdf 5 2 1 0 5
## Terms
## Docs lasseter monsters newman nominado serie
## Buscando_ a_Dory.pdf 0 1 2 0 0
## Cars_2.pdf 13 0 3 0 4
## Cars.pdf 9 5 6 3 2
## Cómo_entrenar_a_tu_dragón_2.pdf 0 0 0 11 3
## Cómo_entrenar_a_tu_dragón.pdf 0 1 0 0 6
## Los_Increíbles.pdf 5 3 0 0 9
## Ratatouille.pdf 2 1 0 0 0
## Toy_Story_2.pdf 11 2 7 4 0
## Toy_Story_3.pdf 5 0 22 6 2
## Toy_Story.pdf 29 2 17 0 7
Obviamente, lo que sucede es que se eliminan palabras que aparecen en muy pocos documentos, bajamos de \(2,477\) palabras a sólo \(131\). En este caso, se tienen en total \(3,144 (= 1,431 + 1,713)\) celdas en la tabla de contingencia resultante.
Aquí sucede algo interesante, la función inspec
sólo nos arroja una muestra de la tabla de contingencia, de hecho son las palabras que se repiten con mayor frecuencia. Si quisiéramos ver toda la tabla, hacemos lo siguiente:
M <- as.matrix(dtm)
o <- order(sM <- colSums(M), decreasing = TRUE)
write.csv(M[,o], paste0(my_dir, "DTM.csv"), fileEncoding = "UTF-8")
Convertimos la tabla a una matriz y ordenamos las comunas para que las columnas de la izquierda sean las que tengan las palabras con mayor frecuencia.
Generamos una nube de palabras para los 24 documentos, esto lo hacemos vía la función wordcloud2
, de la librería con el mismo nombre. Lo que se necesita es crear una base de datos con dos columnas: (palabras, frecuencia de las palabras). Finalmente, descartamos palabras poco frecuentas, en nuestro ejemplo 15 es el punto de corte, i.e. descartamos palabras que aparezcan menos de 15 veces en todos los documentos.
mywords <- data.frame(words = names(sM), freq = as.numeric(sM))
mywords2 <- mywords[mywords$freq > 15,]
wordcloud2(mywords2, fontFamily = "serif",
backgroundColor = "white", shape = 'pentagon', size = 0.4)
Podemos hacer lo mismo para cada uno de los documentos (películas), como en este caso tenemos menos palabras cambiamos el punto de corte a 10 palabras.
Definamos \(M=\{f_{i,j}\}_{mxk}\) como la tabla de contingencia del número de veces que aparece cada palabra, en cada uno de los documentos, así - \(m\) representa el número de documentos. - \(k\) representa el número de palabras distintas totales. - \(f_{i,j}\) número de veces que apareció la palabra \(j\)-ésima en el documento \(i\)-ésimo.
Sea \(\boldsymbol{f}_i\) el vector de frecuencias para cada una de las palabras distintas del documento \(i\)-ésimo. Entonces,
\[\boldsymbol{f}_i = (f_{i,1}, f_{i,2}, \ldots, f_{i,k})\] Entonces, definimos una matriz de distancias \(Dist=\{d_{i,h}\}_{mxm}\), entre los renglones de \(M\), en donde
\[d_{i,h}= ||\boldsymbol{f}_i - \boldsymbol{f}_h||=\sqrt{\sum_{r=1}^k(f_{i,r} - f_{h,r})^2}\]
Con esta matriz de distancias entre documentos, podemos utilizar el método de agrupamiento jerárquico, fijando alguno de los criterios de enlace
distMatrix <- dist(M, method = "euclidean")
groups <- hclust(distMatrix, method = "ward.D")
plot(groups, main = "Dendograma de películas para niños", cex=0.9, hang=-1,
xlab = "", ylab = "Altura")
rect.hclust(groups, k = 10, border="blue")