Recuperación y limpieza de datos

El viaje de la ciencia de datos comienza siempre con la obtención de materia prima. Para construir cualquier modelo predictivo o extraer conclusiones analíticas, primero debemos dominar la ingeniería de adquisición de datos.

En este artículo, exploraremos las estrategias esenciales para recuperar información desde múltiples orígenes —incluyendo bases de datos relacionales (SQL), no relacionales (NoSQL), interfaces de programación (APIs) y almacenes en la nube—, centrándonos en la ingesta práctica de los formatos de archivo más extendidos del sector: CSV y JSON.

La Ingesta de Archivos CSV (Comma Separated Values)

El formato CSV (Valores Separados por Comas) es uno de los estándares más antiguos y populares para el intercambio de datos estructurados. Consiste en archivos de texto plano organizados en filas, donde cada dato o valor se encuentra delimitado convencionalmente por una coma.

A través de la librería Pandas en Python, la lectura e integración de estos archivos en estructuras tabulares de memoria conocidas como DataFrames se resuelve con apenas unas pocas líneas de código.

Implementación básica en Python

Para cargar un archivo CSV en nuestro entorno de desarrollo (como un Jupyter Notebook), se sigue el flujo de importación y definición de rutas estándar:

import pandas as pd

# Definición de la ruta del archivo estructurado
file_path = "data/iris_data.csv"

# Ingesta del archivo mediante la función nativa de Pandas
data = pd.read_csv(file_path)

# Visualización de las primeras 5 observaciones en el notebook
print(data.iloc[0:5])

Al ejecutar estas instrucciones utilizando un conjunto de datos como el histórico Iris, Pandas procesará la estructura de texto y nos devolverá una representación tabular limpia con sus respectivas columnas de características y objetivo:

sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa

Parámetros avanzados para el control de lectura

En la práctica, los archivos CSV del mundo real rara vez vienen perfectamente formateados. La función pd.read_csv cuenta con una serie de argumentos cruciales para mitigar inconsistencias durante la ingesta:

  • Manejo de delimitadores alternativos (sep): No todos los archivos mal llamados “CSV” usan comas. Si nos enfrentamos a un archivo separado por tabuladores (TSV), podemos modificar el separador usando la sintaxis de escape de Python para tabulaciones (\t):
df = pd.read_csv(file_path, sep='\t')
  • Manejo de espacios en blanco (delim_whitespace): Cuando los datos se separan mediante espacios en blanco variables o irregulares en lugar de un carácter fijo, habilitar este parámetro le indica a Pandas que trate cualquier secuencia de espacios como un único delimitador válido.
  • Gestión de cabeceras erróneas o ausentes (header): Muchos archivos del mundo real incluyen comentarios o filas vacías en la parte superior. Con el argumento header, podemos especificar explícitamente qué número de fila debe ser interpretado como el nombre de las columnas.
  • Asignación manual de nombres (names): Si el archivo carece por completo de una fila de títulos, podemos pasar una lista de cadenas con los nombres deseados a través del parámetro names. Es obligatorio asegurar que la longitud de esta lista coincida exactamente con el número de columnas físicas del archivo.
  • Tratamiento personalizado de valores nulos (na_values): En ocasiones, los errores de captura o la ausencia de datos se representan con cadenas específicas (como “NA”, “N/A”, o códigos numéricos anómalos como 99). Pasar estos patrones al parámetro na_values fuerza a Pandas a convertirlos automáticamente en valores nulos reales (NaN), facilitando su posterior limpieza estadística.

Gestión de Archivos JSON (JavaScript Object Notation)

El formato JSON (Notación de Objetos JavaScript) es el estándar por excelencia para la transferencia de información semiestructurada en la web. Es la estructura nativa en la que opera la inmensa mayoría de las APIs modernas y las bases de datos no relacionales (NoSQL).

Su propósito es almacenar información de una manera organizada, jerárquica y fácil de leer tanto para humanos como para máquinas. Conceptualmente, la estructura de un archivo JSON es idéntica a la de los diccionarios de Python, basada en un sistema de pares clave-valor (key-value).

Si analizamos una única fila de datos mapeada en formato JSON, la clave actúa como el nombre de la característica (columna) y el valor representa el dato de esa celda en particular:

{
  "sepal_length": 5.1,
  "sepal_width": 3.5,
  "petal_length": 1.4,
  "petal_width": 0.2,
  "species": "setosa"
}

Lectura y Escritura de JSON en Pandas

La manipulación de estas estructuras se realiza de manera simétrica a los archivos planos mediante funciones optimizadas de entrada y salida:

  • Lectura de datos (pd.read_json): Para cargar el archivo semiestructurado en memoria y transformarlo en un DataFrame plano, basta con invocar la función pasando la ruta correspondiente:
df_json = pd.read_json("data/iris_data.json")
  • Escritura de datos (to_json): Una vez que hayamos concluido los procesos de transformación o limpieza sobre nuestras variables en memoria, podemos exportar el DataFrame de vuelta a un archivo JSON físico definiendo el nombre de salida:
data.to_json("data/output_cleaned.json")

Resolución de problemas de orientación espacial (orient)

A diferencia de las tablas rígidas de un CSV, un archivo JSON puede estructurarse u orientarse de múltiples formas (por registros, por columnas, por índices o dividido en listas independientes).

Si al importar un archivo JSON los datos se cargan de forma desalineada o el intérprete arroja un error de lectura, el primer paso obligado es revisar la documentación del parámetro orient. Probar diferentes configuraciones de este argumento (como split, records, index, columns o values) le explicará a Pandas la dirección exacta en la que se organizaron las claves dentro del fichero, asegurando una conversión exitosa a filas y columnas.

