He tratado de descifrar una respuesta a esta pregunta durante muchos meses mientras aprendía pandas. Utilizo SAS para mi trabajo diario y es excelente por su soporte fuera del núcleo. Sin embargo, SAS es horrible como una pieza de software por muchas otras razones.

Un día espero reemplazar mi uso de SAS con python y pandas, pero actualmente no tengo un flujo de trabajo fuera de núcleo para grandes conjuntos de datos. No estoy hablando de "big data" que requiere una red distribuida, sino archivos demasiado grandes para caber en la memoria pero lo suficientemente pequeños como para caber en un disco duro.

Mi primer pensamiento es usar HDFStore para mantener grandes conjuntos de datos en el disco y extraer solo las piezas que necesito en los marcos de datos para el análisis. Otros han mencionado a MongoDB como una alternativa más fácil de usar. Mi pregunta es esta:

¿Cuáles son algunos flujos de trabajo de mejores prácticas para lograr lo siguiente?

  1. Carga de archivos planos en una estructura de base de datos permanente en disco
  2. Consultar esa base de datos para recuperar datos para alimentar una estructura de datos de pandas
  3. Actualización de la base de datos después de manipular piezas en pandas

Los ejemplos del mundo real serían muy apreciados, especialmente de cualquiera que use pandas en "datos grandes".

Editar: un ejemplo de cómo me gustaría que esto funcione:

  1. Importe iterativamente un archivo plano grande y almacénelo en una estructura de base de datos permanente en el disco. Estos archivos suelen ser demasiado grandes para caber en la memoria.
  2. Para usar Pandas, me gustaría leer subconjuntos de estos datos (generalmente solo unas pocas columnas a la vez) que pueden caber en la memoria.
  3. Crearía nuevas columnas realizando varias operaciones en las columnas seleccionadas.
  4. Entonces tendría que agregar estas nuevas columnas a la estructura de la base de datos.

Estoy tratando de encontrar una forma de práctica recomendada para realizar estos pasos. Al leer enlaces sobre pandas y pytables parece que agregar una nueva columna podría ser un problema.

Editar: responder específicamente a las preguntas de Jeff:

  1. Estoy construyendo modelos de riesgo de crédito al consumo. Los tipos de datos incluyen teléfono, número de seguro social y características de la dirección; valores de propiedad; información despectiva como antecedentes penales, quiebras, etc. Los conjuntos de datos que uso todos los días tienen cerca de 1,000 a 2,000 campos en promedio de tipos de datos mixtos: variables continuas, nominales y ordinales de datos numéricos y de caracteres. Raramente agrego filas, pero realizo muchas operaciones que crean nuevas columnas.
  2. Las operaciones típicas implican combinar varias columnas usando lógica condicional en una nueva columna compuesta. Por ejemplo, if var1 > 2 then newvar = 'A' elif var2 = 4 then newvar = 'B'. El resultado de estas operaciones es una nueva columna para cada registro en mi conjunto de datos.
  3. Finalmente, me gustaría agregar estas nuevas columnas a la estructura de datos en disco. Repetiría el paso 2, explorando los datos con tablas de referencias cruzadas y estadísticas descriptivas tratando de encontrar relaciones interesantes e intuitivas para modelar.
  4. Un archivo de proyecto típico suele ser de aproximadamente 1 GB. Los archivos están organizados de tal manera que una fila consiste en un registro de datos del consumidor. Cada fila tiene el mismo número de columnas para cada registro. Este siempre será el caso.
  5. Es bastante raro que yo subconjunto por filas al crear una nueva columna. Sin embargo, es bastante común para mí subconjuntar en filas al crear informes o generar estadísticas descriptivas. Por ejemplo, podría querer crear una frecuencia simple para una línea de negocio específica, por ejemplo, tarjetas de crédito minoristas. Para hacer esto, seleccionaría solo aquellos registros donde la línea de negocio = minorista, además de las columnas sobre las que deseo informar. Sin embargo, al crear nuevas columnas, extraería todas las filas de datos y solo las columnas que necesito para las operaciones.
  6. El proceso de modelado requiere que analice cada columna, busque relaciones interesantes con alguna variable de resultado y cree nuevas columnas compuestas que describan esas relaciones. Las columnas que exploro generalmente se hacen en pequeños conjuntos. Por ejemplo, me enfocaré en un conjunto de, digamos, 20 columnas que solo tratan los valores de las propiedades y observaré cómo se relacionan con el incumplimiento de un préstamo. Una vez que se exploran y se crean nuevas columnas, luego paso a otro grupo de columnas, digo educación universitaria y repito el proceso. Lo que estoy haciendo es crear variables candidatas que expliquen la relación entre mis datos y algunos resultados. Al final de este proceso, aplico algunas técnicas de aprendizaje que crean una ecuación a partir de esas columnas compuestas.

