2015-01-15

Corrector ortográfico en español para R

Title Hace algunos años Peter Norvig escribió el artículo How to Write a Spelling Corrector, creando un corrector ortográfico en 21 líneas de Python. Recientemente, encontré una versión creada por Rasmus Bååth para R, Peter Norvig's Spell Checker in Two Lines of Base R. En esta entrada me baso en el código de Rasmus Bååth para crear una versión en español. Antes de continuar, recomiendo leer los artículos anteriores.

Código desglosado

# Código modificado
raw_test <- read.csv("https://sites.google.com/site/nubededatosblogspotcom/crea.txt", 
          header = FALSE) # Importa como data frame
sorted_words  <- as.table(as.matrix(raw_test)) # Convierte data frame a table

# Código idéntico al de Rasmus Bååth  
correct <- function(word) {
  # Calcula la distancia entre la palabra y el resto de palabras ordenadas (sorted words).
  edit_dist <- adist(word, sorted_words)
  # Calcula la distancia mínima y devuelve una palabra existente en crea.txt
  # con un límite de 2 ediciones.
  min_edit_dist <- min(edit_dist, 2)
  # Genera un vector con todas las palabras con el mínimo de distancia.
  # Como sorted_words está ordenada de más a menos común, el vector
  # resultante tendrá la primera coincidencia más común/probable.
  proposals_by_prob <- c(sorted_words[ edit_dist <= min(edit_dist, 2)])
  # En caso de que proposals_by_prob esté vacío asignamos la palabra evaluada
  proposals_by_prob <- c(proposals_by_prob, word)
  # ... y devuelve la palabra primera/más probable en el vector.
  proposals_by_prob[1]
}

Funcionamiento

 correct("correrctor")
[1] "corrector"
 correct("ortogrfaico")
[1] "ortográfico"
 correct("wn")
[1] "en"
 correct("foncionamento")
[1] "funcionamiento"

El código define la función correct y como argumento incluimos entre comillas la palabra evaluada. La distancia máxima entre la palabra evaluada y el objetivo (una palabra incluida en el corpus, correcta) es de dos ediciones. Pues, de acuerdo a Norvig, la literatura sobre el tema afirma que la inmensa mayoría de errores ortográficos se encuentran a una distancia de 2 o 1. Si la palabra está incluida en el corpus o si no encuentra una corrección de la misma en dos ediciones, no corregirá dicha palabra.

# Distancia 2 ediciones
correct("edictioness") 
[1] "ediciones"
# Distancia 3 ediciones
 correct("edictiomess")
[1] "edictiomess"

Notas

Básicamente el código original, prácticamente idéntico al código con nuestro propio corpus, extraía las palabras del fichero big.txt de Norvig y creaba un vector en orden descendente en función de la frecuencia de las palabras. Inicialmente pensé que bastaría con crear fichero equivalente en español, concatenando textos en español del proyecto Gutenberg.

Pero buscando un listado de palabras más frecuentes encontré el Corpus de Referencia del Español Actual (CREA), un listado de frecuencias de palabras. Las ventajas de utilizar el CREA frente a una recopilación de textos mía son:

- Mayor representatividad de la frecuencia de las palabras por la selección de texto de la RAE.

- Simplificación del código pues ya no hay que extraer o contar la frecuencia de cada palabra.

- Fichero de texto de menor tamaño.

Una desventaja es que si quisiéramos hacer un corrector más sofisticado, como por ejemplo tener en cuenta el contexto de la palabra dentro del texto, tal y como menciona Norvig, el Corpus de Referencia del Español Actual (CREA) no sería de ayuda.

La lista total de frecuencias comprende 737.799 palabras, pero para ganar en rapidez he seleccionado las primeras 100.000 en el fichero crea.txt. El código anterior (código desglosado) importa este fichero de texto, ya en orden descendente, como data frame y lo transforma en la clase table de R. Después utiliza la función creada por Rasmus Bååth.

Código con nuestro propio corpus

En el caso de que queramos crear nuestro propio corpus agrupando texto en español.

# En dos líneas
sorted_words <- names(sort(table(strsplit(tolower(paste(readLines("corpus.txt"), collapse = " ")), "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+")), decreasing = TRUE))
correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }

# Desglosado
raw_text <- paste(readLines("corpus.txt"), collapse = " ")
split_text <- strsplit(raw_text, "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+")
sorted_words  <- as.table(as.matrix(data.frame(split_text))) 

correct <- function(word) {
  edit_dist <- adist(word, sorted_words)
  min_edit_dist <- min(edit_dist, 2)
  proposals_by_prob <- c(sorted_words[ edit_dist <= min(edit_dist, 2)])
  proposals_by_prob <- c(proposals_by_prob, word)
  proposals_by_prob[1]
}
He sustituido el rango de caracteres "[^a-z]+" por "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+" especificando los caracteres para que al crear el vector con las palabras no las divida erróneamente cuando encuentre acentos, apostrofes o letras como la ñ y la ç.

Código en dos líneas

# Código modificado
sorted_words <- as.table(as.matrix(read.csv("https://sites.google.com/site/nubededatosblogspotcom/crea.txt", header = FALSE)))

# Código idéntico al de Rasmus Bååth  
correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }
# Código original de Rasmus Bååth
sorted_words <- names(sort(table(strsplit(tolower(paste(readLines("http://www.norvig.com/big.txt"), collapse = " ")), "[^a-z]+")), decreasing = TRUE))

correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }

Referencias

9 comentarios:

  1. Gracias por el aporte me ha sido super útil!

    ResponderEliminar
    Respuestas
    1. Me alegro Sara. ¿Le has dado alguna aplicación práctica? Gracias

      Eliminar
  2. Hola Nube de datos. Excelente aporte. Muy bueno tu blog. Un saludo.

    ResponderEliminar
    Respuestas
    1. Muchas gracias por tu comentario, Alex. Un saludo.

      Eliminar
  3. hola, la base de datos corpus.txt cual es ?

    ResponderEliminar
    Respuestas
    1. Hola Daniel. En el ejemplo en la parte superior de esta entrada, utilizo como corpus un listado de palabras (CREA) de la Real Academia Española (RAE). En la parte inferior, a la que te refieres, sería en el caso de que quisieras crear o tuvieras otro listado o corpus contra el que evaluar las palabras. A modo de ejemplo lo he llamado corpus.txt.

      Eliminar
  4. Excelente aporte. Lo utilizaré para validar datos mal digitados por usuarios

    ResponderEliminar
    Respuestas
    1. Fenomenal. Muchas gracias por el comentario. Saludos.

      Eliminar
  5. una pregunta si tengo mi dataframe con palabras erroneas y ese dataframe se llama dtm_d sustituyo "word" y pongo ahi dtm_d para que se haga el corregimiento de palabras verdad?

    ResponderEliminar

Nube de datos