Conexión e Ingesta desde Bases de Datos SQL

Cuando trabajamos en entornos corporativos o con infraestructuras de datos maduras, la información estructurada suele almacenarse en sistemas de gestión de bases de datos relacionales o SQL (Structured Query Language). Estas bases de datos se caracterizan por tener un esquema fijo y altamente estructurado, organizando la información en tablas interconectadas mediante claves funcionales.

Existen múltiples motores SQL en el mercado, cada uno con sutiles diferencias de sintaxis pero con una lógica operativa idéntica:

  • Sistemas Tradicionales / Locales: PostgreSQL, MySQL, Microsoft SQL Server, Oracle DB o IBM Db2.
  • Sistemas de Almacenamiento en la Nube (Data Warehouses): AWS Redshift o Google BigQuery.

Para interactuar con ellos desde Python, necesitamos utilizar la librería conectora específica de cada motor junto con los métodos nativos de Pandas.

Implementación Práctica con SQLite

El paquete estándar sqlite3 viene integrado en la instalación nativa de Python y es ideal para entornos de pruebas o bases de datos ligeras en archivo. El flujo de trabajo consta de tres fases: inicializar la ruta física, abrir un canal de comunicación o conexión (con) y lanzar la consulta analítica (query).

import sqlite3
import pandas as pd

# 1. Definición de la ruta donde reside la base de datos relacional
db_path = "data/classic_rock.db"

# 2. Establecimiento del canal de conexión física
con = sqlite3.connect(db_path)

# 3. Construcción de la consulta estructurada en formato string
query = "SELECT * FROM rock_songs;"

# 4. Ingesta directa del resultado SQL hacia un DataFrame de Pandas
df_rock = pd.read_sql(query, con)

Alternativas de Conexión Corporativa

Si necesitas migrar este mismo bloque de código para extraer datos de los servidores de producción de tu empresa, solo debes sustituir el conector inicial importando el controlador adecuado:

  • SQLAlchemy: Una de las librerías de mapeo objeto-relacional más potentes, compatible con casi cualquier motor SQL del mercado.
  • Psycopg2: El driver optimizado específico para infraestructuras basadas en PostgreSQL.
  • ibm_db: El conector dedicado para la familia de bases de datos IBM Db2.

Ingesta desde bases de datos NoSQL (No Relacionales)

En el polo opuesto encontramos las bases de datos NoSQL, diseñadas para almacenar información no estructurada o semiestructurada. Al prescindir de esquemas rígidos de filas y columnas fijas, reducen drásticamente los costes de infraestructura tecnológica y permiten lecturas masivas a gran velocidad.

La mayoría de los motores NoSQL orientados a documentos almacenan sus registros directamente en el formato JSON que analizamos previamente.

Tipos principales de arquitecturas NoSQL

  1. Bases de Datos Documentales (ej. MongoDB): Cada observación equivale a un “documento” independiente (un archivo jerárquico JSON) con sus propias propiedades internas.
  2. Almacenes Clave-Valor (ej. Redis): Sistemas hiperrápidos donde se apunta a un identificador único (ID) para recuperar un bloque de datos asociado (nombre, dirección, etc.).
  3. Bases de Datos de Grafos (ej. Neo4j): Diseñadas de forma nativa para el análisis de redes complejas. Son el motor detrás de plataformas como LinkedIn o Facebook, estructuradas para calcular conexiones de primer, segundo o tercer nivel entre usuarios de forma inmediata.
  4. Familias de Columnas Anchas (ej. Cassandra): Agrupan columnas lógicamente relacionadas en “familias de columnas”. Por ejemplo, los datos de un usuario pueden segmentarse en una familia Personal (nombre, edad) y otra Profesional (experiencia, visa).

Conexión y Extracción en MongoDB con pymongo

Para interactuar con una base de datos NoSQL como MongoDB, importamos el método MongoClient para abrir la conexión corporativa (la cual puede requerir credenciales de seguridad si el servidor reside en la nube).

from pymongo import MongoClient
import pandas as pd

# 1. Apertura del cliente de conexión (local o URL en la nube)
con = MongoClient()

# 2. Selección de la base de datos específica dentro del servidor
db = con.database_name

# 3. Consulta de la colección de documentos mediante llaves vacías {} (equivale a SELECT *)
# Esto genera un objeto cursor que funciona como un generador de documentos JSON
cursor = db.collection_name.find({})

# 4. Conversión del generador en una lista nativa de diccionarios Python
dict_list = list(cursor)

# 5. Volcado de la lista estructurada dentro del DataFrame analítico
df_nosql = pd.DataFrame(dict_list)

Acceso a Datos en la Nube y APIs Públicas

Muchos proveedores de datos corporativos no exigen conexiones directas a servidores de bases de datos; en su lugar, habilitan APIs (Application Programming Interfaces). Las APIs actúan como ventanillas de servicio técnico automatizadas: envías una solicitud con ciertos parámetros y la API te devuelve un archivo estructurado (normalmente JSON) de forma inmediata. Un ejemplo común es el uso de APIs para extraer tweets históricos o métricas publicitarias desde plataformas como Amazon o Google.

Asimismo, la comunidad científica suele alojar bases de datos de libre acceso directamente en la nube. Pandas permite saltarse la descarga manual de estos archivos en tu ordenador; si conoces la dirección web (URL) del fichero, puedes inyectarlo directamente en memoria:

import pandas as pd

# Definición de la URL del set de datos en un repositorio en la nube (ej. UC Irvine ML Repository)
data_url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"

# Descarga, procesamiento y conversión a DataFrame en un único paso
df_cloud = pd.read_csv(data_url)