Es raro que alguna vez agregue filas al conjunto de datos. Casi siempre crearé nuevas columnas (variables o características en estadísticas / lenguaje de aprendizaje automático).

948
Zelazny7 10 ene. 2013 a las 20:20

14 respuestas

Considere Ruffus si sigue el camino simple de crear una tubería de datos que se divide en varios archivos más pequeños.

10
Golf Monkey 9 oct. 2014 a las 19:07

Me gustaría señalar el paquete Vaex.

Vaex es una biblioteca de Python para marcos de datos flojos fuera del núcleo (similar a Pandas), para visualizar y explorar grandes conjuntos de datos tabulares. Puede calcular estadísticas como media, suma, conteo, desviación estándar, etc., en una cuadrícula N-dimensional de hasta mil millones (10 9 ) objetos / filas por segundo. La visualización se realiza mediante histogramas, gráficos de densidad y representación de volumen en 3D, lo que permite la exploración interactiva de grandes datos. Vaex utiliza mapeo de memoria, política de copia de memoria cero y cálculos lentos para un mejor rendimiento (sin pérdida de memoria).

Eche un vistazo a la documentación: https://vaex.readthedocs.io/en/latest/ La API está muy cerca de la API de los pandas.

6
Rob 3 jun. 2019 a las 09:40

Una variante más

Muchas de las operaciones realizadas en pandas también se pueden realizar como una consulta db (sql, mongo)

El uso de un RDBMS o mongodb le permite realizar algunas de las agregaciones en la consulta de la base de datos (que está optimizada para datos grandes y utiliza la caché y los índices de manera eficiente)

Más tarde, puede realizar el procesamiento posterior utilizando pandas.

La ventaja de este método es que obtiene las optimizaciones de la base de datos para trabajar con datos de gran tamaño, sin dejar de definir la lógica en una sintaxis declarativa de alto nivel, y no tener que lidiar con los detalles de decidir qué hacer en la memoria y qué hacer. de núcleo.

Y aunque el lenguaje de consulta y los pandas son diferentes, generalmente no es complicado traducir parte de la lógica de uno a otro.

15
Ophir Yoktan 28 abr. 2015 a las 05:22

Como han señalado otros, después de algunos años ha surgido un equivalente de pandas 'fuera del núcleo': dask. Aunque dask no es un reemplazo directo de los pandas y toda su funcionalidad, se destaca por varias razones:

Dask es una biblioteca de computación paralela flexible para computación analítica que está optimizada para la programación dinámica de tareas para cargas de trabajo computacionales interactivas de colecciones de "Big Data", como matrices paralelas, marcos de datos y listas que extienden interfaces comunes como iteradores NumPy, Pandas o Python a grandes. que la memoria o entornos distribuidos y escalas de computadoras portátiles a clústeres.

Dask enfatiza las siguientes virtudes:

  • Familiar: proporciona una matriz paralela NumPy y objetos Pandas DataFrame
  • Flexible: proporciona una interfaz de programación de tareas para cargas de trabajo más personalizadas e integración con otros proyectos.
  • Nativo: habilita la informática distribuida en Pure Python con acceso a la pila PyData.
  • Rápido: funciona con baja sobrecarga, baja latencia y una serialización mínima necesaria para algoritmos numéricos rápidos
  • Se amplía: se ejecuta de manera resistente en clústeres con miles de núcleos Se reduce: trivial para configurar y ejecutar en una computadora portátil en un solo proceso
  • Sensible: diseñado teniendo en cuenta la informática interactiva, proporciona retroalimentación y diagnóstico rápidos para ayudar a los humanos

Y para agregar una muestra de código simple:

