在Spark数据集中创建具有运行总计的列

时间:2017-10-05 00:16:12

标签: apache-spark spark-dataframe rdd apache-spark-dataset

假设我们有一个包含两列的Spark数据集,比如Index和Value,按第一列(Index)排序。

((1, 100), (2, 110), (3, 90), ...)

我们希望数据集包含第三列,其中第二列中的值总计为(值)。

((1, 100, 100), (2, 110, 210), (3, 90, 300), ...)

如何有效地完成这项工作,一次通过数据?或者是否有可以用于此的固定CDF类型功能?

如果需要,可以将数据集转换为Dataframe或RDD来完成任务,但它必须保持分布式数据结构。也就是说,它不能简单地收集并转换为数组或序列,并且不使用可变变量(仅val,不var)。

2 个答案:

答案 0 :(得分:1)

  

但它必须保持分布式数据结构。

不幸的是,你所说的你想要做的事情在Spark中是不可能的。如果您愿意将数据集重新分区到单个分区(实际上将其整合到单个主机上),您可以轻松编写一个函数来执行您希望的操作,并将增量值保持为字段。

由于Spark函数在执行时不会在网络上共享状态,因此无法创建保持数据集完全分布所需的共享状态。

如果您愿意放宽您的要求并允许在一台主机上一次性合并和读取数据,那么您可以通过重新分区到单个分区并应用函数来执行您希望的操作。这不会将数据提取到驱动程序(将其保留在HDFS /群集中),但仍会在单个执行程序上串行计算输出。例如:

package com.github.nevernaptitsa

import java.io.Serializable
import java.util

import org.apache.spark.sql.{Encoders, SparkSession}

object SparkTest {

  class RunningSum extends Function[Int, Tuple2[Int, Int]] with Serializable {
    private var runningSum = 0
    override def apply(v1: Int): Tuple2[Int, Int] = {
      runningSum+=v1
      return (v1, runningSum)
    }
  }

  def main(args: Array[String]): Unit ={
    val session = SparkSession.builder()
      .appName("runningSumTest")
      .master("local[*]")
      .getOrCreate()
    import session.implicits._
    session.createDataset(Seq(1,2,3,4,5))
      .repartition(1)
      .map(new RunningSum)
      .show(5)
    session.createDataset(Seq(1,2,3,4,5))
      .map(new RunningSum)
      .show(5)
  }

}

这里的两个语句显示不同的输出,第一个提供正确的输出(串行,因为repartition(1)被调用),第二个提供错误的输出,因为结果是并行计算的。

第一声明的结果:

+---+---+
| _1| _2|
+---+---+
|  1|  1|
|  2|  3|
|  3|  6|
|  4| 10|
|  5| 15|
+---+---+

第二项陈述的结果:

+---+---+
| _1| _2|
+---+---+
|  1|  1|
|  2|  2|
|  3|  3|
|  4|  4|
|  5|  9|
+---+---+

答案 1 :(得分:0)

一位同事建议以下内容依赖于RDD.mapPartitionsWithIndex()方法。 (据我所知,其他数据结构不提供对其分区索引的这种引用。)

val data = sc.parallelize((1 to 5))  // sc is the SparkContext
val partialSums = data.mapPartitionsWithIndex{ (i, values) => 
    Iterator((i, values.sum))
}.collect().toMap  // will in general have size other than data.count
val cumSums = data.mapPartitionsWithIndex{ (i, values) => 
    val prevSums = (0 until i).map(partialSums).sum
    values.scanLeft(prevSums)(_+_).drop(1)
}