Как наиболее эффективно преобразовать строку Scala DataFrame в класс case?
Как только я попал в Spark в некоторый класс Row, либо Dataframe, либо Catalyst, я хочу преобразовать его в класс case в моем коде. Это можно сделать, сопоставляя
someRow match {case Row(a:Long,b:String,c:Double) => myCaseClass(a,b,c)}
Но он становится уродливым, когда строка имеет огромное количество столбцов, например, дюжину парных разрядов, некоторые булевы и даже случайные нули.
Я хотел бы просто иметь возможность -позволить строку в myCaseClass. Возможно ли, или у меня есть самый экономичный синтаксис?
Ответы
Ответ 1
DataFrame - это просто псевдоним типа Dataset [Row]. Эти операции также называются "нетипизированными преобразованиями" в отличие от "типизированных преобразований", которые поставляются с строго типизированными наборами данных Scala/Java.
Преобразование из набора данных [Ряд] в набор данных [Лицо] очень просто в искры
val DFtoProcess = SQLContext.sql("SELECT * FROM peoples WHERE name='test'")
В этот момент Spark преобразует ваши данные в DataFrame = Dataset [Row] - коллекцию общего объекта Row, поскольку он не знает точного типа.
// Create an Encoders for Java class (In my eg. Person is a JAVA class)
// For scala case class you can pass Person without .class reference
val personEncoder = Encoders.bean(Person.class)
val DStoProcess = DFtoProcess.as[Person](personEncoder)
Теперь Spark преобразует объект Dataset[Row] -> Dataset[Person]
, специфичный для типа Scala/Java JVM, как это определено классом Person.
Подробнее см. ссылку ниже, предоставленную databricks.
https://databricks.com/blog/2016/07/14/a-tale-of-three-apache-spark-apis-rdds-dataframes-and-datasets.html
Ответ 2
Насколько я знаю, вы не можете использовать класс Row для класса case, но иногда я предпочитаю напрямую обращаться к строкам, например
map(row => myCaseClass(row.getLong(0), row.getString(1), row.getDouble(2))
Я считаю, что это проще, особенно если конструктор класса case нуждается только в некоторых из полей из строки.
Ответ 3
Конечно, вы можете сопоставить объект Row с классом case. Предположим, что ваш SchemaType имеет много полей, и вы хотите сопоставить некоторые из них в своем классе case.
Если у вас нет нулевых полей, вы можете просто:
case class MyClass(a: Long, b: String, c: Int, d: String, e: String)
dataframe.map {
case Row(a: java.math.BigDecimal,
b: String,
c: Int,
_: String,
_: java.sql.Date,
e: java.sql.Date,
_: java.sql.Timestamp,
_: java.sql.Timestamp,
_: java.math.BigDecimal,
_: String) => MyClass(a = a.longValue(), b = b, c = c, d = d.toString, e = e.toString)
}
Этот подход потерпит неудачу в случае нулевых значений, а также потребует явного определения типа каждого отдельного поля.
Если вам нужно обрабатывать нулевые значения, вам нужно либо отбросить все строки, содержащие нулевые значения, выполнив
dataframe.na.drop()
Это приведет к потере записей, даже если нулевые поля не используются в вашем шаблоне для вашего класса case.
Или, если вы хотите обработать его, вы можете повернуть объект Row в список, а затем использовать шаблон опций:
case class MyClass(a: Long, b: String, c: Option[Int], d: String, e: String)
dataframe.map(_.toSeq.toList match {
case List(a: java.math.BigDecimal,
b: String,
c: Int,
_: String,
_: java.sql.Date,
e: java.sql.Date,
_: java.sql.Timestamp,
_: java.sql.Timestamp,
_: java.math.BigDecimal,
_: String) => MyClass(a = a.longValue(), b = b, c = Option(c), d = d.toString, e = e.toString)
}
Проверьте этот проект gythub Sparkz(), который вскоре представит множество библиотек для упрощения API Spark и DataFrame и сделает их более функциональными, ориентированными на программирование.
Ответ 4
scala> val df = Seq((1, "james"), (2, "tony")).toDF("id", "name")
df: org.apache.spark.sql.DataFrame = [id: int, name: string]
scala> case class Student(id: Int, name: String)
defined class Student
scala> df.as[Student].collectAsList
res6: java.util.List[Student] = [Student(1,james), Student(2,tony)]