Spark 2.0 DataSets groupByKey и деление операции и типа безопасности

Я очень доволен Spare 2.0 DataSets из-за его компиляции безопасности типа времени. Но вот пара проблем, которые я не могу решить, я также не нашел для этого хорошей документации.

Проблема № 1 - деление операции на агрегированном столбце - Рассмотрим ниже код - У меня есть DataSet [MyCaseClass], и я хотел бы groupByKey на c1, c2, c3 и sum (c4)/8. Приведенный ниже код работает хорошо, если я просто вычислил сумму, но он дает ошибку времени компиляции для деления (8). Интересно, как я могу добиться следующих результатов.

final case class MyClass (c1: String,
                          c2: String,
                          c3: String,
                          c4: Double)

    val myCaseClass: DataSet[MyCaseClass] = ??? // assume it being loaded

    import sparkSession.implicits._
    import org.apache.spark.sql.expressions.scalalang.typed.{sum => typedSum}

     myCaseClass.
       groupByKey(myCaseClass =>
          (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3)).
          agg(typedSum[MyCaseClass](_.c4).name("sum(c4)").

разделяй (8)).//это нарушение с исключением шоу()

Если я удаляю .divide(8) и выполняю команду выше, он дает мне ниже выход.

    +-----------+-------------+
    |        key|sum(c4)      |
    +-----------+-------------+
    |[A1,F2,S1]|         80.0|
    |[A1,F1,S1]|         40.0|  
    +-----------+-------------+

Проблема №2 - преобразование результата groupedByKey в другой типизированный DataFrame - Теперь вторая часть моей проблемы заключается в том, что я хочу снова выводить типизированный DataSet. Для этого у меня есть другой класс case (не уверен, что это необходимо), но я не уверен, как сопоставить сгруппированным результатом -

класс final case AnotherClass (c1: String,                             c2: Строка,                             c3: Строка,                             средний: двойной)

  myCaseClass.
           groupByKey(myCaseClass =>
              (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3)).
              agg(typedSum[MyCaseClass](_.c4).name("sum(c4)")).
as[AnotherClass] //this is breaking with exception

но это снова не выполняется с исключением, поскольку сгруппированный по результату ключа не напрямую сопоставляется с AnotherClass.

PS: любое другое решение для достижения выше приветствуется.

Ответ 1

Первая проблема может быть решена с помощью типизированных столбцов вплоть до конца (KeyValueGroupedDataset.agg ожидает TypedColumn(-s))  Вы можете определить результат агрегации как:

val eight = lit(8.0)
  .as[Double]  // Not necessary

val sumByEight = typedSum[MyClass](_.c4)
  .divide(eight)
  .as[Double]  // Required
  .name("div(sum(c4), 8)")

и подключите его к следующему коду:

val myCaseClass = Seq(
  MyClass("a", "b", "c", 2.0),
  MyClass("a", "b", "c", 3.0)
).toDS

myCaseClass
  .groupByKey(myCaseClass => (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3))
  .agg(sumByEight)

чтобы получить

+-------+---------------+
|    key|div(sum(c4), 8)|
+-------+---------------+
|[a,b,c]|          0.625|
+-------+---------------+

Вторая проблема - результат использования класса, который не соответствует форме данных. Правильное представление может быть:

case class AnotherClass(key: (String, String, String), sum: Double)

который используется с данными, определенными выше:

 myCaseClass
   .groupByKey(myCaseClass => (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3))
   .agg(typedSum[MyClass](_.c4).name("sum"))
   .as[AnotherClass]

даст:

+-------+---+
|    key|sum|
+-------+---+
|[a,b,c]|5.0|
+-------+---+

но .as[AnotherClass] здесь не требуется, если Dataset[((String, String, String), Double)] является приемлемым.

Конечно, вы можете пропустить все это и просто mapGroups (хотя и не без снижения производительности):

import shapeless.syntax.std.tuple._   // A little bit of shapeless

val tuples = myCaseClass
 .groupByKey(myCaseClass => (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3))
 .mapGroups((group, iter) => group :+ iter.map(_.c4).sum)

с результатом

+---+---+---+---+   
| _1| _2| _3| _4|
+---+---+---+---+
|  a|  b|  c|5.0|
+---+---+---+---+

reduceGroups может быть лучшим вариантом:

myCaseClass
  .groupByKey(myCaseClass => (myCaseClass.c1, myCaseClass.c2, myCaseClass.c3))
  .reduceGroups((x, y) => x.copy(c4=x.c4 + y.c4))

с результирующим Dataset:

+-------+-----------+    
|     _1|         _2|
+-------+-----------+
|[a,b,c]|[a,b,c,5.0]|
+-------+-----------+