web-dev-qa-db-fra.com

Composition de la fonction par ligne de PySpark

À titre d'exemple simplifié, j'ai une trame de données "df" avec les colonnes "col1, col2" et je veux calculer un maximum par ligne après avoir appliqué une fonction à chaque colonne:

def f(x):
    return (x+1)

max_udf=udf(lambda x,y: max(x,y), IntegerType())
f_udf=udf(f, IntegerType())

df2=df.withColumn("result", max_udf(f_udf(df.col1),f_udf(df.col2)))

Donc, si df:

col1   col2
1      2
3      0

Ensuite

df2:

col1   col2  result
1      2     3
3      0     4

Ce qui précède ne semble pas fonctionner et produit "Impossible d'évaluer l'expression: PythonUDF # f ..."

Je suis absolument certain que "f_udf" fonctionne très bien sur ma table, et le problème principal est avec max_udf.

Sans créer de colonnes supplémentaires ou utiliser une carte de base/réduire, existe-t-il un moyen de faire tout ce qui précède en utilisant entièrement des cadres de données et des udfs? Comment dois-je modifier "max_udf"?

J'ai aussi essayé:

max_udf=udf(max, IntegerType())

ce qui produit la même erreur.

J'ai également confirmé que les travaux suivants:

df2=(df.withColumn("temp1", f_udf(df.col1))
       .withColumn("temp2", f_udf(df.col2))

df2=df2.withColumn("result", max_udf(df2.temp1,df2.temp2))

Pourquoi est-ce que je ne peux pas les faire en une seule fois?

Je voudrais voir une réponse qui se généralise à n'importe quelle fonction "f_udf" et "max_udf."

25
Alex R.

J'ai eu un problème similaire et j'ai trouvé la solution dans la réponse à cette question stackoverflow

Pour passer plusieurs colonnes ou une ligne entière à un UDF, utilisez un struct :

from pyspark.sql.functions import udf, struct
from pyspark.sql.types import IntegerType

df = sqlContext.createDataFrame([(None, None), (1, None), (None, 2)], ("a", "b"))

count_empty_columns = udf(lambda row: len([x for x in row if x == None]), IntegerType())

new_df = df.withColumn("null_count", count_empty_columns(struct([df[x] for x in df.columns])))

new_df.show()

résultats:

+----+----+----------+
|   a|   b|null_count|
+----+----+----------+
|null|null|         2|
|   1|null|         1|
|null|   2|         1|
+----+----+----------+
38
Christoph Hösler

UserDefinedFunction génère une erreur lors de l'acceptation des FDU comme arguments.

Vous pouvez modifier le max_udf comme ci-dessous pour le faire fonctionner.

df = sc.parallelize([(1, 2), (3, 0)]).toDF(["col1", "col2"])

max_udf = udf(lambda x, y: max(x + 1, y + 1), IntegerType())

df2 = df.withColumn("result", max_udf(df.col1, df.col2))

Ou

def f_udf(x):
    return (x + 1)

max_udf = udf(lambda x, y: max(x, y), IntegerType())
## f_udf=udf(f, IntegerType())

df2 = df.withColumn("result", max_udf(f_udf(df.col1), f_udf(df.col2)))

Remarque:

La seconde approche est valable si et seulement si les fonctions internes (ici f_udf) Génèrent des expressions SQL valides.

Cela fonctionne ici parce que f_udf(df.col1) et f_udf(df.col2) sont évalués comme Column<b'(col1 + 1)'> et Column<b'(col2 + 1)'> respectivement, avant d'être passés à max_udf. Cela ne fonctionnerait pas avec une fonction arbitraire.

Cela ne fonctionnerait pas si nous essayions par exemple quelque chose comme ceci:

from math import exp

df.withColumn("result", max_udf(exp(df.col1), exp(df.col2)))
6
Mohan