Estoy tratando con un marco de datos Pandas bastante grande: mi conjunto de datos se asemeja a una df
configuración siguiente :
import pandas as pd
import numpy as np
#--------------------------------------------- SIZING PARAMETERS :
R1 = 20 # .repeat( repeats = R1 )
R2 = 10 # .repeat( repeats = R2 )
R3 = 541680 # .repeat( repeats = [ R3, R4 ] )
R4 = 576720 # .repeat( repeats = [ R3, R4 ] )
T = 55920 # .tile( , T)
A1 = np.arange( 0, 2708400, 100 ) # ~ 20x re-used
A2 = np.arange( 0, 2883600, 100 ) # ~ 20x re-used
#--------------------------------------------- DataFrame GENERATION :
df = pd.DataFrame.from_dict(
{ 'measurement_id': np.repeat( [0, 1], repeats = [ R3, R4 ] ),
'time':np.concatenate( [ np.repeat( A1, repeats = R1 ),
np.repeat( A2, repeats = R1 ) ] ),
'group': np.tile( np.repeat( [0, 1], repeats = R2 ), T ),
'object': np.tile( np.arange( 0, R1 ), T )
}
)
#--------------------------------------------- DataFrame RE-PROCESSING :
df = pd.concat( [ df,
df \
.groupby( ['measurement_id', 'time', 'group'] ) \
.apply( lambda x: np.random.uniform( 0, 100, 10 ) ) \
.explode() \
.astype( 'float' ) \
.to_frame( 'var' ) \
.reset_index( drop = True )
], axis = 1
)
Nota: con el fin de tener un ejemplo mínimo, puede subconjustarse fácilmente (por ejemplo, con df.loc[df['time'] <= 400, :]
), pero dado que simulo los datos de todos modos, pensé que el tamaño original daría una mejor visión general.
Para cada grupo definido por ['measurement_id', 'time', 'group']
necesito llamar a la siguiente función:
from sklearn.cluster import SpectralClustering
from pandarallel import pandarallel
def cluster( x, index ):
if len( x ) >= 2:
data = np.asarray( x )[:, np.newaxis]
clustering = SpectralClustering( n_clusters = 5,
random_state = 42
).fit( data )
return pd.Series( clustering.labels_ + 1, index = index )
else:
return pd.Series( np.nan, index = index )
Para mejorar el rendimiento probé dos enfoques:
Paquete Pandarallel
El primer enfoque fue paralelizar los cálculos usando el pandarallel
paquete:
pandarallel.initialize( progress_bar = True )
df \
.groupby( ['measurement_id', 'time', 'group'] ) \
.parallel_apply( lambda x: cluster( x['var'], x['object'] ) )
Sin embargo, esto parece ser subóptimo ya que consume mucha RAM y no todos los núcleos se usan en los cálculos (incluso a pesar de especificar explícitamente el número de núcleos en el pandarallel.initialize()
método). Además, a veces los cálculos se terminan con varios errores, aunque no he tenido la oportunidad de encontrar una razón para eso (¿posiblemente falta de RAM?).
PySpark Pandas UDF
También probé un Spark Pandas UDF, aunque soy totalmente nuevo en Spark. Aquí está mi intento:
import findspark; findspark.init()
from pyspark.sql import SparkSession
from pyspark.conf import SparkConf
from pyspark.sql.functions import pandas_udf, PandasUDFType
from pyspark.sql.types import *
spark = SparkSession.builder.master( "local" ).appName( "test" ).config( conf = SparkConf() ).getOrCreate()
df = spark.createDataFrame( df )
@pandas_udf( StructType( [StructField( 'id', IntegerType(), True )] ), functionType = PandasUDFType.GROUPED_MAP )
def cluster( df ):
if len( df['var'] ) >= 2:
data = np.asarray( df['var'] )[:, np.newaxis]
clustering = SpectralClustering( n_clusters = 5,
random_state = 42
).fit( data )
return pd.DataFrame( clustering.labels_ + 1,
index = df['object']
)
else:
return pd.DataFrame( np.nan,
index = df['object']
)
res = df \
.groupBy( ['id_half', 'frame', 'team_id'] ) \
.apply( cluster ) \
.toPandas()
Desafortunadamente, el rendimiento también fue insatisfactorio, y por lo que leí sobre el tema, esto puede ser solo la carga del uso de la función UDF, escrita en Python y la necesidad asociada de convertir todos los objetos de Python en objetos Spark y viceversa.
Asi que aqui están mis preguntas:
- ¿Podría cualquiera de mis enfoques ajustarse para eliminar posibles cuellos de botella y mejorar el rendimiento? (p. ej., configuración de PySpark, ajuste de operaciones subóptimas, etc.)
- ¿Son mejores alternativas? ¿Cómo se comparan con las soluciones proporcionadas en términos de rendimiento?
dask