import dask.dataframe as dd
df = dd.read_csv('2015-*-*.csv')
df.groupby(df.user_id).value.mean().compute()

Reemplaza algunos códigos de pandas como este:

import pandas as pd
df = pd.read_csv('2015-01-01.csv')
df.groupby(df.user_id).value.mean()

Y, especialmente notable, proporciona a través de la interfaz concurrent.futures una infraestructura general para el envío de tareas personalizadas:

from dask.distributed import Client
client = Client('scheduler:port')

futures = []
for fn in filenames:
    future = client.submit(load, fn)
    futures.append(future)

summary = client.submit(summarize, futures)
summary.result()
24
wp78de 8 ene. 2020 a las 17:07

Este es el caso de pymongo. También he realizado un prototipo utilizando sql server, sqlite, HDF, ORM (SQLAlchemy) en python. En primer lugar, pymongo es una base de datos basada en documentos, por lo que cada persona sería un documento (dict de atributos). Muchas personas forman una colección y puede tener muchas colecciones (personas, bolsa, ingresos).

Pd.dataframe -> pymongo Nota: uso chunksize y read_csv para mantenerlo en 5 a 10k registros (pymongo elimina el socket si es más grande)

aCollection.insert((a[1].to_dict() for a in df.iterrows()))

Consultas: gt = mayor que ...

pd.DataFrame(list(mongoCollection.find({'anAttribute':{'$gt':2887000, '$lt':2889000}})))

.find() devuelve un iterador, por lo que comúnmente uso ichunked para cortar en iteradores más pequeños.

¿Qué tal una unión ya que normalmente obtengo 10 fuentes de datos para pegar:

aJoinDF = pandas.DataFrame(list(mongoCollection.find({'anAttribute':{'$in':Att_Keys}})))

Luego (en mi caso, a veces tengo que agregar aJoinDF primero antes de que sea "fusionable").

df = pandas.merge(df, aJoinDF, on=aKey, how='left')

Y luego puede escribir la nueva información en su colección principal a través del método de actualización a continuación. (colección lógica vs fuentes de datos físicas).

collection.update({primarykey:foo},{key:change})

En búsquedas más pequeñas, solo desnormalizar. Por ejemplo, tiene código en el documento y simplemente agrega el texto del código de campo y realiza una búsqueda dict a medida que crea documentos.

Ahora que tiene un buen conjunto de datos basado en una persona, puede liberar su lógica en cada caso y hacer más atributos. Finalmente, puede leer en pandas sus 3 indicadores clave de memoria máxima y hacer pivotes / agg / exploración de datos. Esto me funciona para 3 millones de registros con números / texto grande / categorías / códigos / flotantes / ...

También puede usar los dos métodos integrados en MongoDB (MapReduce y el marco agregado). Vea aquí para obtener más información sobre el marco agregado, ya que parece ser más fácil que MapReduce y parece práctico para trabajo agregado rápido. Observe que no necesitaba definir mis campos o relaciones, y puedo agregar elementos a un documento. En el estado actual del conjunto de herramientas de numpy, pandas y python que cambia rápidamente, MongoDB me ayuda a ponerme a trabajar :)

48
ali_m 14 ago. 2014 a las 13:50

Vale la pena mencionar aquí Ray también,
Es un marco de cálculo distribuido, que tiene su propia implementación para pandas de forma distribuida.

Simplemente reemplace la importación de pandas, y el código debería funcionar como está:

# import pandas as pd
import ray.dataframe as pd

#use pd as usual

Puede leer más detalles aquí:

https://rise.cs.berkeley.edu/blog/pandas-on-ray/

14
lev 18 mar. 2018 a las 09:30

Sé que este es un hilo antiguo, pero creo que vale la pena echarle un vistazo a la Blaze. Está construido para este tipo de situaciones.

De los documentos:

Blaze extiende la usabilidad de NumPy y Pandas a la computación distribuida y fuera del núcleo. Blaze proporciona una interfaz similar a la de NumPy ND-Array o Pandas DataFrame, pero asigna estas interfaces familiares en una variedad de otros motores computacionales como Postgres o Spark.

Editar: Por cierto, es compatible con ContinuumIO y Travis Oliphant, autor de NumPy.

54
chishaku 3 dic. 2014 a las 22:09

