tablas - pandas python tutorial español pdf
Lea un csv grande en un marco de datos pandas disperso de una manera eficiente en memoria (2)
La función pandas read_csv
no parece tener una opción dispersa. Tengo datos csv con una tonelada de ceros (se comprime muy bien y al eliminar cualquier valor 0
reduce a casi la mitad del tamaño original).
He intentado cargarlo en una matriz densa primero con read_csv
y luego llamar a to_sparse
, pero toma mucho tiempo y se ahoga en los campos de texto, aunque la mayoría de los datos están en coma flotante. Si llamo pandas.get_dummies(df)
a pandas.get_dummies(df)
para convertir las columnas categóricas a unas y ceros, luego llame a to_sparse(fill_value=0)
toma una cantidad de tiempo absurda, mucho más larga de lo que esperaría para una tabla mayormente numérica que tiene 12 Millones de entradas, en su mayoría cero. Esto sucede incluso si to_sparse()
los ceros del archivo original y llamo a to_sparse()
(para que el valor de relleno sea NaN). Esto también sucede independientemente de que pase kind=''block''
o kind=''integer''
.
Además de construir el marco de datos disperso a mano, ¿hay una manera buena y suave de cargar un csv escaso directamente sin consumir grandes cantidades de memoria innecesaria?
Aquí hay algo de código para crear un conjunto de datos de muestra que tiene 3 columnas de datos de punto flotante y una columna de datos de texto. Aproximadamente el 85% de los valores flotantes son cero y el tamaño total del CSV es de aproximadamente 300 MB, pero es probable que desee aumentar este tamaño para probar realmente las restricciones de memoria.
np.random.seed(123)
df=pd.DataFrame( np.random.randn(10000000,3) , columns=list(''xyz'') )
df[ df < 1.0 ] = 0.0
df[''txt''] = np.random.choice( list(''abcdefghij''), size=len(df) )
df.to_csv(''test.csv'',index=False)
Y aquí hay una forma sencilla de leerlo, pero con suerte hay una forma mejor y más eficiente:
sdf = pd.read_csv( ''test.csv'', dtype={''txt'':''category''} ).to_sparse(fill_value=0.0)
Editar para agregar (de JohnE): si es posible, proporcione estadísticas de rendimiento relativo al leer CSV grandes en su respuesta, incluida información sobre cómo midió la eficiencia de la memoria (especialmente porque la eficiencia de la memoria es más difícil de medir que la hora). En particular, tenga en cuenta que una respuesta más lenta (hora del reloj) podría ser la mejor respuesta aquí, si es más eficiente en memoria .
Aquí hay una respuesta ofrecida principalmente como un punto de referencia. Esperemos que haya mejores maneras que esta.
chunksize = 1000000 # perhaps try some different values here?
chunks = pd.read_csv( ''test.csv'', chunksize=chunksize, dtype={''txt'':''category''} )
sdf = pd.concat( [ chunk.to_sparse(fill_value=0.0) for chunk in chunks ] )
Como observa @acushner, en su lugar, podría hacer esto como una expresión generadora:
sdf = pd.concat( chunk.to_sparse(fill_value=0.0) for chunk in chunks )
Parece haber consenso en que esto es mejor que la forma compacta de la lista, aunque en mis pruebas no vi grandes diferencias, pero quizás pueda tener datos diferentes.
Esperaba informar algunos perfiles de memoria sobre los diversos métodos, pero luché para obtener resultados consistentes, sospecho que Python siempre está limpiando la memoria entre bambalinas, lo que genera un ruido aleatorio que se agrega a los resultados. (En un comentario a la respuesta de Jake, él sugiere reiniciar el kernel jupyter antes de cada %memit
para obtener resultados más consistentes, pero todavía no lo he intentado).
Pero encontré consistentemente (usando %%memit
) que los fragmentos leídos arriba y el método dask de @jakevdp usaron algo muy aproximado en la vecindad de la mitad de la memoria como el método ingenuo en el OP. Para obtener más información sobre la creación de perfiles, consulte el "Código de creación de perfiles y temporización" en el libro de Jake "Manual de ciencia de datos de Python".
Probablemente trataría esto utilizando dask para cargar sus datos en forma de transmisión. Por ejemplo, puede crear un marco de datos dask de la siguiente manera:
import dask.dataframe as ddf
data = ddf.read_csv(''test.csv'')
Este objeto de data
no ha hecho nada en este momento; solo contiene una "receta" de clases para leer el marco de datos del disco en partes manejables. Si desea materializar los datos, puede llamar a compute()
:
df = data.compute().reset_index(drop=True)
En este punto, tiene un marco de datos pandas estándar (llamamos reset_index
porque, de forma predeterminada, cada partición está indexada de forma independiente). El resultado es equivalente a lo que obtienes al llamar a pd.read_csv
directamente:
df.equals(pd.read_csv(''test.csv''))
# True
El beneficio de dask es que puede agregar instrucciones a esta "receta" para construir su marco de datos; por ejemplo, puede hacer que cada partición de los datos sea dispersa de la siguiente manera:
data = data.map_partitions(lambda part: part.to_sparse(fill_value=0))
En este punto, al llamar a compute()
se construirá una matriz dispersa:
df = data.compute().reset_index(drop=True)
type(df)
# pandas.core.sparse.frame.SparseDataFrame
Perfilado
Para comprobar cómo se compara el enfoque de dask con el enfoque de pandas en bruto, hagamos un poco de perfil de línea lprun
y mprun
, como se describe here (revelación completa: esa es una sección de mi propio libro).
Asumiendo que estás trabajando en el cuaderno Jupyter, puedes ejecutarlo de esta manera:
Primero, cree un archivo separado con las tareas básicas que queremos hacer:
%%file dask_load.py
import numpy as np
import pandas as pd
import dask.dataframe as ddf
def compare_loads():
df = pd.read_csv(''test.csv'')
df_sparse = df.to_sparse(fill_value=0)
df_dask = ddf.read_csv(''test.csv'', blocksize=10E6)
df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
df_dask = df_dask.compute().reset_index(drop=True)
A continuación, vamos a hacer un perfil línea por línea para el tiempo de cálculo:
%load_ext line_profiler
from dask_load import compare_loads
%lprun -f compare_loads compare_loads()
Obtengo el siguiente resultado:
Timer unit: 1e-06 s
Total time: 13.9061 s
File: /Users/jakevdp/dask_load.py
Function: compare_loads at line 6
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 def compare_loads():
7 1 4746788 4746788.0 34.1 df = pd.read_csv(''test.csv'')
8 1 769303 769303.0 5.5 df_sparse = df.to_sparse(fill_value=0)
9
10 1 33992 33992.0 0.2 df_dask = ddf.read_csv(''test.csv'', blocksize=10E6)
11 1 7848 7848.0 0.1 df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
12 1 8348217 8348217.0 60.0 df_dask = df_dask.compute().reset_index(drop=True)
Vemos que aproximadamente el 60% del tiempo se gasta en la llamada dask, mientras que el 40% del tiempo se gasta en la llamada de los pandas para el ejemplo anterior. Esto nos dice que dask es aproximadamente un 50% más lento que los pandas para esta tarea: esto es de esperar, porque la fragmentación y la recombinación de las particiones de datos conllevan una sobrecarga adicional.
Donde dask brilla es en el uso de la memoria: usemos mprun
para hacer un perfil de memoria línea por línea:
%load_ext memory_profiler
%mprun -f compare_loads compare_loads()
El resultado en mi máquina es este:
Filename: /Users/jakevdp/dask_load.py
Line # Mem usage Increment Line Contents
================================================
6 70.9 MiB 70.9 MiB def compare_loads():
7 691.5 MiB 620.6 MiB df = pd.read_csv(''test.csv'')
8 828.8 MiB 137.3 MiB df_sparse = df.to_sparse(fill_value=0)
9
10 806.3 MiB -22.5 MiB df_dask = ddf.read_csv(''test.csv'', blocksize=10E6)
11 806.4 MiB 0.1 MiB df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
12 947.9 MiB 141.5 MiB df_dask = df_dask.compute().reset_index(drop=True)
Vemos que el tamaño final del marco de datos de pandas es aproximadamente ~ 140MB, pero los pandas usan ~ 620MB en el camino mientras lee los datos en un objeto denso temporal.
Por otro lado, dask solo usa ~ 140MB en total para cargar la matriz y construir el resultado final disperso. En el caso de que esté leyendo datos cuyo tamaño denso sea comparable a la memoria disponible en su sistema, dask tiene una clara ventaja, a pesar del tiempo de cómputo más lento en ~ 50%.
Pero para trabajar con datos grandes, no debes parar aquí. Es de suponer que está realizando algunas operaciones con sus datos, y la abstracción del marco de datos dask le permite realizar esas operaciones (es decir, agregarlas a la "receta") antes de materializar los datos. Entonces, si lo que está haciendo con los datos involucra aritmética, agregaciones, agrupaciones, etc., no necesita preocuparse por el almacenamiento disperso: solo realice esas operaciones con el objeto dask, llame a compute()
al final, y dask se encargará de aplicarlos de una manera eficiente en memoria.
Entonces, por ejemplo, podría calcular el max()
de cada columna utilizando el marco de datos dask, sin tener que cargar todo el contenido en la memoria de una vez:
>>> data.max().compute()
x 5.38114
y 5.33796
z 5.25661
txt j
dtype: object
Trabajar directamente con los marcos de datos de dask le permitirá evitar las preocupaciones sobre la representación de los datos, ya que es probable que nunca tenga que cargar todos los datos en la memoria a la vez.
¡La mejor de las suertes!