web-dev-qa-db-ja.com

PySparkのGroupedDataにUDFを適用する(機能するpythonの例)

私はこれを持っていますpython pandasデータフレームでローカルに実行されるコード:

df_result = pd.DataFrame(df
                          .groupby('A')
                          .apply(lambda x: myFunction(Zip(x.B, x.C), x.name))

これをPySparkで実行したいのですが、pyspark.sql.group.GroupedDataオブジェクトの処理に問題があります。

私は次を試しました:

sparkDF
 .groupby('A')
 .agg(myFunction(Zip('B', 'C'), 'A')) 

返す

KeyError: 'A'

「A」は列ではなく、x.nameに相当するものが見つからないためだと思います。

その後

sparkDF
 .groupby('A')
 .map(lambda row: Row(myFunction(Zip('B', 'C'), 'A'))) 
 .toDF()

ただし、次のエラーが発生します。

AttributeError: 'GroupedData' object has no attribute 'map'

どんな提案でも本当に感謝されます!

25
arosner09

しようとしているのは、UDF(ユーザー定義関数)ではなく、UDAF(ユーザー定義集計関数)を記述することです。 UDAFは、キーでグループ化されたデータを処理する機能です。具体的には、グループ内の複数の値を単一のパーティションにマージする方法を定義し、次にキーのパーティション間で結果をマージする方法を定義する必要があります。現在、pythonにUDAFを実装する方法はありません。Scalaでのみ実装できます。

ただし、Pythonで回避できます。収集セットを使用してグループ化された値を収集し、通常のUDFを使用して必要な処理を実行できます。唯一の注意点は、collect_setがプリミティブ値でのみ機能するため、それらを文字列にエンコードする必要があることです。

from pyspark.sql.types import StringType
from pyspark.sql.functions import col, collect_list, concat_ws, udf

def myFunc(data_list):
    for val in data_list:
        b, c = data.split(',')
        # do something

    return <whatever>

myUdf = udf(myFunc, StringType())

df.withColumn('data', concat_ws(',', col('B'), col('C'))) \
  .groupBy('A').agg(collect_list('data').alias('data'))
  .withColumn('data', myUdf('data'))

重複排除が必要な場合は、collect_setを使用します。また、いくつかのキーに多くの値がある場合、キーのすべての値をクラスター上のどこかの単一パーティションに収集する必要があるため、これは遅くなります。最終結果が何らかの方法でキーごとの値を組み合わせて(たとえば合計することで)構築する値である場合、中間値を構築できる RDD aggregateByKey メソッドを使用して実装する方が速い場合がありますデータをシャッフルする前に、パーティション内の各キーに対して。

編集:11/21/2018

この回答が書かれて以来、pysparkはPandasを使用したUDAFのサポートを追加しました。 PandaのUDFとUDAFをストレートpython関数をRDDで使用する場合に使用すると、ニースのパフォーマンスが向上します。フードの下で、列をベクトル化します。 こちら でより良い説明をご覧になるか、または ser6910411 の例をご覧ください。

37
Ryan Widmaier

Spark 2.3からpandas_udfを使用できます。 GROUPED_MAPCallable[[pandas.DataFrame], pandas.DataFrame]または、言い換えると、入力と同じ形状のPandas DataFrameから出力DataFrameにマッピングする関数です。

たとえば、データが次のようになっている場合:

df = spark.createDataFrame(
    [("a", 1, 0), ("a", -1, 42), ("b", 3, -1), ("b", 10, -2)],
    ("key", "value1", "value2")
)

value1value2の間のペアワイズ最小値の平均値を計算するには、出力スキーマを定義する必要があります。

from pyspark.sql.types import *

schema = StructType([
    StructField("key", StringType()),
    StructField("avg_min", DoubleType())
])

pandas_udf

import pandas as pd

from pyspark.sql.functions import pandas_udf
from pyspark.sql.functions import PandasUDFType

@pandas_udf(schema, functionType=PandasUDFType.GROUPED_MAP)
def g(df):
    result = pd.DataFrame(df.groupby(df.key).apply(
        lambda x: x.loc[:, ["value1", "value2"]].min(axis=1).mean()
    ))
    result.reset_index(inplace=True, drop=False)
    return result

そしてそれを適用します:

df.groupby("key").apply(g).show()
+---+-------+
|key|avg_min|
+---+-------+
|  b|   -1.5|
|  a|   -0.5|
+---+-------+

スキーマ定義とデコレータを除くと、現在のPandasコードはそのまま適用できます。

Spark 2.4.0)には、 GROUPED_AGG バリアントもあり、Callable[[pandas.Series, ...], T]を取ります。ここで、Tはプリミティブスカラーです。

import numpy as np

@pandas_udf(DoubleType(), functionType=PandasUDFType.GROUPED_AGG)
def f(x, y):
    return np.minimum(x, y).mean()

これは標準のgroup_by/aggコンストラクトで使用できます:

df.groupBy("key").agg(f("value1", "value2").alias("avg_min")).show()
+---+-------+
|key|avg_min|
+---+-------+
|  b|   -1.5|
|  a|   -0.5|
+---+-------+

GROUPED_MAPGROUPPED_AGGpandas_udfUserDefinedAggregateFunctionまたはAggregatorと同じように動作せず、groupByKeyに近いことに注意してください。または無制限のフレームを持つウィンドウ関数。データが最初にシャッフルされ、その後のみUDFが適用されます。

最適化された実行のためには、 implement Scala UserDefinedAggregateFunction and add Python wrapper

も参照してくださいPySparkのウィンドウに適用されるユーザー定義関数?

32
user6910411

私は答えの上に拡張するつもりです。

したがって、@ pandas_udfを使用してpysparkでpandas.groupby()。applyのような同じロジックを実装できます。これはベクトル化メソッドであり、単純なudfよりも高速です。

from pyspark.sql.functions import pandas_udf,PandasUDFType

df3 = spark.createDataFrame(
[("a", 1, 0), ("a", -1, 42), ("b", 3, -1), ("b", 10, -2)],
("key", "value1", "value2")
)

from pyspark.sql.types import *

schema = StructType([
    StructField("key", StringType()),
    StructField("avg_value1", DoubleType()),
    StructField("avg_value2", DoubleType()),
    StructField("sum_avg", DoubleType()),
    StructField("sub_avg", DoubleType())
])

@pandas_udf(schema, functionType=PandasUDFType.GROUPED_MAP)
def g(df):
    gr = df['key'].iloc[0]
    x = df.value1.mean()
    y = df.value2.mean()
    w = df.value1.mean() + df.value2.mean()
    z = df.value1.mean() - df.value2.mean()
    return pd.DataFrame([[gr]+[x]+[y]+[w]+[z]])

df3.groupby("key").apply(g).show()

以下の結果が得られます。

+---+----------+----------+-------+-------+
|key|avg_value1|avg_value2|sum_avg|sub_avg|
+---+----------+----------+-------+-------+
|  b|       6.5|      -1.5|    5.0|    8.0|
|  a|       0.0|      21.0|   21.0|  -21.0|
+---+----------+----------+-------+-------+

したがって、グループ化されたデータの他のフィールド間でより多くの計算を実行し、それらをリスト形式でデータフレームに追加できます。

6
Mayur Dangar