web-dev-qa-db-ja.com

Python関数をPandas grouped DataFrameに適用する-計算を高速化するための最も効率的なアプローチは何ですか?

私は非常に大きなPandas DataFrameを処理しています-私のデータセットは次のdf設定に似ています:

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
                )

注:最小限の例を示すために、(たとえばdf.loc[df['time'] <= 400, :]を使用して)簡単にサブセット化できますが、とにかくデータをシミュレートするので、元のサイズの方が良いと思いました概要。

['measurement_id', 'time', 'group']で定義されたグループごとに、次の関数を呼び出す必要があります。

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 )

パフォーマンスを向上させるために、2つの方法を試しました。

パンダラレルパッケージ

最初のアプローチは、pandarallelパッケージを使用して計算を並列化することでした:

pandarallel.initialize( progress_bar = True )
df \
  .groupby( ['measurement_id', 'time', 'group'] ) \
  .parallel_apply( lambda x: cluster( x['var'], x['object'] ) )

ただし、これはRAMを大量に消費し、すべてのコアが計算で使用されるわけではないため、最適ではないようです(pandarallel.initialize()メソッドでコアの数を明示的に指定した場合でも)。また、その理由(RAMの不足など)を見つける機会がなかったにもかかわらず、計算がさまざまなエラーで終了することがあります。

PySpark Pandas UDF

私はSpark Pandas UDFも試しましたが、Sparkはまったく初めてです。これが私の試みです:

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()

残念ながら、パフォーマンスも不十分でした。このトピックで読んだところ、これはPythonで記述されたUDF関数を使用する負担であり、すべてのPythonオブジェクトからSparkオブジェクトへ、そしてその逆。

これが私の質問です:

  1. 可能性のあるボトルネックを排除してパフォーマンスを向上させるために、私のアプローチのいずれかを調整できますか? (例:PySparkのセットアップ、最適でない操作の調整など)
  2. それらはより良い代替品ですか?パフォーマンスに関して、提供されたソリューションとどのように比較しますか?
9
Kuba_

私はDaskの専門家ではありませんが、ベースラインとして次のコードを提供します。

import dask.dataframe as ddf

df = ddf.from_pandas(df, npartitions=4) # My PC has 4 cores

task = df.groupby(["measurement_id", "time", "group"]).apply(
    lambda x: cluster(x["var"], x["object"]),
    meta=pd.Series(np.nan, index=pd.Series([0, 1, 1, 1])),
)

res = task.compute()
0
loopy