web-dev-qa-db-fra.com

Erreur du codeur lors de la tentative de mappage de la ligne dataframe sur la ligne mise à jour

Quand j'essaie de faire la même chose dans mon code comme mentionné ci-dessous

dataframe.map(row => {
  val row1 = row.getAs[String](1)
  val make = if (row1.toLowerCase == "tesla") "S" else row1
  Row(row(0),make,row(2))
})

J'ai pris la référence ci-dessus à partir d'ici: Scala: Comment puis-je remplacer une valeur dans Dataframs à l'aide de scala Mais j'obtiens une erreur de codeur

Impossible de trouver le codeur pour le type stocké dans un jeu de données. Les types primitifs (Int, S tring, etc.) et les types de produit (classes de cas) sont pris en charge lors de l'importation de demandes spark.im._ La prise en charge de la sérialisation d'autres types sera ajoutée dans les prochaines versions.

Note: J'utilise spark 2.0!

33
Advika

Il n'y a rien d'inattendu ici. Vous essayez d'utiliser un code qui a été écrit avec Spark 1.x et n'est plus pris en charge dans Spark 2.0:

  • dans 1.x DataFrame.map est ((Row) ⇒ T)(ClassTag[T]) ⇒ RDD[T]
  • dans 2.x Dataset[Row].map est ((Row) ⇒ T)(Encoder[T]) ⇒ Dataset[T]

Pour être honnête, cela n'avait pas beaucoup de sens dans 1.x non plus. Indépendamment de la version, vous pouvez simplement utiliser DataFrame API:

import org.Apache.spark.sql.functions.{when, lower}

val df = Seq(
  (2012, "Tesla", "S"), (1997, "Ford", "E350"),
  (2015, "Chevy", "Volt")
).toDF("year", "make", "model")

df.withColumn("make", when(lower($"make") === "tesla", "S").otherwise($"make"))

Si vous voulez vraiment utiliser map, vous devez utiliser Dataset de manière statique:

import spark.implicits._

case class Record(year: Int, make: String, model: String)

df.as[Record].map {
  case tesla if tesla.make.toLowerCase == "tesla" => tesla.copy(make = "S")
  case rec => rec
}

ou au moins retourner un objet qui aura un encodeur implicite:

df.map {
  case Row(year: Int, make: String, model: String) => 
    (year, if(make.toLowerCase == "tesla") "S" else make, model)
}

Enfin si pour certains complètement fo raison que vous voulez vraiment mapper sur Dataset[Row] vous devez fournir le codeur requis:

import org.Apache.spark.sql.catalyst.encoders.RowEncoder
import org.Apache.spark.sql.types._
import org.Apache.spark.sql.Row

// Yup, it would be possible to reuse df.schema here
val schema = StructType(Seq(
  StructField("year", IntegerType),
  StructField("make", StringType),
  StructField("model", StringType)
))

val encoder = RowEncoder(schema)

df.map {
  case Row(year, make: String, model) if make.toLowerCase == "tesla" => 
    Row(year, "S", model)
  case row => row
} (encoder)
69
zero323

Pour le scénario où le schéma de la structure de données est connu à l'avance, la solution donnée par @ zero323 est la solution

mais pour les scénarios avec schéma dynamique/ou transfert de plusieurs images par données à une fonction générique: Le code suivant a fonctionné pour nous lors de la migration de 1.6.1 de 2.2.0

import org.Apache.spark.sql.Row

val df = Seq(
   (2012, "Tesla", "S"), (1997, "Ford", "E350"),
   (2015, "Chevy", "Volt")
 ).toDF("year", "make", "model")

val data = df.rdd.map(row => {
  val row1 = row.getAs[String](1)
  val make = if (row1.toLowerCase == "tesla") "S" else row1
  Row(row(0),make,row(2))
})

ce code s'exécute sur les deux versions de spark.

inconvénient: l'optimisation fournie par spark sur les cadres de données/ensembles de données api ne sera pas appliquée.

5
PoojanKothari