Spark 2.0データセットとデータフレーム
spark 2.0.1で始まります。いくつかの質問を受けました。多くのドキュメントを読みましたが、これまでのところ十分な答えが見つかりませんでした。
- 違いは何ですか
df.select("foo")
df.select($"foo")
- 私はそれを正しく理解していますか
myDataSet.map(foo.someVal)
はタイプセーフであり、RDD
に変換されませんが、DataSet表現のままです/追加のオーバーヘッドはありません(2.0.0のパフォーマンスは賢明です)
- 他のすべてのコマンド、たとえばselect、..は単に構文上の砂糖です。それらはタイプセーフではなく、代わりにマップを使用できます。どうすればマップステートメントなしで
df.select("foo")
型保証できますか?- マップの代わりにUDF/UADFを使用する必要があるのはなぜですか(マップがデータセット表現に留まっていると仮定した場合)?
df.select("foo")
とdf.select($"foo")
の違いはシグネチャです。前者は少なくとも1つのString
を受け取り、後者は0以上のColumns
を受け取ります。それを超える実用的な違いはありません。myDataSet.map(foo.someVal)
型チェックが行われますが、Dataset
操作はオブジェクトのRDD
を使用するため、DataFrame
操作と比較すると、かなりのオーバーヘッドがあります。簡単な例を見てみましょう:case class FooBar(foo: Int, bar: String) val ds = Seq(FooBar(1, "x")).toDS ds.map(_.foo).explain
== Physical Plan == *SerializeFromObject [input[0, int, true] AS value#123] +- *MapElements <function1>, obj#122: int +- *DeserializeToObject newInstance(class $line67.$read$$iw$$iw$FooBar), obj#121: $line67.$read$$iw$$iw$FooBar +- LocalTableScan [foo#117, bar#118]
ご覧のとおり、この実行プランはすべてのフィールドへのアクセスを必要とし、
DeserializeToObject
を実行する必要があります。いいえ。一般的に、他のメソッドは構文砂糖ではなく、大幅に異なる実行プランを生成します。例えば:
ds.select($"foo").explain
== Physical Plan == LocalTableScan [foo#117]
列に直接アクセスできるようになる前のプランと比較。これはAPIの制限ではなく、運用上のセマンティクスの違いによるものです。
Mapステートメントなしでdf.select( "foo")の型保証をするにはどうすればよいですか?
そのようなオプションはありません。型指定された列を使用すると、静的に
Dataset
を静的に型指定された別のDataset
に変換できます。ds.select($"bar".as[Int])
タイプセーフはありません。タイプセーフな最適化された操作 型付き集計のような を含める他の試みがいくつかありますが、この実験的なAPIです。
マップの代わりにUDF/UADFを使用する理由
それは完全にあなた次第です。 Sparkの各分散データ構造には、独自の利点と欠点があります(たとえば、 Spark UDAFとArrayTypeをbufferSchemaのパフォーマンスの問題として使用 を参照)。
個人的には、静的に型付けされたDataset
が最も役に立たないことがわかります。
Dataset[Row]
と同じ範囲の最適化を提供しないでください(ただし、ストレージ形式といくつかの実行プランの最適化を共有しますが、コード生成やオフヒープストレージのメリットは十分ではありません)。DataFrame
。型付き変換はブラックボックスであり、オプティマイザの分析バリアを効果的に作成します。たとえば、選択(フィルター)を型付き変換にプッシュすることはできません。
ds.groupBy("foo").agg(sum($"bar") as "bar").as[FooBar].filter(x => true).where($"foo" === 1).explain
== Physical Plan == *Filter (foo#133 = 1) +- *Filter <function1>.apply +- *HashAggregate(keys=[foo#133], functions=[sum(cast(bar#134 as double))]) +- Exchange hashpartitioning(foo#133, 200) +- *HashAggregate(keys=[foo#133], functions=[partial_sum(cast(bar#134 as double))]) +- LocalTableScan [foo#133, bar#134]
に比べ:
ds.groupBy("foo").agg(sum($"bar") as "bar").as[FooBar].where($"foo" === 1).explain
== Physical Plan == *HashAggregate(keys=[foo#133], functions=[sum(cast(bar#134 as double))]) +- Exchange hashpartitioning(foo#133, 200) +- *HashAggregate(keys=[foo#133], functions=[partial_sum(cast(bar#134 as double))]) +- *Filter (foo#133 = 1) +- LocalTableScan [foo#133, bar#134]
これは、述語プッシュダウンやプロジェクションプッシュダウンなどの機能に影響します。
ネイティブでサポートされている型の小さなサブセットのみで、
RDDs
ほど柔軟性はありません。Encoders
を使用した「タイプセーフ」は、Dataset
がas
メソッドを使用して変換された場合に問題になります。データ形式は署名を使用してエンコードされないため、コンパイラーはEncoder
の存在のみを検証できます。
関連する質問:
Spark Dataset
は、Spark Dataframe
よりも強力です。小さな例-作成できるのはDataframe
of Row
だけです。 Tuple
またはDataset
以外の任意のプリミティブデータ型を使用すると、プリミティブ以外の型のDataset
を作成することもできます。つまり、文字どおりオブジェクト型のDataset
を作成できます。 。
例:
case class Employee(id:Int,name:String)
Dataset[Employee] // is valid
Dataframe[Employee] // is invalid