Recientemente me encontré con un problema similar. Descubrí que simplemente leer los datos en fragmentos y agregarlos a medida que los escribo en el mismo csv funciona bien. Mi problema fue agregar una columna de fecha basada en la información en otra tabla, usando el valor de ciertas columnas de la siguiente manera. Esto puede ayudar a aquellos confundidos por dask y hdf5 pero más familiarizados con los pandas como yo.

def addDateColumn():
"""Adds time to the daily rainfall data. Reads the csv as chunks of 100k 
   rows at a time and outputs them, appending as needed, to a single csv. 
   Uses the column of the raster names to get the date.
"""
    df = pd.read_csv(pathlist[1]+"CHIRPS_tanz.csv", iterator=True, 
                     chunksize=100000) #read csv file as 100k chunks

    '''Do some stuff'''

    count = 1 #for indexing item in time list 
    for chunk in df: #for each 100k rows
        newtime = [] #empty list to append repeating times for different rows
        toiterate = chunk[chunk.columns[2]] #ID of raster nums to base time
        while count <= toiterate.max():
            for i in toiterate: 
                if i ==count:
                    newtime.append(newyears[count])
            count+=1
        print "Finished", str(chunknum), "chunks"
        chunk["time"] = newtime #create new column in dataframe based on time
        outname = "CHIRPS_tanz_time2.csv"
        #append each output to same csv, using no header
        chunk.to_csv(pathlist[2]+outname, mode='a', header=None, index=None)
8
timpjohns 4 oct. 2016 a las 15:32

Un truco que encontré útil para los casos de uso de datos grandes es reducir el volumen de los datos reduciendo la precisión flotante a 32 bits. No es aplicable en todos los casos, pero en muchas aplicaciones la precisión de 64 bits es exagerada y los ahorros de memoria 2x valen la pena. Para hacer un punto obvio aún más obvio:

>>> df = pd.DataFrame(np.random.randn(int(1e8), 5))
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float64(5)
memory usage: 3.7 GB

>>> df.astype(np.float32).info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float32(5)
memory usage: 1.9 GB
33
AkshayNevrekar 3 nov. 2018 a las 06:51

Ahora hay, dos años después de la pregunta, un equivalente de pandas 'fuera de núcleo': dask. ¡Es excelente! Aunque no admite todas las funciones de pandas, puedes llegar muy lejos con él.

76
Private 23 mar. 2016 a las 20:30

Si sus conjuntos de datos tienen entre 1 y 20 GB, debería obtener una estación de trabajo con 48 GB de RAM. Entonces Pandas puede contener todo el conjunto de datos en la RAM. Sé que no es la respuesta que está buscando aquí, pero hacer computación científica en una computadora portátil con 4 GB de RAM no es razonable.

61
rjurney 2 nov. 2013 a las 07:14

Creo que a las respuestas anteriores les falta un enfoque simple que he encontrado muy útil.

Cuando tengo un archivo que es demasiado grande para cargarlo en la memoria, lo divido en varios archivos más pequeños (ya sea por filas o por columnas)

Ejemplo: en el caso de 30 días de datos comerciales de ~ 30 GB de tamaño, lo divido en un archivo por día de ~ 1 GB de tamaño. Posteriormente, proceso cada archivo por separado y agrego los resultados al final

Una de las mayores ventajas es que permite el procesamiento paralelo de los archivos (ya sea múltiples hilos o procesos)

La otra ventaja es que la manipulación de archivos (como agregar / eliminar fechas en el ejemplo) se puede lograr mediante comandos de shell regulares, lo que no es posible en formatos de archivo más avanzados / complicados

Este enfoque no cubre todos los escenarios, pero es muy útil en muchos de ellos.

132
user1827356 23 dic. 2013 a las 15:21

En este momento estoy trabajando "como" usted, solo en una escala más baja, por lo que no tengo un PoC para mi sugerencia.

Sin embargo, parece que tengo éxito en el uso de pickle como sistema de almacenamiento en caché y la subcontratación de la ejecución de varias funciones en archivos, ejecutando estos archivos desde mi comando / archivo principal; Por ejemplo, uso prepare_use.py para convertir tipos de objetos, dividir un conjunto de datos en un conjunto de datos de prueba, validación y predicción.

