1 mar 2019

Minería de texto: limpiar, estandarizar y “tokenizar” datos de texto en R

# install.packages("tm")
# install.packages("NLP")
library(NLP)
library(tm)

El manejo de datos tipo cadenas, son complejos y requiere mucha reflexión y esfuerzo. Uno tiene que considerar cómo quitar números y puntuación; encargarse de palabras poco interesantes como: y, pero, y o; y cómo separar las oraciones en palabras individuales (tokenization).

Afortunadamente, esta funcionalidad ha sido proporcionada por los miembros de la comunidad R en un paquete de minería de texto titulado tm. En el siguiente ejemplo se colocan los datos de forma tal que data observación corresponden a un elemento de lista.

Para ver el texto del mensaje real, la función as.character () debe aplicarse a los mensajes deseados. Para ver un mensaje, use la función as.character () en un solo elemento de lista, que señala que se requiere la notación de doble corchete:

texto <- c("Todos somos responsables de nuestros hechos. 17?",
           "Los recursos humanos suelen ser formados por un hombre",
           "Conozco al hombre y amo a sus perro")

sms_corpus <- VCorpus(VectorSource(texto))

inspect(sms_corpus[1:2])

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"
lapply(sms_corpus[1:2], as.character)
## $`1`
## [1] "Todos somos responsables de nuestros hechos. 17?"
##
## $`2`
## [1] "Los recursos humanos suelen ser formados por un hombre"

Debido a que tm corpus es esencialmente una lista compleja, podemos usar las operaciones de lista para seleccionar documentos en el corpus. Para recibir un resumen de mensajes específicos, podemos usar la función inspect() con operadores de listas. Por ejemplo, el siguiente comando verá un resumen de los mensajes SMS primero y segundo:

inspect(sms_corpus)
## <<VCorpus>>
## Metadata:  corpus specific: 0, document level (indexed): 0
## Content:  documents: 3
##
## [[1]]
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 48
##
## [[2]]
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 54
##
## [[3]]
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 35

Para realizar nuestro análisis, necesitamos dividir estos mensajes en palabras individuales. Pero primero, tenemos que limpiar el texto, para estandarizar las palabras, eliminando la puntuación y otros caracteres que saturan el resultado. Por ejemplo, nos gustaría que las cadenas Hello, HELLO y hello se cuenten como instancias de la misma palabra. La función tm_map () proporciona un método para aplicar una transformación (también conocida como mapeo) a un cuerpo de tm. Usaremos esta función para limpiar nuestro corpus mediante una serie de transformaciones y guardar el resultado en un nuevo objeto llamado corpusclean.

sms_corpus_clean <- tm_map(sms_corpus, content_transformer(tolower))

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"

as.character(sms_corpus_clean[[1]])
## [1] "todos somos responsables de nuestros hechos. 17?"

Continuemos nuestra limpieza eliminando números de los mensajes SMS. Aunque algunos números pueden proporcionar información útil, la mayoría probablemente sea única para los remitentes individuales y, por lo tanto, no proporcionará patrones útiles en todos los mensajes. Con esto en mente, eliminaremos todos los números del corpus de la siguiente manera:

sms_corpus_clean <- tm_map(sms_corpus_clean, removeNumbers)

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"

as.character(sms_corpus_clean[[1]])
## [1] "todos somos responsables de nuestros hechos. ?"

Nuestra siguiente tarea es eliminar las palabras de relleno como, y, pero, y / o de nuestros mensajes SMS. Estos términos se conocen como palabras de detención y generalmente se eliminan antes de la minería de texto. Esto se debe al hecho de que, aunque aparecen con mucha frecuencia, no proporcionan mucha información útil para el aprendizaje automático.

#stopwords("english")
sms_corpus_clean <- tm_map(sms_corpus_clean,removeWords, stopwords("spanish"))

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"

as.character(sms_corpus_clean[[1]])
## [1] "  responsables   hechos. ?"

