从spark DataFrame

时间:2016-05-31 18:25:49

标签: scala apache-spark dataframe apache-spark-sql

我有一个行Seq[(String, String, String)]的火花DF。我正试图做某种flatMap,但我做的任何尝试最终都会抛出

  

java.lang.ClassCastException:org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema无法强制转换为scala.Tuple3

我可以从DF中获取单行或多行

df.map{ r => r.getSeq[Feature](1)}.first

返回

Seq[(String, String, String)] = WrappedArray([ancient,jj,o], [olympia_greece,nn,location] .....

并且RDD的数据类型似乎是正确的。

org.apache.spark.rdd.RDD[Seq[(String, String, String)]]

df的架构是

root
 |-- article_id: long (nullable = true)
 |-- content_processed: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- lemma: string (nullable = true)
 |    |    |-- pos_tag: string (nullable = true)
 |    |    |-- ne_tag: string (nullable = true)

我知道这个问题与spark sql将RDD行视为org.apache.spark.sql.Row有关,即使他们愚蠢地说它是Seq[(String, String, String)]。有一个相关的问题(链接如下),但这个问题的答案对我不起作用。我也不熟悉火花来弄清楚如何把它变成一个可行的解决方案。

Row[Seq[(String, String, String)]]Row[(String, String, String)]Seq[Row[(String, String, String)]]或某些内容是否更加疯狂。

我正在尝试做类似

的事情
df.map{ r => r.getSeq[Feature](1)}.map(_(1)._1)

似乎有效,但实际上并没有

df.map{ r => r.getSeq[Feature](1)}.map(_(1)._1).first

抛出上述错误。那么我应该如何(例如)在每一行获得第二个元组的第一个元素?

同样为什么已经设计了这样做的火花,声称事物属于一种类型似乎是愚蠢的,而实际上它不是也不能转换为声称的类型。

相关问题:GenericRowWithSchema exception in casting ArrayBuffer to HashSet in DataFrame to RDD from Hive table

相关错误报告:http://search-hadoop.com/m/q3RTt2bvwy19Dxuq1&subj=ClassCastException+when+extracting+and+collecting+DF+array+column+type

2 个答案:

答案 0 :(得分:16)

嗯,它没有声称它是一个元组。它声称它是struct,映射到Row

import org.apache.spark.sql.Row

case class Feature(lemma: String, pos_tag: String, ne_tag: String)
case class Record(id: Long, content_processed: Seq[Feature])

val df = Seq(
  Record(1L, Seq(
    Feature("ancient", "jj", "o"),
    Feature("olympia_greece", "nn", "location")
  ))
).toDF

val content = df.select($"content_processed").rdd.map(_.getSeq[Row](0))

您可以在Spark SQL programming guide中找到确切的映射规则。

由于Row不是非常漂亮的结构,您可能希望将其映射到有用的东西:

content.map(_.map {
  case Row(lemma: String, pos_tag: String, ne_tag: String) => 
    (lemma, pos_tag, ne_tag)
})

或:

content.map(_.map ( row => (
  row.getAs[String]("lemma"),
  row.getAs[String]("pos_tag"),
  row.getAs[String]("ne_tag")
)))

最后使用Datasets

进行更简洁的方法
df.as[Record].rdd.map(_.content_processed)

df.select($"content_processed").as[Seq[(String, String, String)]]

虽然现在看起来似乎有些麻烦。

第一种方法(Row.getAs)和第二种方法(Dataset.as)有重要区别。前一个提取对象为Any并应用asInstanceOf。后者使用编码器在内部类型和所需表示之间进行转换。

答案 1 :(得分:0)

object ListSerdeTest extends App {

  implicit val spark: SparkSession = SparkSession
    .builder
    .master("local[2]")
    .getOrCreate()


  import spark.implicits._
  val myDS = spark.createDataset(
    Seq(
      MyCaseClass(mylist = Array(("asd", "aa"), ("dd", "ee")))
    )
  )

  myDS.toDF().printSchema()

  myDS.toDF().foreach(
    row => {
      row.getSeq[Row](row.fieldIndex("mylist"))
        .foreach {
          case Row(a, b) => println(a, b)
        }
    }
  )
}

case class MyCaseClass (
                 mylist: Seq[(String, String)]
               )

以上代码是处理嵌套结构的另一种方法。 Spark默认的编码器将对TupleX进行编码,使其成为嵌套结构,这就是为什么您会看到这种奇怪的行为。就像其他人在评论中说的那样,您不能只做getAs[T](),因为它只是强制转换(x.asInstanceOf[T]),因此会给您运行时异常。