web-dev-qa-db-ja.com

PySparkグループ内の中央値/変位値

Sparkデータフレーム(PySparkを使用)でグループ変位値を計算したいです。近似または正確な結果で問題ありません。groupBy/aggなので、他のPySpark集計関数と混合できますが、これが何らかの理由で不可能な場合は、別のアプローチでも問題ありません。

この質問 は関連していますが、approxQuantileを集約関数として使用する方法を示していません。

percentile_approx Hive UDFにもアクセスできますが、それを集計関数として使用する方法がわかりません。

特異性のために、次のデータフレームがあると仮定します。

from pyspark import SparkContext
import pyspark.sql.functions as f

sc = SparkContext()    

df = sc.parallelize([
    ['A', 1],
    ['A', 2],
    ['A', 3],
    ['B', 4],
    ['B', 5],
    ['B', 6],
]).toDF(('grp', 'val'))

df_grp = df.groupBy('grp').agg(f.magic_percentile('val', 0.5).alias('med_val'))
df_grp.show()

期待される結果は次のとおりです。

+----+-------+
| grp|med_val|
+----+-------+
|   A|      2|
|   B|      5|
+----+-------+
24
abeboparebop

もう必要ないでしょう。しかし、将来の世代のためにここに残します(つまり、来週忘れたとき)。

from pyspark.sql import Window
import pyspark.sql.functions as F

grp_window = Window.partitionBy('grp')
magic_percentile = F.expr('percentile_approx(val, 0.5)')

df.withColumn('med_val', magic_percentile.over(grp_window))

またはあなたの質問に正確に対処するために、これも機能します:

df.groupBy('gpr').agg(magic_percentile.alias('med_val'))

ボーナスとして、パーセンタイルの配列を渡すことができます:

quantiles = F.expr('percentile_approx(val, array(0.25, 0.5, 0.75))')

そして、あなたは見返りにリストを取得します。

28
kael

percentile_approxにアクセスできるので、簡単な解決策の1つはSQLコマンドで使用することです。

from pyspark.sql import SQLContext
sqlContext = SQLContext(sc)

df.registerTempTable("df")
df2 = sqlContext.sql("select grp, percentile_approx(val, 0.5) as med_val from df group by grp")
10
Shaido

残念ながら、私の知る限り、「純粋な」PySparkコマンドを使用してこれを行うことはできないようです(ShaidoのソリューションはSQLの回避策を提供します)。その理由は非常に基本的です。 meanapproxQuantileなどの関数はColumn型を返しませんが、list

サンプルデータの簡単な例を見てみましょう。

spark.version
# u'2.2.0'

import pyspark.sql.functions as func
from pyspark.sql import DataFrameStatFunctions as statFunc

# aggregate with mean works OK:
df_grp_mean = df.groupBy('grp').agg(func.mean(df['val']).alias('mean_val'))
df_grp_mean.show()
# +---+--------+ 
# |grp|mean_val|
# +---+--------+
# |  B|     5.0|
# |  A|     2.0|
# +---+--------+

# try aggregating by median:
df_grp_med = df.groupBy('grp').agg(statFunc(df).approxQuantile('val', [0.5], 0.1))
# AssertionError: all exprs should be Column

# mean aggregation is a Column, but median is a list:

type(func.mean(df['val']))
# pyspark.sql.column.Column

type(statFunc(df).approxQuantile('val', [0.5], 0.1))
# list

私が言ったように、根本的な理由は非常に基本的なものであるため、ウィンドウベースのアプローチが何らかの違いを生むとは思わない。

詳細については my answer here も参照してください。

8
desertnaut