我有一个行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
答案 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]
),因此会给您运行时异常。