web-dev-qa-db-fra.com

Comment convertir une ligne d'un Scala DataFrame en classe de cas plus efficacement?

Une fois que je suis entré Spark une classe Row, Dataframe ou Catalyst, je souhaite le convertir en classe de cas dans mon code. Cela peut être fait en faisant correspondre

someRow match {case Row(a:Long,b:String,c:Double) => myCaseClass(a,b,c)}

Mais cela devient moche quand la rangée a un grand nombre de colonnes, disons une douzaine de doubles, des booléens et même des nuls occasionnels.

Je voudrais simplement pouvoir - désoler - de lancer Row vers myCaseClass. Est-ce possible ou ai-je déjà la syntaxe la plus économique?

43
arivero

DataFrame est simplement un alias de type de Dataset [Row]. Ces opérations sont également appelées "transformations non typées" par opposition aux "transformations typées" fournies avec des jeux de données Scala/Java fortement typés.

La conversion de Dataset [Row] à Dataset [Person] est très simple en spark

val DFtoProcess = SQLContext.sql("SELECT * FROM peoples WHERE name='test'")

À ce stade, Spark convertit vos données en DataFrame = Dataset [Row], une collection d'objets Row génériques, car il ne connaît pas le type exact.

// Create an Encoders for Java class (In my eg. Person is a Java class)
// For scala case class you can pass Person without .class reference
val personEncoder = Encoders.bean(Person.class) 

val DStoProcess = DFtoProcess.as[Person](personEncoder)

Maintenant, Spark convertit le Dataset[Row] -> Dataset[Person] spécifique au type Scala/Java de la JVM, tel que dicté par la classe Person.

Veuillez vous référer au lien ci-dessous fourni par databricks pour plus de détails.

https://databricks.com/blog/2016/07/14/a-tale-of-three-Apache-spark-apis-rdds-dataframes-and-datasets.html

36
Rahul

Autant que je sache, vous ne pouvez pas attribuer une ligne à une classe de cas, mais j'ai parfois choisi d'accéder directement aux champs de la ligne, comme

map(row => myCaseClass(row.getLong(0), row.getString(1), row.getDouble(2))

Je trouve cela plus facile, en particulier si le constructeur de la classe de cas n'a besoin que de certains champs de la ligne.

23
scala> import spark.implicits._    
scala> val df = Seq((1, "james"), (2, "tony")).toDF("id", "name")
df: org.Apache.spark.sql.DataFrame = [id: int, name: string]

scala> case class Student(id: Int, name: String)
defined class Student

scala> df.as[Student].collectAsList
res6: Java.util.List[Student] = [Student(1,james), Student(2,tony)]

Ici le spark dans spark.implicits._ est votre SparkSession. Si vous êtes à l'intérieur du REPL), la session est déjà définie en tant que spark sinon vous devez ajuster le nom en conséquence pour qu'il corresponde à votre SparkSession.

15
secfree

Vous pouvez bien sûr faire correspondre un objet Row à une classe de cas. Supposons que votre type de schéma comporte de nombreux champs et que vous souhaitiez en faire correspondre quelques-uns dans votre classe de cas. Si vous n'avez pas de champs nuls, vous pouvez simplement faire:

case class MyClass(a: Long, b: String, c: Int, d: String, e: String)

dataframe.map {
  case Row(a: Java.math.BigDecimal, 
    b: String, 
    c: Int, 
    _: String,
    _: Java.sql.Date, 
    e: Java.sql.Date,
    _: Java.sql.Timestamp, 
    _: Java.sql.Timestamp, 
    _: Java.math.BigDecimal, 
    _: String) => MyClass(a = a.longValue(), b = b, c = c, d = d.toString, e = e.toString)
}

Cette approche échouera en cas de valeurs NULL et vous obligera également à définir explicitement le type de chaque champ. Si vous devez gérer des valeurs NULL, vous devez soit supprimer toutes les lignes contenant des valeurs NULL en effectuant

dataframe.na.drop()

Cela supprimera des enregistrements même si les champs nuls ne sont pas ceux utilisés dans votre correspondance de modèle pour votre classe de cas. Ou, si vous souhaitez le gérer, vous pouvez transformer l'objet Row en une liste, puis utiliser le modèle d'option:

case class MyClass(a: Long, b: String, c: Option[Int], d: String, e: String)

dataframe.map(_.toSeq.toList match {
  case List(a: Java.math.BigDecimal, 
    b: String, 
    c: Int, 
    _: String,
    _: Java.sql.Date, 
    e: Java.sql.Date,
    _: Java.sql.Timestamp, 
    _: Java.sql.Timestamp, 
    _: Java.math.BigDecimal, 
    _: String) => MyClass(
      a = a.longValue(), b = b, c = Option(c), d = d.toString, e = e.toString)
}

Vérifiez ce projet github Sparkz () qui introduira bientôt de nombreuses bibliothèques pour simplifier les API Spark et DataFrame) et les rendre plus orientées vers la programmation fonctionnelle.

7