web-dev-qa-db-fra.com

Définition d'un UDF qui accepte un tableau d'objets dans un Spark DataFrame?

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.

25
ohruunuruus

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 :

  • La représentation actuelle de ArrayType est, comme vous le savez déjà, WrappedArray donc Array ne fonctionnera pas et il vaut mieux rester prudent.
  • Selon la spécification officielle , le type local (externe) pour 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? .

23
zero323