Lorsque vous travaillez avec les DataFrames de Spark, des fonctions définies par l'utilisateur (UDF) sont nécessaires pour mapper les données dans les colonnes. Les FDU nécessitent que les types d'arguments soient explicitement spécifiés. Dans mon cas, je dois manipuler une colonne composée de tableaux d'objets et je ne sais pas quel type utiliser. Voici un exemple:
import sqlContext.implicits._
// Start with some data. Each row (here, there's only one row)
// is a topic and a bunch of subjects
val data = sqlContext.read.json(sc.parallelize(Seq(
"""
|{
| "topic" : "pets",
| "subjects" : [
| {"type" : "cat", "score" : 10},
| {"type" : "dog", "score" : 1}
| ]
|}
""")))
Il est relativement simple d'utiliser le org.Apache.spark.sql.functions
Intégré pour effectuer des opérations de base sur les données dans les colonnes
import org.Apache.spark.sql.functions.size
data.select($"topic", size($"subjects")).show
+-----+--------------+
|topic|size(subjects)|
+-----+--------------+
| pets| 2|
+-----+--------------+
et il est généralement facile d'écrire des UDF personnalisés pour effectuer des opérations arbitraires
import org.Apache.spark.sql.functions.udf
val enhance = udf { topic : String => topic.toUpperCase() }
data.select(enhance($"topic"), size($"subjects")).show
+----------+--------------+
|UDF(topic)|size(subjects)|
+----------+--------------+
| PETS| 2|
+----------+--------------+
Mais que se passe-t-il si je veux utiliser un UDF pour manipuler le tableau d'objets dans la colonne "sujets"? Quel type dois-je utiliser pour l'argument dans l'UDF? Par exemple, si je veux réimplémenter la fonction taille, au lieu d'utiliser celle fournie par spark:
val my_size = udf { subjects: Array[Something] => subjects.size }
data.select($"topic", my_size($"subjects")).show
Il est clair que Array[Something]
Ne fonctionne pas ... quel type dois-je utiliser!? Dois-je abandonner Array[]
? Fouiller me dit que scala.collection.mutable.WrappedArray
Peut avoir quelque chose à voir avec ça, mais il y a encore un autre type que je dois fournir.
Ce que vous recherchez est Seq[o.a.s.sql.Row]
:
import org.Apache.spark.sql.Row
val my_size = udf { subjects: Seq[Row] => subjects.size }
Explication :
ArrayType
est, comme vous le savez déjà, WrappedArray
donc Array
ne fonctionnera pas et il vaut mieux rester prudent.StructType
est Row
. Malheureusement, cela signifie que l'accès aux champs individuels n'est pas sécurisé.Remarques :
Pour créer struct
dans Spark <2.3, la fonction passée à udf
doit renvoyer Product
type (Tuple*
Ou case class
), Pas Row
. C'est parce que les variantes udf
correspondantes dépendent de Scala réflexion :
Définit un Scala fermeture des arguments n en tant que fonction définie par l'utilisateur (UDF). Les types de données sont automatiquement déduits en fonction de la signature de la fermeture Scala.
Dans Spark> = 2.3, il est possible de renvoyer Row
directement, tant que le schéma est fourni .
def udf(f: AnyRef, dataType: DataType): UserDefinedFunction
Définit une fonction définie par l'utilisateur déterministe (UDF) à l'aide d'une fermeture Scala. Pour cette variante, l'appelant doit spécifier le type de données de sortie, et il n'y a pas d'entrée automatique type coercition.
Voir par exemple Comment créer un Spark UDF dans Java/Kotlin qui retourne un type complexe? .