Manejo de datos eficiente en Python con flecha

1. Introducción

Todos estamos acostumbrados a trabajar con CSVS, archivos JSON … con las bibliotecas tradicionales y para conjuntos de datos grandes, estos pueden ser extremadamente lentos de leer, escribir y operar, lo que lleva a cuellos de botella de rendimiento (ha estado allí). Es precisamente con grandes cantidades de datos que ser eficiente manejar los datos es crucial para nuestro flujo de trabajo de ciencia de datos/análisis, y aquí es exactamente donde Flecha de apache entra en juego.

¿Por qué? La razón principal reside en cómo se almacenan los datos en la memoria. Mientras que JSON y CSVS, por ejemplo, son formatos basados ​​en texto, Arrow es un formato de datos columnar en memoria (y eso permite un intercambio rápido de datos entre diferentes herramientas de procesamiento de datos). Por lo tanto, Arrow está diseñada para optimizar el rendimiento al permitir lecturas de copia cero, reducir el uso de la memoria y admitir una compresión eficiente.

Además, Apache Arrow es de código abierto y optimizado para análisis. Está diseñado para acelerar el procesamiento de big data mientras mantiene la interoperabilidad con varias herramientas de datos, como pandas, chispa y dask. Al almacenar datos en un formato columnar, Arrow permite operaciones de lectura/escritura más rápidas y un uso eficiente de la memoria, lo que lo hace ideal para cargas de trabajo analíticas.

Suena genial, ¿verdad? Lo mejor es que esta es toda la introducción a la flecha que proporcionaré. Suficiente teoría, queremos verlo en acción. Entonces, en esta publicación, exploraremos cómo usar Arrow en Python y cómo aprovecharlo al máximo.

2. Arrow in Python

Para comenzar, debe instalar las bibliotecas necesarias: pandas y pyarrow.

pip install pyarrow pandas

Entonces, como siempre, importarlos en su guión de Python:

import pyarrow as pa
import pandas as pd

Aún no hay nada nuevo, solo pasos necesarios para hacer lo que sigue. Comencemos por realizar algunas operaciones simples.

2.1. Crear y almacenar una mesa

Lo más simple que podemos hacer es Hardcodiced los datos de nuestra tabla. Creemos una tabla de dos columnas con datos de fútbol:

teams = pa.array(['Barcelona', 'Real Madrid', 'Rayo Vallecano', 'Athletic Club', 'Real Betis'], type=pa.string())
goals = pa.array([30, 23, 9, 24, 12], type=pa.int8())

team_goals_table = pa.table([teams, goals], names=['Team', 'Goals'])

El formato es pyarrow.tablepero podemos convertirlo fácilmente a pandas si queremos:

df = team_goals_table.to_pandas()

Y restaurarlo a Arrow usando:

team_goals_table = pa.Table.from_pandas(df)

Y finalmente almacenaremos la tabla en un archivo. Podríamos usar diferentes formatos, como Feather, Parquet … usaré este último porque es rápido y optimizado para la memoria:

import pyarrow.parquet as pq
pq.write_table(team_goals_table, 'data.parquet')

Leer un archivo de parquet solo consistiría en usar pq.read_table('data.parquet').

2.2. Funciones de calcular

Arrow tiene su propio módulo de cómputo para las operaciones habituales. Comencemos por comparar dos matrices en cuanto al elemento:

import pyarrow.compute as pc
>>> a = pa.array([1, 2, 3, 4, 5, 6])
>>> b = pa.array([2, 2, 4, 4, 6, 6])
>>> pc.equal(a,b)
[
  false,
  true,
  false,
  true,
  false,
  true
]

Eso fue fácil, podríamos sumar todos los elementos en una matriz con:

>>> pc.sum(a)
<pyarrow.Int64Scalar: 21>

Y a partir de esto podríamos adivinar fácilmente cómo podemos calcular un recuento, un piso, una exp, un medio, un máximo, una multiplicación … no es necesario pasar sobre ellos. Así que pasemos a operaciones tabulares.

Comenzaremos mostrando cómo ordenarlo:

>>> table = pa.table({'i': ['a','b','a'], 'x': [1,2,3], 'y': [4,5,6]})
>>> pc.sort_indices(table, sort_keys=[('y', descending)])
<pyarrow.lib.UInt64Array object at 0x1291643a0>
[
  2,
  1,
  0
]

Al igual que en los pandas, podemos agrupar valores y agregar los datos. Veamos, por ejemplo, agrupar por “i” y calcular la suma en “x” y la media en “y”:

>>> table.group_by('i').aggregate([('x', 'sum'), ('y', 'mean')])
pyarrow.Table
i: string
x_sum: int64
y_mean: double
----
i: [["a","b"]]
x_sum: [[4,2]]
y_mean: [[5,5]]

O podemos unirnos a dos tablas:

>>> t1 = pa.table({'i': ['a','b','c'], 'x': [1,2,3]})
>>> t2 = pa.table({'i': ['a','b','c'], 'y': [4,5,6]})
>>> t1.join(t2, keys="i")
pyarrow.Table
i: string
x: int64
y: int64
----
i: [["a","b","c"]]
x: [[1,2,3]]
y: [[4,5,6]]