¿Cómo funciona el almacenamiento en caché con pepinillos? Utilizo cadenas para acceder a los archivos pickle que se crean dinámicamente, dependiendo de qué parámetros y conjuntos de datos se pasaron (con eso trato de capturar y determinar si el programa ya se ejecutó, usando .shape para el conjunto de datos, dict para pasado parámetros). Respetando estas medidas, obtengo una Cadena para tratar de encontrar y leer un archivo .pickle y puedo, si se encuentra, omitir el tiempo de procesamiento para saltar a la ejecución en la que estoy trabajando en este momento.

Al usar bases de datos me encontré con problemas similares, por lo que me gustó usar esta solución, sin embargo, hay muchas restricciones, por ejemplo, almacenar grandes conjuntos de encurtidos debido a la redundancia. La actualización de una tabla desde antes hasta después de una transformación se puede realizar con una indexación adecuada: la validación de la información abre un libro completamente diferente (intenté consolidar los datos de alquiler rastreados y básicamente dejé de usar una base de datos después de 2 horas, ya que me hubiera gustado retroceder después cada proceso de transformación)

Espero que mis 2 centavos te ayuden de alguna manera.

Saludos.

-1
TiRoX 8 ene. 2020 a las 14:05

Lo vi un poco tarde, pero trabajo con un problema similar (modelos de prepago de hipoteca). Mi solución ha sido omitir la capa de pandas HDFStore y usar pytables rectas. Guardo cada columna como una matriz HDF5 individual en mi archivo final.

Mi flujo de trabajo básico es obtener primero un archivo CSV de la base de datos. Lo comprimo, así que no es tan grande. Luego lo convierto en un archivo HDF5 orientado a filas, repitiéndolo en python, convirtiendo cada fila a un tipo de datos real y escribiéndolo en un archivo HDF5. Eso lleva algunas decenas de minutos, pero no usa memoria, ya que solo funciona fila por fila. Luego "transpongo" el archivo HDF5 orientado a filas en un archivo HDF5 orientado a columnas.

La transposición de la tabla se ve así:

def transpose_table(h_in, table_path, h_out, group_name="data", group_path="/"):
    # Get a reference to the input data.
    tb = h_in.getNode(table_path)
    # Create the output group to hold the columns.
    grp = h_out.createGroup(group_path, group_name, filters=tables.Filters(complevel=1))
    for col_name in tb.colnames:
        logger.debug("Processing %s", col_name)
        # Get the data.
        col_data = tb.col(col_name)
        # Create the output array.
        arr = h_out.createCArray(grp,
                                 col_name,
                                 tables.Atom.from_dtype(col_data.dtype),
                                 col_data.shape)
        # Store the data.
        arr[:] = col_data
    h_out.flush()

Leerlo de nuevo y luego se ve así:

def read_hdf5(hdf5_path, group_path="/data", columns=None):
    """Read a transposed data set from a HDF5 file."""
    if isinstance(hdf5_path, tables.file.File):
        hf = hdf5_path
    else:
        hf = tables.openFile(hdf5_path)

    grp = hf.getNode(group_path)
    if columns is None:
        data = [(child.name, child[:]) for child in grp]
    else:
        data = [(child.name, child[:]) for child in grp if child.name in columns]

    # Convert any float32 columns to float64 for processing.
    for i in range(len(data)):
        name, vec = data[i]
        if vec.dtype == np.float32:
            data[i] = (name, vec.astype(np.float64))

    if not isinstance(hdf5_path, tables.file.File):
        hf.close()
    return pd.DataFrame.from_items(data)

Ahora, generalmente ejecuto esto en una máquina con una tonelada de memoria, por lo que es posible que no sea lo suficientemente cuidadoso con mi uso de memoria. Por ejemplo, de manera predeterminada, la operación de carga lee todo el conjunto de datos.

Esto generalmente funciona para mí, pero es un poco torpe y no puedo usar la elegante magia de las tablas.

Editar: La ventaja real de este enfoque, sobre el valor predeterminado de pytables de la matriz de registros, es que luego puedo cargar los datos en R usando h5r, que no puede manejar tablas. O, al menos, no he podido hacer que cargue tablas heterogéneas.

40
Johann Hibschman 22 mar. 2013 a las 15:38
14262433