web-dev-qa-db-fra.com

fonction approximative quantique de pyspark

J'ai dataframe avec ces colonnes id, price, timestamp.

J'aimerais trouver la valeur médiane groupée par id.

J'utilise ce code pour le trouver mais cela me donne cette erreur.

from pyspark.sql import DataFrameStatFunctions as statFunc
windowSpec = Window.partitionBy("id")
median = statFunc.approxQuantile("price",
                                 [0.5],
                                 0) \
                 .over(windowSpec)

return df.withColumn("Median", median)

N'est-il pas possible d'utiliser DataFrameStatFunctions pour renseigner des valeurs dans une nouvelle colonne?

TypeError: unbound method approxQuantile() must be called with DataFrameStatFunctions instance as first argument (got str instance instead)
7
BK C.

En effet, il est pas possible d’utiliser approxQuantile pour renseigner les valeurs d’une nouvelle colonne de structure de données, mais ce n’est pas la raison pour laquelle vous obtenez cette erreur. Malheureusement, toute l’histoire sous-jacente est plutôt frustrante, car j’ai soutenu , c’est le cas de nombreuses fonctionnalités de Spark (en particulier de PySpark) et de l’absence de documentation adéquate.

Pour commencer, il n'y a pas une, mais deuxapproxQuantile méthodes; le premier fait partie de la classe standard DataFrame, c’est-à-dire que vous n’avez pas besoin d’importer DataFrameStatFunctions:

spark.version
# u'2.1.1'

sampleData = [("bob","Developer",125000),("mark","Developer",108000),("carl","Tester",70000),("peter","Developer",185000),("jon","Tester",65000),("roman","Tester",82000),("simon","Developer",98000),("eric","Developer",144000),("carlos","Tester",75000),("henry","Developer",110000)]

df = spark.createDataFrame(sampleData, schema=["Name","Role","Salary"])
df.show()
# +------+---------+------+ 
# |  Name|     Role|Salary|
# +------+---------+------+
# |   bob|Developer|125000| 
# |  mark|Developer|108000|
# |  carl|   Tester| 70000|
# | peter|Developer|185000|
# |   jon|   Tester| 65000|
# | roman|   Tester| 82000|
# | simon|Developer| 98000|
# |  eric|Developer|144000|
# |carlos|   Tester| 75000|
# | henry|Developer|110000|
# +------+---------+------+

med = df.approxQuantile("Salary", [0.5], 0.25) # no need to import DataFrameStatFunctions
med
# [98000.0]

Le second fait partie de DataFrameStatFunctions, mais si vous l'utilisez comme vous le faites, vous obtenez l'erreur que vous signalez:

from pyspark.sql import DataFrameStatFunctions as statFunc
med2 = statFunc.approxQuantile( "Salary", [0.5], 0.25)
# TypeError: unbound method approxQuantile() must be called with DataFrameStatFunctions instance as first argument (got str instance instead)

parce que l'utilisation correcte est

med2 = statFunc(df).approxQuantile( "Salary", [0.5], 0.25)
med2
# [82000.0]

bien que vous ne puissiez pas trouver un exemple simple dans la documentation de PySpark à ce sujet (il m’a fallu du temps pour le comprendre moi-même) ... La meilleure partie? Les deux valeurs sont non égales:

med == med2
# False

Je suppose que cela est dû à l'algorithme non déterministe utilisé (après tout, il est supposé être une médiane approximative), et même si vous réexécutez les commandes avec les mêmes données de jouet, vous risquez d'obtenir un résultat différent. valeurs (et différentes de celles que je rapporte ici) - je suggère d'expérimenter un peu pour avoir le sentiment ...

Mais, comme je l’ai déjà dit, ce n’est pas la raison pour laquelle vous ne pouvez pas utiliser approxQuantile pour renseigner des valeurs dans une nouvelle colonne dataframe - même si vous utilisez la syntaxe correcte, vous obtiendrez une erreur différente:

df2 = df.withColumn('median_salary', statFunc(df).approxQuantile( "Salary", [0.5], 0.25))
# AssertionError: col should be Column

Ici, col fait référence au deuxième argument de l'opération withColumn, c'est-à-dire celui approxQuantile, et le message d'erreur indique qu'il ne s'agit pas d'un type Column - il s'agit en fait d'une liste:

type(statFunc(df).approxQuantile( "Salary", [0.5], 0.25))
# list

Ainsi, lors du remplissage des valeurs de colonne, Spark attend des arguments de type Column et vous ne pouvez pas utiliser de listes. Voici un exemple de création d'une nouvelle colonne avec des valeurs moyennes par rôle au lieu de valeurs médianes:

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

windowSpec = Window.partitionBy(df['Role'])
df2 = df.withColumn('mean_salary', func.mean(df['Salary']).over(windowSpec))
df2.show()
# +------+---------+------+------------------+
# |  Name|     Role|Salary|       mean_salary| 
# +------+---------+------+------------------+
# |  carl|   Tester| 70000|           73000.0| 
# |   jon|   Tester| 65000|           73000.0|
# | roman|   Tester| 82000|           73000.0|
# |carlos|   Tester| 75000|           73000.0|
# |   bob|Developer|125000|128333.33333333333|
# |  mark|Developer|108000|128333.33333333333| 
# | peter|Developer|185000|128333.33333333333| 
# | simon|Developer| 98000|128333.33333333333| 
# |  eric|Developer|144000|128333.33333333333|
# | henry|Developer|110000|128333.33333333333| 
# +------+---------+------+------------------+

qui fonctionne parce que, contrairement à approxQuantile, mean renvoie une Column:

type(func.mean(df['Salary']).over(windowSpec))
# pyspark.sql.column.Column
31
desertnaut

Ajout d'un exemple de construction d'appel de fonction par nom (par exemple percentile_approx): 

from pyspark.sql.column import Column, _to_Java_column, _to_seq

def from_name(sc, func_name, *params):
    """
       create call by function name 
    """
    callUDF = sc._jvm.org.Apache.spark.sql.functions.callUDF
    func = callUDF(func_name, _to_seq(sc, *params, _to_Java_column))
    return Column(func)

Exemple d'utilisation avec la fonction percentile_approx:

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

spark = SparkSession.builder.getOrCreate()
sc = spark.sparkContext

# build percentile_approx function call by name: 
target = from_name(sc, "percentile_approx", [f.col("salary"), f.lit(0.95)])


# load dataframe for persons data 
# with columns "person_id", "group_id" and "salary"
persons = spark.read.parquet( ... )

# apply function for each group
persons.groupBy("group_id").agg(
    target.alias("target")).show()
1
volhv