Por defecto, es una unión externa izquierda, pero podríamos torcerla usando el Join_type parámetro.

Hay muchas más operaciones útiles, pero veamos solo una más para evitar hacer esto demasiado tiempo: agregar una nueva columna a una tabla.

>>> t1.append_column("z", pa.array([22, 44, 99]))
pyarrow.Table
i: string
x: int64
z: int64
----
i: [["a","b","c"]]
x: [[1,2,3]]
z: [[22,44,99]]

Antes de finalizar esta sección, debemos ver cómo filtrar una tabla o matriz:

>>> t1.filter((pc.field('x') > 0) & (pc.field('x') < 3))
pyarrow.Table
i: string
x: int64
----
i: [["a","b"]]
x: [[1,2]]

Fácil, ¿verdad? ¡Especialmente si has estado usando pandas y numpy durante años!

3. Trabajando con archivos

Ya hemos visto cómo podemos leer y escribir archivos parquet. Pero revisemos otros tipos de archivos populares para que tengamos varias opciones disponibles.

3.1. Apache Orc

Al ser muy informal, Apache Orc puede entenderse como el equivalente de la flecha en el ámbito de los tipos de archivos (aunque sus orígenes no tienen nada que ver con la flecha). Al ser más correcto, es un formato de código abierto y de almacenamiento columnar.

Leer y escribirlo es el siguiente:

from pyarrow import orc
# Write table
orc.write_table(t1, 't1.orc')
# Read table
t1 = orc.read_table('t1.orc')

Como nota al margen, podríamos decidir comprimir el archivo mientras escribimos utilizando el parámetro “Compresión”.

3.2. CSV

No hay secreto aquí, Pyarrow tiene el módulo CSV:

from pyarrow import csv
# Write CSV
csv.write_csv(t1, "t1.csv")
# Read CSV
t1 = csv.read_csv("t1.csv")

# Write CSV compressed and without header
options = csv.WriteOptions(include_header=False)
with pa.CompressedOutputStream("t1.csv.gz", "gzip") as out:
    csv.write_csv(t1, out, options)

# Read compressed CSV and add custom header
t1 = csv.read_csv("t1.csv.gz", read_options=csv.ReadOptions(
    column_names=["i", "x"], skip_rows=1
)]

3.2. Json

Pyarrow permite la lectura JSON pero no la escritura. Es bastante sencillo, veamos un ejemplo suponiendo que tengamos nuestros datos JSON en “data.json”:

from pyarrow import json
# Read json
fn = "data.json"
table = json.read_json(fn)

# We can now convert it to pandas if we want to
df = table.to_pandas()

Feather es un formato de archivo portátil para almacenar tablas de flecha o marcos de datos (desde idiomas como Python o R) que utiliza el Formato de flecha IPC internamente. Entonces, al contrario de Apache Orc, este fue creado al principio del proyecto Arrow.

from pyarrow import feather
# Write feather from pandas DF
feather.write_feather(df, "t1.feather")
# Write feather from table, and compressed
feather.write_feather(t1, "t1.feather.lz4", compression="lz4")

# Read feather into table
t1 = feather.read_table("t1.feather")
# Read feather into df
df = feather.read_feather("t1.feather")

4. Características avanzadas

Acabamos de tocar las características más básicas y lo que la mayoría necesitaría mientras trabajaba con Arrow. Sin embargo, su sorprendente no termina aquí, es justo donde comienza.

Como esto será bastante específico del dominio y no es útil para nadie (ni considerado introductorio) solo mencionaré algunas de estas características sin usar ningún código:

  • Podemos manejar la gestión de la memoria a través del Buffer Tipo (construido en la parte superior del objeto de búfer C ++). Crear un búfer con nuestros datos no asigna ningún recuerdo; Es una vista de copia cero sobre la memoria exportada desde el objeto Bytes de datos. Mantenerse al día con esta gestión de memoria, una instancia de Memorypool rastrea todas las asignaciones y traficaciones (como malloc y gratis Cª). Esto nos permite rastrear la cantidad de memoria que se está asignando.
  • Del mismo modo, hay diferentes formas de trabajar con transmisiones de entrada/salida en lotes.
  • Pyarrow viene con una interfaz de sistema de archivos abstracto, así como implementaciones concretas para varios tipos de almacenamiento. Entonces, por ejemplo, podemos escribir y leer archivos de Parquet desde un cubo S3 utilizando el S3FileSystem. El sistema de archivos distribuidos (HDFS) de Google Cloud y Hadoop también se aceptan.

5. Conclusión y comida llave

Apache Arrow es una herramienta poderosa para eficiente Manejo de datos en Python. Su formato de almacenamiento columnar, lecturas de copia cero y su interoperabilidad con bibliotecas populares de procesamiento de datos lo hacen ideal para flujos de trabajo de ciencia de datos. Al integrar Arrow en su tubería, puede aumentar significativamente el rendimiento y optimizar el uso de la memoria.

6. Recursos