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:

  1. 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.

  2. 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.

  3. 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.

  4. 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).

  5. 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:

  1. Elimina las vocales, conservando únicamente la estructura consonántica de los nombres.
  2. Reduce sonidos duplicados, unificando repeticiones que suelen aparecer por errores de tipeo o escritura fonética.
  3. No recodifica letras individuales, lo que disminuye la carga computacional.
  4. 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:

  1. Reducción de dobles letras y sílabas características: ll → y, qu → k, ch → x.
  2. Conversión de combinaciones como ce, ci a se, si; y gue, gui a gi.
  3. Reglas específicas como ^j → y, ^hua → wa, y ^hu → w, comunes en nombres quechuas o aimaras.
  4. Normalización de acentos, letra ñ y otros caracteres mediante stri_trans_general(…, “Latin-ASCII”).
  5. Eliminación de vocales y letras mudas para capturar la estructura fonética esencial.
  6. 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

censo_limpio <- censo_limpio |> 
                mutate(across(c(nombre, apellido), ~codif_fonetico(.), .names = "{.col}_cod"))

encuesta_limpia <- encuesta_limpia |> 
                   mutate(across(c(nombre, apellido), ~codif_fonetico(.), .names = "{.col}_cod"))

References

Christen, Peter. 2012. Data Matching Concepts and Techniques for Record Linkage, Entity Resolution, and Duplicate Detection. Springer.
Churches, Tim, Peter Christen, Kim Lim, and Justin Xi Zhu. 2002. “Preparation of Name and Address Data for Record Linkage Using Hidden Markov Models.” BMC Medical Informatics and Decision Making 2: 1–16.
Herzog, Thomas N, Fritz J Scheuren, and William E Winkler. 2007. Data Quality and Record Linkage Techniques. Vol. 1. Springer.
Howard, James P et al. 2020. “Phonetic Spelling Algorithm Implementations for r.” Journal of Statistical Software 95: 1–21.
Lynch, Billy T, William L Arends, et al. 1977. “Selection of a Surname Coding Procedure for the SRS Record Linkage System.” Washington, DC: US Department of Agriculture, Sample Survey Research Branch, Research Division.
Nauman, Felix, and Melanie Herschel. 2022. An Introduction to Duplicate Detection. Springer Nature.
Navarro, Gonzalo. 2001. “A Guided Tour to Approximate String Matching.” ACM Computing Surveys (CSUR) 33 (1): 31–88.
Odell, Margaret, and Robert Russell. 1918. “The Soundex Coding System.” US Patents 1261167: 9.
Philips, Lawrence. 1990. “Hanging on the Metaphone.” Computer Language 7 (12): 39–43.