Datos no estructurados (I)
Limpieza y normalización (1)
En la práctica diaria de los Servicios de Orientación (SEO), la información no suele presentarse en tablas de datos o archivos de Excel; generalmente se encuentra contenida en textos, lo que implica que se presenta de forma no estructurada. Un ejemplo son los documentos médicos que presentan las familias, los PTI que elabora el profesorado o los propios informes psicopedagógicos.
Para un SEO analizar estos documentos y extraer la información que contienen es, con frecuencia, más un deseo que una realidad, ya que transformar este volumen de texto en conocimiento útil supone disponer de un tiempo del que no disponeme. La solución puede venir de la mano de los medios informáticos que faciliten automatizar los procedimientos de extracción de datos, pero el primer problema a resolver pasa por la limpieza de formato y ruido digital, ya que sin ella esa automatización no es viable.
Un documento digitalizado contiene numerosos elementos que nuestros ojos omiten o a los que no damos importancia porque no afectan a nuestro modo de procesar la información, pero sí, y mucho, al modo de procesarla de los medios digitales. Para un ordenador los encabezados repetidos, los números de página que cortan un párrafo a la mitad, las URLs largas los espacios dobles y los elementos de formato incorrectos, todo ese contenido es "ruido" que fragmenta la frase y distorsiona los análisis. Un ejemplo: Si un sistema analiza la frecuencia de palabras para detectar determinados problemas, el acoso escolar, por ejemplo, mezclar el código HTML, trabajar con textos rotos por guiones de final de línea de un PDF (co- / nvivencia) o afrontar las variantes de codificación de una misma palabra (más y más) restan precisión al sistema, genera errores en su funcionamiento y provoca fallos que pueden afectar a la fluidez del proceso, cuando no invalidarlo. Ante esto, la normalización y el filtrado estructural corrigen los defectos y facilitan unificar el texto.
Te presento a continuación un ejemplo (un tanto forzado) del tipò de ruido que puede incluir un texto
El alumno presenta un comp- ortamiento atípico en el Áula. se observa un incre- mento de la ansiédad durante las clases de Matemáticas. --- Página 12 --- Protocolo de Detección Primaria El orientador indíca que el caso es URGENTE y requiere atención inmediata (ver informe en https://edu.gob.es/sec/id?8492). Los padres no co- / operan con las pautas. El pró- ximo mércoles se realizará otra evaluaciÓn psicológica. Nota importante: No se de- be clasi_ficar al menor sin más evidencias científicas.
Veamos un script de Python que además de limpiar el ruido normaliza el texto, eliminando las mayúsculas, las tildes y los signos de puntuación, algo que es necesario en función de la normalización del texto para su posterior tratamiento.
import re
import unicodedata
# 1. El texto original corrupto
'''
Podemos sustituir la inclusión del texto en el script mediante un procedimiento que
permita escribirlo directamente:
with open("informe.txt", "r", encoding="utf-8") as archivo:
texto_sucio = archivo.read()
'''
texto_sucio = """
El alumno presenta un comp-
ortamiento atípico en el Áula. se observa un incre-
mento de la ansiédad durante las clases de Matemáticas.
--- Página 12 --- Protocolo de Detección Primaria
El orientador indíca que el caso es URGENTE y requiere
atención inmediata (ver informe en https://edu.gob.es/sec/id?8492).
Los padres no co- / operan con las pautas. El pró-
ximo mércoles se realizará otra evaluaciÓn psicológica.
Nota importante: No se de-
be clasi_ficar al menor sin más evidencias científicas.
"""
# Función de limpieza y normalización -------------------------------------------------
def normalizacion_agresiva(texto):
print("--- INICIANDO NORMALIZACIÓN PARA ANÁLISIS ESTADÍSTICO (SIN TILDES) ---")
# PASO 1: Reparación exacta de Mojibake web (Unicode) antes de limpiar
texto = texto.replace("ansi\u00c3\u00a9dad", "ansiedad")
texto = texto.replace("m\u00c3\u00a9rcoles", "miércoles")
# PASO 2: Eliminar etiquetas HTML
texto = re.sub(r'<[^>]+>', ' ', texto)
# PASO 3: Eliminar líneas de paginación
texto = re.sub(r'---.*---.*', '', texto)
# PASO 4: Eliminar URLs completas
texto = re.sub(r'https?://\S+', '', texto)
# PASO 5: Unir palabras rotas al final de la línea
texto = re.sub(r'(\w+)-\s*(?:/\s*)?[\r\n]+\s*(\w+)', r'\1\2', texto)
# PASO 6: Limpieza de caracteres extraños aislados (como el guion bajo)
texto = texto.replace('_', '')
# PASO 7: Convertir todo el texto a minúsculas (Estandarización)
texto = texto.lower()
# PASO 8: Eliminación de tildes (Normalización Unicode KD)
# Separa las letras de sus acentos (ej. 'á' se convierte en 'a' + '´')
texto_descompuesto = unicodedata.normalize('NFKD', texto)
# Filtra el texto manteniendo solo los caracteres que no sean signos de acentuación (Non-Spacing Marks)
texto_sin_tildes = "".join([c for c in texto_descompuesto if not unicodedata.combining(c)])
# PASO 9: Limpieza final de signos de puntuación (opcional pero recomendado para conteo de palabras)
# Mantiene solo letras, números y espacios
texto_limpio = re.sub(r'[^\w\s]', '', texto_sin_tildes)
# PASO 10: Normalización de espacios en blanco
texto_limpio = re.sub(r'\s+', ' ', texto_limpio).strip()
return texto_limpio
# Ejecución del script --------------------------------------------
if __name__ == "__main__":
resultado_plano = normalizacion_agresiva(texto_sucio)
print("\nTEXTO COMPLETAMENTE NORMALIZADO (Listo para conteo/frecuencias):")
print(resultado_plano)
print("-" * 50)
El resultado que obtendremos será el siguiente:
Observarás que, además de desaparecer el "ruido" también han desaparecido las mayúsculas, los signos de puntuación y las tildes, cosa que no se ajusta a nuestra idea de "corrección ortográfica". Esto es así porque estos procedimientos de "limpieza" lo son de normalización, por lo que incluyen la eliminación de todo aquello que incide negativamente en el tratamiento posterior del texto.
Además soluciones como la propuesta presentan una limitación: es necesario ajustarlas a la realidad del texto a procesar, ya que no existe una "limpiador universal". Una forma de acercarse lo más posible a esa universalidad es crear un procedimiento que permita un uso modular del mismo, como podría ser el ejemplo que sigue:
import re
import unicodedata
# El texto de trabajo (puede ser cualquiera)
texto_sucio = """
El alumno presenta un comp-
ortamiento atípico en el Áula. se observa un incre-
mento de la ansiédad durante las clases de Matemáticas.
--- Página 12 --- Protocolo de Detección Primaria
El orientador indíca que el caso es URGENTE y requiere
atención inmediata (ver informe en https://edu.gob.es/sec/id?8492).
Los padres no co- / operan con las pautas. El pró-
ximo mércoles se realizará otra evaluaciÓn psicológica.
Nota importante: No se de-
be clasi_ficar al menor sin más evidencias científicas.
"""
def limpiador_modular(texto):
print("--- INICIANDO LIMPIEZA MODULAR ADAPTATIVA ---")
# =========================================================================
# PASO 1: REPARACIÓN DE MOJIBAKE (Errores de codificación web/servidor)
# COMENTAR si el texto proviene de un documento limpio o Word nativo.
# =========================================================================
texto = texto.replace("ansi\u00c3\u00a9dad", "ansiedad")
texto = texto.replace("m\u00c3\u00a9rcoles", "miércoles")
# =========================================================================
# PASO 2: ELIMINAR ETIQUETAS HTML / XML
# COMENTAR si el texto no proviene de páginas web, emails o plataformas digitales.
# =========================================================================
texto = re.sub(r'<[^>]+>', ' ', texto)
# =========================================================================
# PASO 3: ELIMINAR LÍNEAS DE PAGINACIÓN O ENCABEZADOS REPETITIVOS
# COMENTAR si el documento original no tiene saltos de página marcados por patrones.
# =========================================================================
texto = re.sub(r'---.*---.*', '', texto)
# =========================================================================
# PASO 4: ELIMINAR ENLACES WEB Y ENLACES DE CORREO
# COMENTAR si las URLs contienen información relevante que quieras conservar.
# =========================================================================
texto = re.sub(r'https?://\S+', '', texto)
# =========================================================================
# PASO 5: RECOMPOSICIÓN DE PALABRAS ROTAS (Guiones de final de línea)
# COMENTAR si el texto no viene de un PDF escaneado con texto justificado.
# CUIDADO: Puede unir palabras compuestas legítimas (ej. "teórico-práctico").
# =========================================================================
texto = re.sub(r'(\w+)-\s*(?:/\s*)?[\r\n]+\s*(\w+)', r'\1\2', texto)
# =========================================================================
# PASO 6: LIMPIEZA DE CARACTERES EXTRÁNEOS DE FORMATO (Guiones bajos, símbolos aislados)
# COMENTAR o modificar según los caracteres raros específicos de tu base de datos.
# =========================================================================
texto = texto.replace('_', '')
# =========================================================================
# PASO 7: CONVERSIÓN A MINÚSCULAS
# OBLIGATORIO para análisis estadístico tradicional (Frecuencias, TF-IDF).
# COMENTAR si vas a usar IA Generativa (ya que penaliza la pérdida de mayúsculas).
# =========================================================================
texto = texto.lower()
# =========================================================================
# PASO 8: ELIMINACIÓN DE TILDES (Normalización Unicode)
# OBLIGATORIO para conteo de palabras homogéneo.
# COMENTAR si vas a usar IA Generativa (necesita las tildes para la semántica).
# =========================================================================
texto_descompuesto = unicodedata.normalize('NFKD', texto)
texto = "".join([c for c in texto_descompuesto if not unicodedata.combining(c)])
# =========================================================================
# PASO 9: ELIMINACIÓN DE SIGNOS DE PUNTUACIÓN Y SÍMBOLOS
# Deja únicamente caracteres alfanuméricos y espacios.
# COMENTAR si la estructura de las frases (puntos y comas) es necesaria.
# =========================================================================
texto = re.sub(r'[^\w\s]', '', texto)
# =========================================================================
# PASO 10: NORMALIZACIÓN DE ESPACIOS EN BLANCO
# Crucial al final para limpiar los "huecos" dejados por los pasos anteriores.
# NO RECOMENDABLE COMENTAR (Mantiene la salud del formato).
# =========================================================================
texto = re.sub(r'\s+', ' ', texto).strip()
return texto
if __name__ == "__main__":
texto_final = limpiador_modular(texto_sucio)
print("\nRESULTADO CON CONFIGURACIÓN ACTUAL:")
print(texto_final)
Esto nos lleva, no obstante, a que nos planteemos si debemos "limpiar" siempre los textos. Este enfoque es adecuado cuando empleamos procedimientos estadísticos como base para la automatización dentro de proyectos como la creación de nubes de palabras o mapas de calor de términos recurrentes, pero ni es necesario ni conveniente si usamos modelos de Inteligencia Artificial basados en Deep Learning o en Transformers para analizar la documentación. En este caso la limpieza agresiva es innecesaria (ya que estos modelos pueden trabajar con ruido) y además resulta contraproducente: por ejemplo, para estos modelos la puntuación es muy relevante ya que, al igual que para nosotros, un signo de interrogación aporta el contexto de una duda y una coma modifica la relación entre conceptos.
En consecuencia, se presentan tres escenarios posibles, siendo necesario que, como profesionales, identifiquenos nuestro objetivo al trabajar con textos y, según éste, decidamos el procedmiento a seguir:
- La limpieza agresiva es pertinente en tareas como el análisis de tendencias en la formulación de demandas o en el análisis del contenido de "buzones de convivencia" en los que procedemos mediante conteo de palabras. En estos casos podemos necesitar aislar conceptos puros (ej. "ansiedad", "redes", "estudio") eliminando el ruido formal.
- La limpieza intermedia es una opción válidad en tareas de clasificación automática del nivel de prioridad de las derivaciones utilizando modelos híbridos o redes neuronales superficiales. Aquí, las mayúsculas sostienen información (ej. escribir "URGENTE"). El texto se limpia de errores de formato, pero se preserva la intensidad de la puntuación en un canal numérico secundario que el algoritmo aprovecha.
- La limpieza agresiva es improcedente por perjudicial cuando deseamos realizar la extracción automatizada de variables clínicas en informes psicopedagógicos mediante modelos de lenguaje locales. Aquí se debe conservar la integridad total del texto (mayúsculas, formato de listas, conectores) para que el modelo interprete correctamente los datos en todas su complejidad.
Dicho lo anterior, pero volviendo al momento actual, es correcto que nos planteemos estos procedimientos de limpieza y normalización, como base para el desarrollo de determinados procedimientos de automatización.
Fuentes para ampliación
- Bird, S., Klein, E., y Loper, E. (2009). Natural Language Processing with Python. O'Reilly Media. Manual de referencia para entender el procesamiento de texto plano y el filtrado de ruido estructural.
- Agencia Española de Protección de Datos (AEPD) Orientaciones para la validación de tratamientos basados en Inteligencia Artificial en relación al RGPD. Documento de obligada lectura para el diseño de sistemas locales que procesen datos de menores.
- Goldberg, Y. (2017). Neural Network Methods in Natural Language Processing. Morgan & Claypool. Explicación de los efectos de la alteración de la sintaxis en modelos basados en redes neuronales.
(1)La idea del contenido, el esquema temático y el modelo de script de esta entrada han sido desarrollados por el titular del blog partiendo de contenido-fuente generado por IA-Gemini. El desarrollo y la sucesión del contenidos, así como la redacción son responsabilidad única y exclusiva del autor.
No hay comentarios:
Publicar un comentario
Comenta esta entrada