Spark 2.0 Dataset vs DataFrame
à partir de spark 2.0.1 j'ai quelques questions. J'ai lu beaucoup de documentation mais jusqu'à présent je n'ai pas pu trouver de réponses suffisantes:
- Quelle est la différence entre
-
df.select("foo")
-
df.select($"foo")
-
- est-ce que je comprends bien que
-
myDataSet.map(foo.someVal)
est typesafe et ne se convertira pas enRDD
mais rester dans L'ensemble de données représentation / pas de charge supplémentaire (performance sage pour 2.0.0)
-
- toutes les autres commandes, p.ex. select,.. sont tout sucre syntaxique. Ils ne sont pas typesafe et une carte pourrait être utilisée à la place. Comment pourrais-je taper
df.select("foo")
sans un énoncé de carte?- Pourquoi devrais-je utiliser un UDF / UADF au lieu d'une carte (en supposant que la carte reste dans la représentation de l'ensemble de données)?
2 réponses
- la différence entre
df.select("foo")
etdf.select($"foo")
est une signature. Le premier prend au moins unString
, le second zéro ou plusColumns
. Il n'y a pas de différence pratique au-delà. -
myDataSet.map(foo.someVal)
contrôles de type, mais comme touteDataset
opération utiliseRDD
d'objets, et par rapport àDataFrame
opérations, Il ya un important au-dessus. Regardons un exemple simple: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]
comme vous pouvez le voir ce plan d'exécution nécessite l'accès à tous les champs et doit
DeserializeToObject
. -
Pas de. En général, d'autres méthodes ne sont pas sucre syntaxique et générer un plan d'exécution sensiblement différent. Par exemple:
ds.select($"foo").explain
== Physical Plan == LocalTableScan [foo#117]
par rapport au plan affiché avant qu'elle puisse accéder directement à la colonne. Il n'est pas si une limitation de L'API mais le résultat d'une différence dans la sémantique opérationnelle.
-
Comment ai-je pu df.sélectionnez ("foo") type-safe sans l'énoncé d'une carte?
il n'y a pas une telle option. Tandis que les colonnes dactylographiées vous permettent de transformer statiquement
Dataset
en un autre statiquement typéDataset
:ds.select($"bar".as[Int])
il n'y a pas de type sûr. Il y a quelques autres tente d'inclure des opérations optimisées de type sûr, comme agrégations typées , mais cette API expérimentale.
-
pourquoi utiliser UDF / UADF au lieu d'une carte
c'est entièrement à vous de décider. Chaque structure de données distribuées dans Spark fournit ses propres avantages et désavantages (voir par exemple UDAF Spark avec ArrayType comme bufferSchema performance les questions de ).
personnellement, je trouve statiquement dactylographié Dataset
pour être le moins utile:
-
"ne fournissent pas la même gamme d'optimisations que
Dataset[Row]
(bien qu'ils partagent le format de stockage et certaines optimisations de plan d'exécution, il ne bénéficie pas pleinement de la génération de code ou de stockage hors tas) ni l'accès à toutes les capacités analytiques duDataFrame
. -
les transformations dactylographiées sont des boîtes noires, et créent efficacement une barrière d'analyse pour l'optimiseur. Par exemple, les sélections (filtres) ne peuvent pas être poussées au-dessus de la transformation dactylographiée:
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]
par rapport à:
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]
Ceci a des répercussions sur des caractéristiques comme prédicat de refoulement ou de projection déroulant.
-
il n'y a pas aussi flexible comme
RDDs
avec seulement un petit sous-ensemble de types supportés nativement. - "Type de sécurité" avec
Encoders
est discutable lorsqueDataset
est converti à l'aide deas
la méthode. Comme la forme des données n'est pas encodée à l'aide d'une signature, un compilateur ne peut que vérifier l'existence d'unEncoder
.
questions connexes:
l'Étincelle Dataset
est beaucoup plus puissant que l'Étincelle Dataframe
. Petit exemple - vous ne pouvez créer Dataframe
de Row
, Tuple
ou n'importe quel type de données primitif mais Dataset
vous donne le pouvoir de créer Dataset
de n'importe quel type non primitif aussi. i.e. vous pouvez littéralement créer Dataset
de type d'objet.
Ex:
case class Employee(id:Int,name:String)
Dataset[Employee] // is valid
Dataframe[Employee] // is invalid