尝试将数据帧行映射到更新行时出现编码器错误

时间:2016-09-11 06:21:39

标签: scala apache-spark apache-spark-sql apache-spark-dataset apache-spark-encoders

当我尝试在我的代码中执行相同的操作时,如下所述

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

我从这里采取了上述参考: Scala: How can I replace value in Dataframs using scala 但我收到编码器错误

  

无法找到存储在数据集中的类型的编码器。原始类型   (Int,S tring等)和产品类型(案例类)受支持   导入spark.im plicits._支持序列化其他类型   在将来的版本中添加。

注意:我正在使用spark 2.0!

4 个答案:

答案 0 :(得分:64)

这里没有任何意外。您正在尝试使用已使用Spark 1.x编写且Spark 2.0中不再支持的代码:

    1. li DataFrame.map中的
  • ((Row) ⇒ T)(ClassTag[T]) ⇒ RDD[T]
  • 2.x Dataset[Row].map中的
  • ((Row) ⇒ T)(Encoder[T]) ⇒ Dataset[T]

说实话,它在1.x中也没有多大意义。与版本无关,您只需使用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"))

如果你真的想使用map,你应该使用静态类型Dataset

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
}

或者至少返回一个具有隐式编码器的对象:

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

最后,如果出于某些完全疯狂的原因,您确实希望映射Dataset[Row],则必须提供所需的编码器:

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)

答案 1 :(得分:5)

对于预先知道数据帧模式的情况,@ zero323给出的答案是解决方案

但是对于具有动态架构/或将多个数据帧传递给泛型函数的场景: 以下代码对我们有用,同时从1.6.0从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))
})

此代码在spark的两个版本上执行。

缺点:提供优化 通过数据帧/数据集上的spark,不会应用api。

答案 2 :(得分:0)

在我的spark 2.4.4版本中,我必须导入隐式。这是一个普遍的答案

val spark2 = spark
import spark2.implicits._

val data = df.rdd.map(row => my_func(row))

my_func进行了一些操作。

答案 3 :(得分:0)

只需添加一些其他重要的知识点,即可很好地理解其他答案(特别是@ zero323关于 map的答案的最终点 {1}} ):

  • 首先,Dataset[Row]给您一个Dataframe.map(更具体地说,是Dataset,而不是Dataset[T])!
  • Dataset[Row]始终需要编码器,这就是“ Dataset[T]Dataset[Row].map ”这句话的意思。
  • Spark确实确实有lots of encoders predefined(可以通过进行((Row) ⇒ T)(Encoder[T]) ⇒ Dataset[T] import来实现),但是该列表仍无法涵盖开发人员可能会使用的许多特定于域的类型创建,则需要create encoders yourself
  • 在此页面上的特定示例中,import spark.implicits._返回df.map的{​​{1}}类型,并稍等片刻,Row类型不在类型列表中具有由Spark预定义的编码器,因此您将自己创建一个。
  • 我承认为Dataset类型创建编码器与above link中描述的方法有些不同,并且您必须使用Row,它需要Row作为描述行类型的参数,例如@ zero323在上面提供的内容:
RowEncoder