Continuando con nuestro proceso de limpieza, también podemos eliminar cualquier puntuación de los mensajes de texto usando la transformación incorporada removePunctuation ():

sms_corpus_clean <- tm_map(sms_corpus_clean, removePunctuation)

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"

as.character(sms_corpus_clean[[1]])
## [1] "  responsables   hechos "

A continuación de utiliza el concepto wordStem, este, lo que hace es colocar los diferencies variantes de una palabra, como para considerarla partes de una misma palabras.

# install.packages("SnowballC")
library(SnowballC)

wordStem(c("aprender", "aprendiendo", "aprendió"), "spanish")
## [1] "aprend" "aprend" "aprend"

#sms_corpus_clean <- tm_map(sms_corpus_clean, stemDocument, "spanish")

El último paso en nuestro proceso de limpieza de texto es eliminar espacios en blanco adicionales, utilizando stripWhitespace ():

sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace)

as.character(sms_corpus[[1]])
## [1] "Todos somos responsables de nuestros hechos. 17?"

as.character(sms_corpus_clean[[1]])
## [1] " responsables hechos "

Ahora que los datos se procesan a nuestro gusto, el paso final es dividir los mensajes en componentes individuales a través de un proceso llamado tokenización. Un token es un elemento único de una cadena de texto; en este caso, los tokens son palabras. El paquete tm proporciona funcionalidad para tokenizar el cuerpo del mensaje SMS. La función DocumentTermMatrix () tomará un corpus y creará una estructura de datos llamada Document Term Matrix (DTM) en la que las filas indican documentos (mensajes SMS) y las columnas indican términos (palabras).

sms_dtm <- DocumentTermMatrix(sms_corpus_clean)
sms_dtm

## <<DocumentTermMatrix (documents: 3, terms: 11)>>
## Non-/sparse entries: 12/21
## Sparsity           : 64%
## Maximal term length: 12
## Weighting          : term frequency (tf)

Una alternativa al proceso anterior:

sms_dtm2 <- DocumentTermMatrix(sms_corpus,
           control = list(
             tolower = TRUE,
             removeNumbers = TRUE,
             stopwords = TRUE,
             removePunctuation = TRUE,
             stemming = TRUE
                         )
                       )
findFreqTerms(sms_dtm2)
##  [1] "amo"     "conozco" "formado" "hecho"   "hombr"   "humano"  "los"   
##  [8] "nuestro" "perro"   "por"     "recurso" "respons" "ser"     "somo"  
## [15] "suelen"  "sus"     "todo"

Finalmente, la "tokenización" de los datos se puede obtener utilizando el comando inspect:

inspect(sms_dtm2)
## <<DocumentTermMatrix (documents: 3, terms: 17)>>
## Non-/sparse entries: 18/33
## Sparsity           : 65%
## Maximal term length: 7
## Weighting          : term frequency (tf)
## Sample             :
##     Terms
## Docs amo conozco formado hecho hombr humano los nuestro perro por
##    1   0       0       0     1     0      0   0       1     0   0
##    2   0       0       1     0     1      1   1       0     0   1
##    3   1       1       0     0     1      0   0       0     1   0

Y finalmente,  convertir los datos en un data.frame:

datos<-data.frame(inspect(sms_dtm2))
datos
##   amo conozco formado hecho hombr humano los nuestro perro por
## 1   0       0       0     1     0      0   0       1     0   0
## 2   0       0       1     0     1      1   1       0     0   1
## 3   1       1       0     0     1      0   0       0     1   0

Ahora, realizamos una nube de puntos con la frecuencia de cada variable:

# install.packages("wordcloud")
library(wordcloud)
wordcloud(sms_corpus_clean, min.freq = 1, random.order = FALSE)


 

Referencia:

Lantz, Brent (2013). Machine Learning with R.  Packt Publishing Ltd. ISBN 978-1-78216-214-8. 


Recodificación de variables usando dplyr en R

Una base de datos suele tener diversos tipos de variables del tipo cualitativo y cuantitativo. En función del tipo de variables aplicamos di...