web-dev-qa-db-fra.com

Extraire `Seq [(String, String, String)]` de spark DataFrame

J'ai une étincelle DF avec des lignes de Seq[(String, String, String)]. J'essaie de faire une sorte de flatMap avec cela, mais tout ce que j'essaie finit par jeter

Exception Java.lang.ClassCast: org.Apache.spark.sql.catalyst.expressions.GenericRowWithSchema ne peut pas être convertie en scala.Tuple3

Je peux prendre une seule ligne ou plusieurs lignes du DF très bien

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

résultats

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

et le type de données du RDD semble correct.

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

Le schéma du df est

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)

Je sais que ce problème est lié au fait que spark SQL traite les lignes RDD comme org.Apache.spark.sql.Row, même si elles disent de manière idiote que c'est un Seq[(String, String, String)]. Il y a une question connexe (lien ci-dessous), mais la réponse à cette question ne me convient pas. Je ne suis pas non plus assez familier avec spark pour comprendre comment en faire une solution efficace.

Sont les lignes Row[Seq[(String, String, String)]] ou Row[(String, String, String)] ou Seq[Row[(String, String, String)]] ou quelque chose d'encore plus fou.

J'essaie de faire quelque chose comme

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

qui semble fonctionner mais ne fait pas

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

jette l'erreur ci-dessus. Alors, comment suis-je censé (par exemple) obtenir le premier élément du deuxième tuple sur chaque ligne?

AussiPOURQUOIa été conçu pour cela, il semble idiot de prétendre que quelque chose est d'un type alors qu'en fait ce n'est pas et ne peut pas être converti au type revendiqué.


Question associée: Exception GenericRowWithSchema dans le transtypage de ArrayBuffer vers HashSet dans DataFrame vers RDD à partir de la table Hive

Rapport de bug lié: http://search-hadoop.com/m/q3RTt2bvwy19Dxuq1&subj=ClassCastException+when+extracting+and+collecting+DF+array+column+type

11
Matti Lyra

Eh bien, il ne prétend pas que c'est un tuple. Il prétend qu'il s'agit d'une struct qui correspond à 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))

Vous trouverez des règles de mappage exactes dans le guide de programmation Spark SQL .

Puisque Row n'est pas exactement une jolie structure, vous voudrez probablement le mapper à quelque chose d'utile:

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

ou:

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

Enfin, une approche légèrement plus concise avec Datasets:

df.as[Record].rdd.map(_.content_processed)

ou 

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

bien que cela semble être légèrement buggy en ce moment.

Il existe une différence importante entre la première approche (Row.getAs) et la seconde (Dataset.as). Le premier extrait les objets sous la forme Any et applique asInstanceOf. Ce dernier utilise des encodeurs pour transformer les types internes en représentation souhaitée.

14
zero323
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)]
               )

Le code ci-dessus est un autre moyen de gérer la structure imbriquée. Spark default Encoder encodera TupleX, ce qui en fera une structure imbriquée, c’est pourquoi vous observez ce comportement étrange. et comme d’autres l’ont dit dans le commentaire, vous ne pouvez pas utiliser simplement getAs[T]() car il s’agit simplement d’un transtypage (x.asInstanceOf[T]); 

0
linehrr