web-dev-qa-db-de.com

Wie konvertiert man die Zeile eines Scala DataFrame in eine Fallklasse am effizientesten?

Sobald ich in einer Zeilenklasse (Dataframe oder Catalyst) Spark angekommen bin, möchte ich sie in meinem Code in eine case-Klasse konvertieren. Dies kann durch Matching erfolgen

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

Aber es wird hässlich, wenn die Zeile eine große Anzahl von Spalten enthält, beispielsweise ein Dutzend Doubles, einige Booleaner und gelegentlich sogar Null.

Ich möchte nur in der Lage sein, Row zu myCaseClass zu übertragen. Ist das möglich oder habe ich schon die sparsamste Syntax?

43
arivero

DataFrame ist einfach ein Typalias von Dataset [Row]. Diese Operationen werden auch als "untypisierte Transformationen" bezeichnet, im Gegensatz zu "typisierten Transformationen", die mit stark typisierten Scala/Java-Datasets geliefert werden.

Die Konvertierung von Dataset [Row] nach Dataset [Person] ist im Spark sehr einfach

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

Zu diesem Zeitpunkt konvertiert Spark Ihre Daten in DataFrame = Dataset [Row], eine Sammlung von generischen Row-Objekten, da der genaue Typ nicht bekannt ist.

// 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)

Nun konvertiert Spark das Dataset[Row] -> Dataset[Person] typspezifisch Scala/Java JVM-Objekt, wie von der Klasse Person vorgegeben.

Weitere Informationen finden Sie unter dem Link, der von Databricks bereitgestellt wird

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

36
Rahul

Soweit ich weiß, können Sie eine Zeile nicht in eine Case-Klasse umwandeln, aber ich habe mich manchmal dafür entschieden, direkt auf die Zeilenfelder zuzugreifen, z

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

Ich finde das einfacher, besonders wenn der Case-Class-Konstruktor nur einige der Felder aus der Zeile benötigt.

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)]

Hier das spark in spark.implicits._ ist dein SparkSession. Wenn Sie sich in der REPL) - Sitzung befinden, ist diese bereits als spark definiert, andernfalls müssen Sie den Namen entsprechend Ihrem SparkSession anpassen.

15
secfree

Natürlich können Sie ein Row-Objekt einer Case-Klasse zuordnen. Angenommen, Ihr SchemaType enthält viele Felder, und Sie möchten einige davon Ihrer Fallklasse zuordnen. Wenn Sie keine Nullfelder haben, können Sie einfach Folgendes tun:

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)
}

Dieser Ansatz schlägt bei Nullwerten fehl und erfordert, dass Sie den Typ jedes einzelnen Felds explizit definieren. Wenn Sie mit Nullwerten umgehen müssen, müssen Sie entweder alle Zeilen verwerfen, die Nullwerte enthalten

dataframe.na.drop()

Dadurch werden Datensätze gelöscht, auch wenn die Nullfelder nicht für den Mustervergleich für Ihre Fallklasse verwendet werden. Wenn Sie damit umgehen möchten, können Sie das Row-Objekt in eine Liste umwandeln und dann das Optionsmuster verwenden:

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)
}

Überprüfen Sie dieses Github-Projekt Sparkz (), das in Kürze zahlreiche Bibliotheken zur Vereinfachung der APIs Spark und DataFrame) einführt und sie funktionsorientierter programmiert.

7