6.3 Preprocesamiento
Es común que las tablas de datos que se usarán en el proceso de emparejamiento de datos puedan variar en formato, estructura y contenido. Dado que el emparejamiento de datos comúnmente se basa en información personal, como nombres, sexo, direcciones y fechas de nacimiento, es importante asegurarse de que los datos provenientes de diferentes bases de datos sean limpiados y estandarizados adecuadamente.
El objetivo de esta etapa es garantizar que los atributos utilizados para el emparejamiento tengan la misma estructura y que su contenido siga los mismos formatos. Se ha reconocido que la limpieza y estandarización de datos son pasos cruciales para un emparejamiento exitoso (Herzog, Scheuren, and Winkler 2007). Los datos brutos de entrada deben convertirse en formatos bien definidos y consistentes, y las inconsistencias en la forma en que se representa y codifica la información deben resolverse (Churches et al. 2002).
Existen al menos cinco pasos que son necesarios (aunque probablemente no suficientes) en el preprocesamiento de datos:
Eliminar caracteres y palabras irrelevantes: Este paso corresponde a una limpieza inicial, donde se eliminan caracteres como comas, dos puntos, puntos y comas, puntos, numerales y comillas. En ciertas aplicaciones, también se pueden eliminar algunas palabras si se sabe que no contienen información relevante para el proceso de emparejamiento. Estas palabras también se conocen como “stop words” o palabras vacías.
Expandir abreviaturas y corregir errores ortográficos: Este segundo paso del preprocesamiento es crucial para mejorar la calidad de los datos a emparejar. Comúnmente, este paso se basa en tablas de búsqueda que contienen variaciones de nombres, apodos, errores ortográficos comunes y sus versiones correctas o expandidas. La estandarización de valores realizada en este paso reducirá significativamente las variaciones en atributos que contienen nombres.
Codificación fonética: Es muy común que se tengan errores de ortografía o que los nombres se escriban de manera diferente, por ejemplo “Catalina Benavides” puede corresponder a “Katalina Venavidez”, pero un algoritmo no encontrará la coincidencia perfecta, así que lograr el emparejamiento automático se convierte en un desafío.
Segmentación: Dividir el contenido de atributos que contienen varias piezas de información en un conjunto de nuevos atributos, cada uno con una pieza de información bien definida regularmente es exitoso. El proceso de segmentar valores de atributos también se llama parsing (Herzog, Scheuren, and Winkler 2007). Es de gran importancia realizarlo para nombres, direcciones o fechas. Se han desarrollado diversas técnicas para lograr esta segmentación, ya sea utilizando sistemas basados en reglas o técnicas probabilísticas como modelos ocultos de Markov (Churches et al. 2002).
Verificar: Este paso puede aplicarse cuando existen fuentes externas que permiten realizar una validación de los datos, por ejemplo, si se dispone de una base de datos externa que contenga todas las direcciones conocidas y válidas en un país o región. La información detallada en dicha base de datos debe incluir el rango de números de calles, así como combinaciones de nombres de calles para validar la información del censo y de la encuesta de cobertura.
6.3.1 Limpieza de los datos
En este paso implementamos una función que nos permita remover los caracteres raros y así limpiar el texto, esta función se ha denominado limpiar_texto()
y en cada línea hemos documentado el objetivo, es importante señalar que pueden existir otras estructuras que pueden ser removidas.
library(pacman)
p_load(dplyr, tidyr, stringr, stringi, assertr)
limpiar_texto <- function(x) {
x |>
iconv(from = "", to = "UTF-8", sub = "") |>
str_to_lower() |> # Convertir a minúsculas
stri_trans_general("Latin-ASCII") |> # Quitar acentos
str_replace_all("[[:punct:]]", " ") |> # Quitar puntuación
str_replace_all("\\s+", " ") |> # Espacios múltiples
str_trim() # Quitar espacios extremos
}
De igual manera el investigador puede establecer un vector de palabras vacías o irrelevantes, que prefiere eliminar de las cadena de texto. Por ejemplo, a continuación se crea el vector stop_words
con varias palabras y se aplica la función eliminar_stopwords()
para eliminarlas.
stop_words <- c("de", "del", "la", "los", "las", "el", "y")
eliminar_stopwords <- function(x, palabras = stop_words) {
palabras_pattern <- paste0("\\b(", paste(palabras, collapse = "|"), ")\\b")
str_remove_all(x, palabras_pattern) %>%
str_replace_all("\\s+", " ") %>%
str_trim()
}
Ahora podemos aplicar nuestras funciones sobre las variables de interés en los conjuntos de datos. Es importante destacar que el proceso de preprocesamiento de datos no debe sobrescribir los datos originales y en su lugar, se deben crear nuevos atributos que contengan los datos limpios y estandarizados, o generar nuevas tablas de datos que contengan los datos limpios y estandarizados.
censo_limpio <- censo |>
mutate(across(c(nombre, apellido, parentesco, sexo),
~eliminar_stopwords(limpiar_texto(.))))
Tabla censo_limpio | |||||||||
id_segmento | id_hogar | id_censo | nombre | apellido | sexo | anio_nac | mes_nac | dia_nac | parentesco |
---|---|---|---|---|---|---|---|---|---|
101 | H101_1 | c1 | carlos | perez | m | 1947 | 1 | 1 | jefe |
101 | H101_1 | c2 | lucia | castro | f | 1975 | 1 | 1 | hijo a |
101 | H101_1 | c3 | camila | castro | f | 2012 | 1 | 1 | hijo a |
101 | H101_1 | c4 | maria | castro | f | 1959 | 1 | 1 | nieto a |
102 | H102_1 | c5 | jorge | gomez | m | 1954 | 1 | 1 | jefe |
102 | H102_1 | c6 | sofia | ramirez | f | 2000 | 1 | 1 | hijo a |
En el caso de la tabla de la encuesta, primero se separa el nombre_completo en varias variables para generar la misma estructura que la tabla del censo, o podría unirse las variables del censo para generar un nombre_unico, lo importante es dejar las tablas en la misma estructura. De igual forma para la fecha de nacimiento. Paso seguido se aplican las funciones sobre las variables de interés.
encuesta_limpia <- encuesta |>
separate(nombre_completo, c("nombre", "apellido"), sep=" ") |>
separate(fecha_nacimiento, c("anio_nac", "mes_nac", "dia_nac"), sep="-") |>
mutate(across(c("anio_nac", "mes_nac", "dia_nac"), ~as.numeric(.))) |>
mutate(across(c(nombre, apellido, parentesco, sexo),
~eliminar_stopwords(limpiar_texto(.))))
Tabla encuesta_limpia | |||||||||
id_segmento | id_hogar | id_encuesta | nombre | apellido | sexo | anio_nac | mes_nac | dia_nac | parentesco |
---|---|---|---|---|---|---|---|---|---|
101 | H101_1 | e1 | maria | castro | f | 1959 | 1 | 1 | nieto a |
101 | H101_1 | e2 | carlos | perez | m | 1947 | 1 | 1 | jefe |
101 | H101_1 | e3 | lucia | castro | f | 1975 | 1 | 1 | hijo a |
101 | H101_10 | e4 | camila | ramirez | f | 2010 | 1 | 1 | hijo a |
101 | H101_2 | e5 | sofia | castro | f | 1966 | 1 | 1 | jefe |
101 | H101_2 | e6 | ana | martinez | f | 1973 | 1 | 1 | conyuge |
Las versiones preprocesadas (limpiadas y estandarizadas) de las dos tablas de datos ahora tienen los mismos atributos. El formato y contenido de estos atributos han sido estandarizados.
6.3.2 Codificación fonética
Existen diversas funciones diseñadas para codificar fonéticamente los valores de ciertos atributos antes de utilizarlos en procesos de emparejamiento o deduplicación de registros. Su propósito es mitigar los errores derivados de variaciones en la escritura o errores ortográficos, especialmente en variables como nombres, apellidos u otras susceptibles a inconsistencias tipográficas. Estas funciones buscan agrupar cadenas de texto que suenan de forma similar al ser pronunciadas, aunque estén escritas de manera distinta.
La codificación fonética también puede combinarse con medidas de similitud como la distancia de Levenshtein, Smith-Waterman o el coeficiente de Jaccard, para comparar cadenas de texto que suenan de forma similar (Navarro 2001; Nauman and Herschel 2022).
El principio fundamental consiste en transformar un texto en un código fonético basado en su pronunciación. ESin embargo, muchas de las técnicas clásicas fueron desarrolladas para el idioma inglés, lo que limita su aplicabilidad directa en contextos de América Latina y el Caribe, donde se emplean otros idiomas como el español, portugués, francés o lenguas indígenas.
A pesar de estas limitaciones, algunos métodos pueden resultar útiles en este contexto. Por ejemplo, el algoritmo Double Metaphone permite generar codificaciones alternativas para un mismo nombre, considerando distintas variantes ortográficas. Su uso puede mejorar la identificación de coincidencias en registros provenientes de censos y encuestas, donde la calidad y la estandarización de los nombres pueden variar significativamente entre fuentes y regiones.
6.3.2.1 Algoritmo Soundex
El algoritmo Soundex es uno de los métodos más antiguos y ampliamente conocidos para la codificación fonética de cadenas de texto. Fue desarrollado originalmente por (Odell and Russell 1918) y ha sido utilizado tradicionalmente en tareas como la consolidación de listas de nombres y la indexación de registros. En el ámbito del emparejamiento de registros entre censos y encuestas de cobertura en América Latina y el Caribe, Soundex puede servir como una herramienta complementaria para enfrentar errores de escritura, diferencias dialectales, y variaciones ortográficas en nombres y apellidos.
Soundex fue diseñado originalmente para nombres en inglés estadounidense, por lo que puede presentar limitaciones en su aplicación directa a nombres hispanos, portugueses o de otras lenguas de la región. Sin embargo, su simplicidad y bajo costo computacional lo convierten en un buen punto de partida para ilustrar los principios básicos de codificación fonética.
Letras | Código |
---|---|
b, f, p, v | 1 |
c, g, j, k, q, s, x, z | 2 |
d, t | 3 |
l | 4 |
m, n | 5 |
r | 6 |
a, e, i, o, u, h, w, y | 0 (se elimina) |
Después de convertir la cadena en dígitos, se eliminan todos los ceros (que corresponden a vocales y las letras ‘h’, ‘w’ e ‘y’), así como las repeticiones del mismo número. Por ejemplo:
Las reglas del algoritmo son:
- Conservar la primera letra del nombre.
- Convertir las letras restantes en números usando la tabla de codificación.
- Eliminar los ceros, ya que las vocales y ciertas consonantes no aportan a la diferenciación fonética.
- Eliminar repeticiones consecutivas del mismo número (por ejemplo, “bb” se convierte en “b1”, no en “b11”).
- Si el código resultante tiene más de tres dígitos, se trunca para que tenga una longitud final de cuatro caracteres (letra + tres dígitos).
- Si tiene menos de tres dígitos, se rellena con ceros.
La siguiente tabla presenta un ejemplo de codificación con el algoritmo soundex. Se observa que, a pesar de que algunos nombres suenan igual, el algoritmo los diferencia según la primera letra.
Nombre | Codificación | Resultado Final |
---|---|---|
Catalina | C, 0, 3, 4, 0, 4, 5, 0 | C345 |
Katalina | K, 0, 3, 4, 0, 4, 5, 0 | K345 |
Yovana | Y, 0, 1, 5, 0, 0 | Y150 |
Jovanna | J, 0, 1, 5, 5, 0, 0 | J150 |
Giovanna | G, 0, 1, 5, 5, 0, 0 | G150 |
Yenny | Y, 0, 5, 5, 0 | Y550 → Y500 |
Yeni | Y, 0, 5, 0 | Y50 → Y500 |
Gonzales | G, 0, 5, 2, 4, 2 | G524 |
Gonzalez | G, 0, 5, 2, 4, 2 | G524 |
El algoritmo se puede implementar en R con el paquete phonics
(Howard et al. 2020) de la siguiente manera
library(pacman)
p_load(phonics)
nombres <- c("Catalina", "Katalina", "Yovana", "Jovanna", "Giovanna", "Yenny", "Yeni", "Gonzalez", "Gonzales")
codigos_soundex <- soundex(limpiar_texto(nombres))
names(codigos_soundex) <- nombres
codigos_soundex
## Catalina Katalina Yovana Jovanna Giovanna Yenny Yeni Gonzalez
## "C345" "K345" "Y150" "J150" "G150" "Y500" "Y500" "G524"
## Gonzales
## "G524"
6.3.3 Metaphone
El algoritmo Metaphone es una técnica de codificación fonética desarrollada por Lawrence Philips en 1990 (Philips 1990), diseñada para mejorar la coincidencia de palabras con escritura diferente pero pronunciación similar. A diferencia de algoritmos como Soundex, Metaphone no se limita al análisis de nombres en inglés, lo que lo convierte en una alternativa útil para la deduplicación de datos en contextos de otros idiomas, como los encontrados en los censos y encuestas de cobertura en América Latina y el Caribe.
Una ventaja clave de Metaphone es que no asigna códigos numéricos sino representaciones fonéticas alfabéticas, lo que permite una mayor precisión fonética, especialmente para consonantes. El algoritmo captura 16 sonidos consonánticos comunes en múltiples idiomas y los representa en la transcripción resultante.
No obstante, como fue diseñado originalmente para el inglés, su aplicación en nombres de origen hispano o indígena puede ser limitada. Para superar estas limitaciones, se desarrollaron algoritmos posteriores como Double Metaphone, que permite hasta dos codificaciones por palabra para capturar variaciones fonéticas adicionales, especialmente útiles en bases de datos que tienen varios idiomas (P. Christen 2012).
El algoritmo se puede implementar en R con el paquete phonics
de la siguiente manera:
codigos_metaphone <- metaphone(limpiar_texto(nombres))
names(codigos_metaphone) <- nombres
codigos_metaphone
## Catalina Katalina Yovana Jovanna Giovanna Yenny Yeni Gonzalez
## "KTLN" "KTLN" "YFN" "JFN" "JFN" "YN" "YN" "KNSLS"
## Gonzales
## "KNSLS"
Note que este algoritmo resulta más preciso para los nombres y apellidos de nuestro ejemplo, generando la misma codificación para los nombres que suenan igual.
6.3.4 Algoritmo Statistics Canada
El algoritmo fonético desarrollado por Statistics Canada, también conocido como el método de Lynch y Arends (Lynch, Arends, et al. 1977), es una alternativa simple y eficiente para la codificación fonética de nombres, ampliamente utilizada en censos y procesos de vinculación de registros administrativos en Canadá.
Este método es útil cuando se requiere una solución rápida, pero con capacidad de captura de errores comunes de transcripción y ortografía. Es especialmente relevante en contextos de censos de población y encuestas de gran escala en países de América Latina y el Caribe, donde los nombres pueden tener múltiples variantes fonéticas y ortográficas debido a la diversidad cultural.
Entre las características principales del algoritmo se encuentran:
- Elimina las vocales, conservando únicamente la estructura consonántica de los nombres.
- Reduce sonidos duplicados, unificando repeticiones que suelen aparecer por errores de tipeo o escritura fonética.
- No recodifica letras individuales, lo que disminuye la carga computacional.
- Proporciona una forma simplificada de agrupación fonética que no depende del idioma, a diferencia de algoritmos como Soundex o Metaphone.
codigos_statcan <- statcan(limpiar_texto(nombres))
names(codigos_statcan) <- nombres
codigos_statcan
## Catalina Katalina Yovana Jovanna Giovanna Yenny Yeni Gonzalez
## "CTLN" "KTLN" "YVN" "JVN" "GVN" "YN" "YN" "GNZL"
## Gonzales
## "GNZL"
Hay otras alternativas que pueden ser utilizadas, en Howard et al. (2020) se pueden encontrar otros algoritmos como NYSIIS, Caverphone, Cologne, RogerRoot, Phonex o MRA.
6.3.5 Adaptación para Encuestas de América Latina
A diferencia de los algoritmos fonéticos clásicos como Soundex, Metaphone y StatCan, que fueron desarrollados principalmente para nombres de origen anglosajón, en América Latina los nombres presentan una gran diversidad fonética y ortográfica influenciada por lenguas indígenas, castellano, portugués y otras tradiciones europeas. Por ello, se ha desarrollado un algoritmo personalizado que tiene en cuenta las transformaciones fonéticas y ortográficas más comunes en la región.
La función codif_fonetico()
fue diseñada por los autores de este material para capturar las variantes más frecuentes en los nombres latinoamericanos, mediante las siguientes transformaciones:
- Reducción de dobles letras y sílabas características: ll → y, qu → k, ch → x.
- Conversión de combinaciones como ce, ci a se, si; y gue, gui a gi.
- Reglas específicas como ^j → y, ^hua → wa, y ^hu → w, comunes en nombres quechuas o aimaras.
- Normalización de acentos, letra ñ y otros caracteres mediante stri_trans_general(…, “Latin-ASCII”).
- Eliminación de vocales y letras mudas para capturar la estructura fonética esencial.
- Conversión de v a b, y de z a s, fonéticamente indistinguibles en la mayoría de los dialectos del español latinoamericano.
El orden en que se aplican las transformaciones también juega un rol especial, el usuario puede ampliar las reglas si así lo desea, incorporando nuevas líneas.
require(stringi)
require(stringr)
codif_fonetico <- function(nombre) {
nombre <- tolower(nombre)
nombre <- gsub("lly", "li", nombre)
nombre <- gsub("ll", "y", nombre)
nombre <- gsub("yn$", "in", nombre)
nombre <- gsub("^hu", "w", nombre)
nombre <- gsub("^hua", "wa", nombre)
nombre <- gsub("^qui|^qhi", "ki", nombre)
nombre <- gsub("^xi", "ji", nombre)
nombre <- gsub("^j", "y", nombre)
nombre <- gsub("^gio", "yo", nombre)
nombre <- gsub("y$", "i", nombre)
nombre <- gsub("\\b(\\w*)hui(\\w*)\\b", "\\1wi\\2", nombre)
nombre <- gsub("ch", "x", nombre)
nombre <- gsub("[aeiouh]", "", nombre)
nombre <- gsub("v", "b", nombre)
nombre <- gsub("z", "s", nombre)
nombre <- str_replace_all(nombre, "c(?=[ei])", "s")
nombre <- gsub("c", "k", nombre)
nombre <- gsub("qu", "k", nombre)
nombre <- str_replace_all(nombre, "g(?=[ei])", "j")
nombre <- gsub("gue|gui", "gi", nombre)
nombre <- stri_trans_general(nombre, "Latin-ASCII")
nombre <- gsub("(.)\\1+", "\\1", nombre)
nombre <- gsub("[aeiou]", "", nombre)
toupper(nombre)
}
A continuación se presenta la aplicación para nuestro ejemplo
datos <- data.frame(nombre = nombres) |>
mutate(nombre = limpiar_texto(nombre)) |>
mutate(codif = codif_fonetico(nombre))
nombre | codif |
---|---|
catalina | KTLN |
katalina | KTLN |
yovana | YBN |
jovanna | YBN |
giovanna | YBN |
yenny | YN |
yeni | YN |
gonzalez | GNSLS |
gonzales | GNSLS |
Considere otro ejemplo. La siguiente tabla presenta el resultado de aplicar los algoritmos fonéticos al campo del nombre. En este caso, se puede observar que el método propuesto, columna nom_latino, origina un mejor resultado que los otros algoritmos.
nom <- df |>
mutate(soundex =soundex(limpiar_texto(nombre)),
metaphone = metaphone(limpiar_texto(nombre)),
statcan = statcan(limpiar_texto(nombre)),
latino = codif_fonetico(limpiar_texto(nombre)))
nombre | apellido | soundex | metaphone | statcan | latino |
---|---|---|---|---|---|
Wilmer | Huanca | W456 | WLMR | WLMR | WLMR |
Guilmer | Wuanca | G456 | KLMR | GLMR | GLMR |
Wilmar | Guanca | W456 | WLMR | WLMR | WLMR |
Yohana | Kuispe | Y500 | YHN | YHN | YN |
Johanna | Quispe | J500 | JHN | JHN | YN |
Bryan | Kispe | B650 | BRYN | BRN | BRYN |
Brayan | Qhispe | B650 | BRYN | BRN | BRYN |
Marleni | Rodriguez | M645 | MRLN | MRLN | MRLN |
Marleny | Rodrigues | M645 | MRLN | MRLN | MRLN |
Marlenni | Rodriwues | M645 | MRLN | MRLN | MRLN |
Nely | Ñahui | N400 | NL | NL | NL |
Neli | Nahui | N400 | NL | NL | NL |
Nelly | Nahuy | N400 | NL | NL | NL |
Ximena | Ñawi | X550 | SMN | XMN | YMN |
Jimena | Ñahui | J550 | JMN | JMN | YMN |
En el caso del apellido, es fundamental tener en cuenta las particularidades culturales de cada región, ya que pueden influir significativamente en la forma en que son escritos o pronunciados. Estas variaciones hacen que ningún algoritmo de codificación fonética sea completamente robusto por sí solo, por lo que es recomendable adaptar o complementar los métodos según el contexto local.
apell <- df |>
mutate(soundex =soundex(limpiar_texto(apellido)),
metaphone = metaphone(limpiar_texto(apellido)),
statcan = statcan(limpiar_texto(apellido)),
latino = codif_fonetico(limpiar_texto(apellido)))
nombre | apellido | soundex | metaphone | statcan | latino |
---|---|---|---|---|---|
Wilmer | Huanca | H520 | HNK | HNC | WNK |
Guilmer | Wuanca | W520 | WNK | WNC | WNK |
Wilmar | Guanca | G520 | KNK | GNC | GNK |
Yohana | Kuispe | K210 | KSP | KSP | KSP |
Johanna | Quispe | Q210 | KSP | QSP | KSP |
Bryan | Kispe | K210 | KSP | KSP | KSP |
Brayan | Qhispe | Q210 | KHSP | QHSP | KSP |
Marleni | Rodriguez | R362 | RTRKS | RDRG | RDRGS |
Marleny | Rodrigues | R362 | RTRKS | RDRG | RDRGS |
Marlenni | Rodriwues | R362 | RTRWS | RDRW | RDRWS |
Nely | Ñahui | N000 | NH | NH | NW |
Neli | Nahui | N000 | NH | NH | NW |
Nelly | Nahuy | N000 | NH | NH | NW |
Ximena | Ñawi | N000 | NW | NW | NW |
Jimena | Ñahui | N000 | NH | NH | NW |
La siguiente tabla muestra el resultado de aplicar la función codif_fonetico
tanto al nombre como al apellido. No obstante, se recomienda utilizar en cada campo el algoritmo fonético que mejor se adapte a las características lingüísticas y culturales del caso específico.
res <- df |>
mutate(nom_cod = codif_fonetico(limpiar_texto(nombre)),
ape_cod = codif_fonetico(limpiar_texto(apellido)))
nombre | apellido | nom_cod | ape_cod |
---|---|---|---|
Wilmer | Huanca | WLMR | WNK |
Guilmer | Wuanca | GLMR | WNK |
Wilmar | Guanca | WLMR | GNK |
Yohana | Kuispe | YN | KSP |
Johanna | Quispe | YN | KSP |
Bryan | Kispe | BRYN | KSP |
Brayan | Qhispe | BRYN | KSP |
Marleni | Rodriguez | MRLN | RDRGS |
Marleny | Rodrigues | MRLN | RDRGS |
Marlenni | Rodriwues | MRLN | RDRWS |
Nely | Ñahui | NL | NW |
Neli | Nahui | NL | NW |
Nelly | Nahuy | NL | NW |
Ximena | Ñawi | YMN | NW |
Jimena | Ñahui | YMN | NW |
Ahora aplicaremos la función codif_fonetico
a nuestros conjuntos de datos del censo y de la encuesta