web-dev-qa-db-ja.com

Scala DataFrameの行を最も効率的にケースクラスに変換する方法は?

Spark DataframeまたはCatalystのいずれかのRowクラスを取得したら、コードでケースクラスに変換します。これは、

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

しかし、行に膨大な数の列、たとえば数十個のDouble、いくつかのブール値、および場合によってはnullが含まれていると、見苦しくなります。

RowをmyCaseClassに-sorry-キャストできるようにしたいだけです。それは可能ですか、またはすでに最も経済的な構文を持っていますか?

43
arivero

DataFrameは、単にDataset [Row]の型エイリアスです。これらの操作は、厳密に型指定されたScala/Javaデータセットに付属する「型付き変換」とは対照的に、「型なし変換」とも呼ばれます。

Dataset [Row]からDataset [Person]への変換は非常に簡単です

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

この時点で、Sparkは、正確なタイプを知らないため、データを汎用のRowオブジェクトのコレクションであるDataFrame = Dataset [Row]に変換します。

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

現在、SparkはDataset[Row] -> Dataset[Person]タイプ固有Scala/JavaクラスPersonが指示するJVMオブジェクト。

詳細については、databricksが提供する以下のリンクを参照してください。

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

36
Rahul

私の知る限り、行をケースクラスにキャストすることはできませんが、次のように、行フィールドに直接アクセスすることを選択することもありました。

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

これは、特にケースクラスコンストラクターが行の一部のフィールドのみを必要とする場合に簡単です。

もちろん、Rowオブジェクトをケースクラスに一致させることができます。 SchemaTypeに多くのフィールドがあり、そのうちのいくつかをケースクラスに一致させたいとします。 nullフィールドがない場合は、次の操作を実行できます。

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

Null値の場合、このアプローチは失敗し、各単一フィールドのタイプを明示的に定義する必要があります。 null値を処理する必要がある場合は、null値を含むすべての行を破棄する必要があります。

dataframe.na.drop()

Nullフィールドがケースクラスのパターンマッチングで使用されるフィールドではない場合でも、レコードが削除されます。または、それを処理する場合は、Rowオブジェクトをリストに変換してから、オプションパターンを使用できます。

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

SparkおよびDataFrame APIを単純化し、より機能的なプログラミング指向にするためのライブラリをすぐに導入するこのgithubプロジェクトSparkz()を確認してください。

7