web-dev-qa-db-fra.com

Spark Créer une fonction de colonne personnalisée, fonction définie par l'utilisateur

J'utilise Scala et je veux créer ma propre fonction DataFrame. Par exemple, je veux traiter une colonne comme un tableau, parcourir chaque élément et faire un calcul.

Pour commencer, j'essaie d'implémenter ma propre méthode getMax. La colonne x aurait donc les valeurs [3,8,2,5,9], et la sortie attendue de la méthode serait 9.

Voici à quoi ça ressemble à Scala

def getMax(inputArray: Array[Int]): Int = {
   var maxValue = inputArray(0)
   for (i <- 1 until inputArray.length if inputArray(i) > maxValue) {
     maxValue = inputArray(i)
   }
   maxValue
}

Voici ce que j'ai jusqu'à présent, et obtenez cette erreur

"value length is not a member of org.Apache.spark.sql.column", 

et je ne sais pas comment itérer autrement dans la colonne.

def getMax(col: Column): Column = {
var maxValue = col(0)
for (i <- 1 until col.length if col(i) > maxValue){
    maxValue = col(i)
}
maxValue

}

Une fois que je pourrai implémenter ma propre méthode, je créerai une fonction colonne

val value_max:org.Apache.spark.sql.Column=getMax(df.col(“value”)).as(“value_max”)

Et puis j'espère pouvoir l'utiliser dans une instruction SQL, par exemple

val sample = sqlContext.sql("SELECT value_max(x) FROM table")

et la sortie attendue serait de 9, compte tenu de la colonne d'entrée [3,8,2,5,9]

Je suis en train de suivre une réponse d'un autre thread Spark Scala - Comment puis-je itérer les lignes dans le cadre de données et ajouter des valeurs calculées en tant que nouvelles colonnes du bloc de données où ils créent un méthode privée pour l'écart-type. Les calculs que je ferai seront plus complexes que cela (par exemple, je comparerai chaque élément de la colonne), est-ce que je vais dans la bonne direction ou devrais-je chercher plus dans les fonctions définies par l'utilisateur?

18
other15

Dans un Spark DataFrame, vous ne pouvez pas parcourir les éléments d'une colonne en utilisant les approches auxquelles vous pensiez car une colonne n'est pas un objet itérable.

Cependant, pour traiter les valeurs d'une colonne, vous avez quelques options et la bonne dépend de votre tâche:

1) Utilisation des fonctions intégrées existantes

Spark SQL possède déjà de nombreuses fonctions utiles pour le traitement des colonnes, notamment des fonctions d'agrégation et de transformation. La plupart d'entre eux se trouvent dans le package functions ( documentation ici ). Vous en trouverez d'autres (fonctions binaires en général) directement dans l'objet Column ( documentation ici ). Donc, si vous pouvez les utiliser, c'est généralement la meilleure option. Remarque: n'oubliez pas les Fonctions de fenêtre .

2) Création d'un UDF

Si vous ne pouvez pas terminer votre tâche avec les fonctions intégrées, vous pouvez envisager de définir une UDF (fonction définie par l'utilisateur). Ils sont utiles lorsque vous pouvez traiter chaque élément d'une colonne indépendamment et que vous vous attendez à produire une nouvelle colonne avec le même nombre de lignes que l'original (pas une colonne agrégée). Cette approche est assez simple: d'abord, vous définissez une fonction simple, puis vous l'enregistrez en tant qu'UDF, puis vous l'utilisez. Exemple:

def myFunc: (String => String) = { s => s.toLowerCase }

import org.Apache.spark.sql.functions.udf
val myUDF = udf(myFunc)

val newDF = df.withColumn("newCol", myUDF(df("oldCol")))

Pour plus d'informations, voici un bel article.

3) Utilisation d'un UDAF

Si votre tâche consiste à créer des données agrégées, vous pouvez définir une UDAF (User Defined Aggregation Function). Je n'ai pas beaucoup d'expérience avec cela, mais je peux vous indiquer un joli tutoriel:

https://ragrawal.wordpress.com/2015/11/03/spark-custom-udaf-example/

4) Revenir au traitement RDD

Si vous ne pouvez vraiment pas utiliser les options ci-dessus, ou si votre tâche de traitement dépend de différentes lignes pour en traiter une et que ce n'est pas une agrégation, je pense que vous devrez sélectionner la colonne que vous souhaitez et la traiter à l'aide du RDD correspondant. Exemple:

val singleColumnDF = df("column")

val myRDD = singleColumnDF.rdd

// process myRDD

Donc, il y avait les options auxquelles je pouvais penser. J'espère que ça aide.

27
Daniel de Paula

Un exemple simple est donné dans le excellente documentation , où une section entière est dédiée aux UDF:

import org.Apache.spark.sql._

val df = Seq(("id1", 1), ("id2", 4), ("id3", 5)).toDF("id", "value")
val spark = df.sparkSession
spark.udf.register("simpleUDF", (v: Int) => v * v)
df.select($"id", callUDF("simpleUDF", $"value"))
4
Boern