Volver al inicio

Parquet en Python: lectura, escritura y optimización

Guía para el uso práctico de Parquet en Python para ingenieros de datos. Muestra cómo leer de manera eficiente con filtrado, escribir con esquema explícito y codificación de diccionario, configurar grupo de filas y compresión, y aplicar particionamiento para grandes conjuntos de datos.

Parquet en producción: de pandas a pyarrow
Advertisement 728x90

Parquet en producción: cómo leer, escribir y optimizar datos para análisis

Parquet es más que un simple formato de almacenamiento; es una herramienta de optimización arquitectónica. Su estructura columnar, el filtrado incorporado en tiempo de lectura, la codificación por diccionario y los metadatos de grupos de filas reducen la entrada/salida en órdenes de magnitud y disminuyen el tamaño de los datos hasta 140 veces en comparación con CSV. Para ingenieros de datos y analistas, esto significa no solo ahorrar espacio en disco, sino también reducir la latencia en consultas interactivas, disminuir la presión sobre la memoria y lograr un rendimiento predecible. Este artículo ofrece orientación práctica para trabajar con Parquet en Python a nivel intermedio o avanzado, desde la lectura básica mediante pandas hasta el control manual sobre el esquema, la partición y la compresión.

Por qué Parquet está reemplazando a CSV en las pipelines de datos modernas

CSV es un formato basado en texto sin semántica: cada fila se procesa como un bloque único, los delimitadores se analizan al vuelo y los tipos se infieren heurísticamente. Parquet, en cambio, es binario, estrictamente tipado y columnar, con metadatos integrados. Las principales diferencias incluyen:

  • Almacenamiento columnar: cada columna se almacena por separado, lo que permite leer solo los campos necesarios (por ejemplo, year y indicator_value) mientras se ignoran el resto, sin tener que analizar toda la fila.
  • Grupos de filas: los datos se dividen en bloques (por defecto ~128 MB), cada uno con estadísticas (min, max, null_count). Al filtrar year == 2023, el motor saltea grupos de filas enteros sin cargarlos en memoria.
  • Codificación por diccionario: para columnas de texto con alta repetición (regiones, categorías), Parquet almacena automáticamente o de forma explícita un diccionario de valores únicos y referencias a ellos; así, en lugar de “Región de Moscú” × 500.000 registros, utiliza el código 1 × 500.000.
  • Seguridad de tipos: en CSV, un NULL en un campo numérico convierte toda la columna en float64; en Parquet, los tipos se definen rigurosamente en el esquema y se validan al escribir.

La diferencia en la práctica es enorme: un conjunto de datos de incidencia de cáncer de 576 MB se reduce a 4 MB en Parquet (relación de compresión ×144), y el tiempo para leer un subconjunto filtrado pasa de 12–18 segundos a menos de 300 ms.

Google AdInline article slot

Lectura: de pandas al control fino con pyarrow

Lectura básica con pandas

Para empezar rápidamente, pd.read_parquet() es suficiente:

import pandas as pd

df = pd.read_parquet(
    "data_zis_109_v20260126.parquet",
    columns=["object_name", "year", "indicator_value"],
    filters=[("year", "=", 2023)]
)

Acceso directo con pyarrow: control sobre el esquema y los metadatos

Cuando se necesita un control preciso, se usa pyarrow.parquet directamente:

import pyarrow.parquet as pq

# Sin cargar los datos—solo el esquema y los metadatos
schema = pq.read_schema("data_zis_109_v20260126.parquet")
meta = pq.read_metadata("data_zis_109_v20260126.parquet")
print(f"Filas: {meta.num_rows:,}, Columnas: {meta.num_columns}")

# Lectura con filtrado y selección de columnas
table = pq.read_table(
    "data_zis_109_v20260126.parquet",
    columns=["object_name", "object_oktmo", "year", "indicator_value"],
    filters=[("year", "=", 2023)]
)

df = table.to_pandas()  # Convertir a pandas si es necesario
pf = pq.ParquetFile("data_zis_109_v20260126.parquet")
for batch in pf.iter_batches(batch_size=100_000):
    df_batch = batch.to_pandas()
    # Procesar el lote

Escritura: de lo sencillo a la serialización de grado productivo

Desafíos de la serialización automática

df.to_parquet() funciona, pero oculta riesgos:

Google AdInline article slot
  • Determinación implícita de tipos: int64 con NULL → float64, datetime64string.
  • Falta de codificación por diccionario para columnas de texto de alta cardinalidad.
  • Tamaño inapropiado de los grupos de filas (por defecto 64 MB), lo que reduce la eficiencia del filtrado.

Esquema explícito y codificación controlada

import pyarrow as pa
import pyarrow.parquet as pq

schema = pa.schema([
    pa.field("object_name", pa.string()),
    pa.field("object_oktmo", pa.dictionary(pa.int32(), pa.string())),
    pa.field("year", pa.int32()),
    pa.field("indicator_value", pa.float64())
])

table = pa.Table.from_pandas(df, schema=schema, preserve_index=False)

pq.write_table(
    table,
    "data_zis_new_version.parquet",
    compression="zstd",
    row_group_size=500_000,
    version="2.6",
    write_statistics=True
)

Particionamiento y trabajo con conjuntos de archivos

Si los datos están lógicamente divididos (por año, región), se usa write_to_dataset():

pq.write_to_dataset(
    table,
    root_path="data_zis_partitioned/",
    partition_cols=["year"]
)
import pyarrow.dataset as ds
dataset = ds.dataset("data_zis_partitioned/", format="parquet", partitioning="hive")

table = dataset.to_table(filter=ds.field("year") == 2023)

Qué importa

  • Parquet no sustituye a una base de datos: es ideal para escenarios de análisis de solo escritura y alta carga de lectura, pero no admite transacciones, UPDATE ni índices.
  • La codificación por diccionario ofrece máximos beneficios cuando la cardinalidad está por debajo de 10⁴ y la repetición supera el 30%. Para columnas con valores únicos (UUIDs, marcas temporales), es inútil.
  • El tamaño del grupo de filas afecta la precisión del filtrado: demasiado pequeño genera metadatos excesivos; demasiado grande hace que saltar datos innecesarios sea poco eficiente. El rango óptimo es de 100–500 mil filas.
  • Las estadísticas (write_statistics=True) son obligatorias para producción. Sin ellas, el filtrado actúa como un escaneo completo.
  • Siempre valide el esquema antes de escribir: pa.Table.from_pandas(df, schema=schema) lanzará un error si los tipos no coinciden—esto protege contra la corrupción silenciosa.

— Editorial Team

Advertisement 728x90

Leer después