J'ai affaire à un Pandas DataFrame assez volumineux - mon ensemble de données ressemble à une df
configuration suivante :
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
)
Remarque: Dans le but d'avoir un exemple minimal, il peut être facilement sous-réglé (par exemple avec df.loc[df['time'] <= 400, :]
), mais comme je simule les données de toute façon, je pensais que la taille d'origine donnerait une meilleure vue d'ensemble.
Pour chaque groupe défini par ['measurement_id', 'time', 'group']
je dois appeler la fonction suivante:
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 )
Pour améliorer les performances, j'ai essayé deux approches:
Forfait Pandarallel
La première approche a été de paralléliser les calculs à l'aide de pandarallel
package:
pandarallel.initialize( progress_bar = True )
df \
.groupby( ['measurement_id', 'time', 'group'] ) \
.parallel_apply( lambda x: cluster( x['var'], x['object'] ) )
Cependant, cela semble sous-optimal car il consomme beaucoup de RAM et tous les cœurs ne sont pas utilisés dans les calculs (même en spécifiant explicitement le nombre de cœurs dans la pandarallel.initialize()
méthode). De plus, parfois les calculs se terminent par diverses erreurs, même si je n'ai pas eu l'occasion de trouver une raison à cela (peut-être un manque de RAM?).
PySpark Pandas UDF
J'ai également essayé un UDF Spark Pandas, bien que je sois totalement nouveau pour Spark. Voici ma tentative:
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()
Malheureusement, les performances n'étaient pas satisfaisantes également, et d'après ce que j'ai lu sur le sujet, cela peut être juste le fardeau d'utiliser la fonction UDF, écrite en Python et le besoin associé de convertir tous les objets Python en objets Spark et inversement.
Donc, voici mes questions:
- L'une ou l'autre de mes approches pourrait-elle être ajustée pour éliminer les éventuels goulots d'étranglement et améliorer les performances? (par exemple, configuration de PySpark, ajustement des opérations sous-optimales, etc.)
- Existe-t-il de meilleures alternatives? Comment se comparent-ils aux solutions proposées en termes de performances?
dask
(((donc mon commentaire c'est juste un conseil pour la recherche.