【问题标题】:Histogram -Doing it in a parallel way直方图 - 以并行方式进行
【发布时间】:2020-04-01 02:39:47
【问题描述】:
+----+----+--------+
| Id | M1 |  trx   |
+----+----+--------+
| 1  | M1 | 11.35  |
| 2  | M1 | 3.4    |
| 3  | M1 | 10.45  |
| 2  | M1 | 3.95   |
| 3  | M1 | 20.95  |
| 2  | M2 | 25.55  |
| 1  | M2 |  9.95  |
| 2  | M2 | 11.95  |
| 1  | M2 |  9.65  |
| 1  | M2 | 14.54  |
+----+----+--------+

使用上面的数据框,我应该能够使用下面的代码生成如下直方图。 Similar Queston is here

val (Range,counts) = df
.select(col("trx"))
.rdd.map(r => r.getDouble(0))
.histogram(10)
// Range: Array[Double] = Array(3.4, 5.615, 7.83, 10.045, 12.26, 14.475, 16.69, 18.905, 21.12, 23.335, 25.55)
// counts: Array[Long] = Array(2, 0, 2, 3, 0, 1, 0, 1, 0, 1) 

但这里的问题是,如何根据列“M1”并行创建直方图?这意味着我需要为列值 M1 和 M2 输出两个直方图。

【问题讨论】:

    标签: scala apache-spark


    【解决方案1】:

    首先,您需要知道histogram 会生成两个单独的顺序作业。一种用于检测数据的最小值和最大值,一种用于计算实际的直方图。您可以使用 Spark UI 进行检查。

    我们可以按照相同的方案在任意多的列上构建直方图,只需两个作业。然而,我们不能使用 histogram 函数,它只用于处理一个双精度集合。我们需要自己去实现。第一份工作非常简单。

    val Row(min_trx : Double, max_trx : Double) = df.select(min('trx), max('trx)).head
    

    然后我们在本地计算直方图的范围。请注意,我对所有列使用相同的范围。它允许在列之间轻松比较结果(通过将它们绘制在同一图上)。不过,每列有不同的范围只是对这段代码的一个小修改。

    val hist_size = 10
    val hist_step = (max_trx - min_trx) / hist_size
    val hist_ranges = (1 until hist_size)
        .scanLeft(min_trx)((a, _) => a + hist_step) :+ max_trx
    // I add max_trx manually to avoid rounding errors that would exclude the value
    

    这是第一部分。然后,我们可以使用 UDF 来确定每个值在什么范围内结束,并与 spark 并行计算所有直方图。

    val range_index = udf((x : Double) => hist_ranges.lastIndexWhere(x >= _))
    val hist_df = df
        .withColumn("rangeIndex", range_index('trx))
        .groupBy("M1", "rangeIndex")
        .count()
    // And voilà, all the data you need is there.
    hist_df.show()
    +---+----------+-----+
    | M1|rangeIndex|count|
    +---+----------+-----+
    | M2|         2|    2|
    | M1|         0|    2|
    | M2|         5|    1|
    | M1|         3|    2|
    | M2|         3|    1|
    | M1|         7|    1|
    | M2|        10|    1|
    +---+----------+-----+
    

    作为奖励,您可以使用 RDD API 或通过收集数据帧并在 scala 中修改数据来调整数据以在本地(在驱动程序中)使用它。

    这是使用 spark 的一种方法,因为这是关于 spark 的问题;-)

    val hist_map = hist_df.rdd
        .map(row => row.getAs[String]("M1") ->
                 (row.getAs[Int]("rangeIndex"), row.getAs[Long]("count")))
        .groupByKey
        .mapValues( _.toMap)
        .mapValues( hists => (1 to hist_size)
                        .map(i => hists.getOrElse(i, 0L)).toArray )
        .collectAsMap
    

    编辑:如何为每列值构建一个范围:

    我们不计算 M1 的最小值和最大值,而是为 groupBy 列的每个值计算它。

    val min_max_map = df.groupBy("M1")
        .agg(min('trx), max('trx))
        .rdd.map(row => row.getAs[String]("M1") ->
          (row.getAs[Double]("min(trx)"), row.getAs[Double]("max(trx)")))
        .collectAsMap // maps each column value to a tuple (min, max)
    

    然后我们调整 UDF 以便它使用这张地图,我们就完成了。

    // for clarity, let's define a function that generates histogram ranges
    def generate_ranges(min_trx : Double, max_trx : Double, hist_size : Int) = {
        val hist_step = (max_trx - min_trx) / hist_size
        (1 until hist_size).scanLeft(min_trx)((a, _) => a + hist_step) :+ max_trx
    }
    // and use it to generate one range per column value
    val range_map = min_max_map.keys
        .map(key => key ->
            generate_ranges(min_max_map(key)._1, min_max_map(key)._2, hist_size))
        .toMap
    
    val range_index = udf((x : Double, m1 : String) =>
                           range_map(m1).lastIndexWhere(x >= _))
    

    最后,只需将range_index('trx) 替换为range_index('trx, 'M1),每个列值将有一个范围。

    【讨论】:

    • 这个太棒了。但是任何方式来获取每列值“M1”的最大值和最小值,而不是获取整个列然后创建直方图。可能这里有些调整——val Row(min_trx : Double, max_trx : Double) = df.select(min('trx), max('trx)).head
    • 这意味着每组 M1 的范围索引都不同
    • 这可以通过df.groupBy("M1").agg(min('trx), max('trx)) 之类的方式实现。我会在答案中添加可能性。
    • 谢谢。但是我的代码似乎有一些错误。
    • 我编辑了答案,还有一些需要修改的地方。
    【解决方案2】:

    我用Spark做直方图的方法如下:

    val binEdes = 0.0 to 25.0 by 5.0
    val bins = binEdes.init.zip(binEdes.tail).toDF("bin_from","bin_to")
    
    df
      .join(bins,$"trx">=$"bin_from" and $"trx"<$"bin_to","right")
      .groupBy($"bin_from",$"bin_to")
      .agg(
        count($"trx").as("count")
       // add more, e.g. sum($"trx)
      )
      .orderBy($"bin_from",$"bin_to")
      .show()
    

    给予:

    +--------+------+-----+
    |bin_from|bin_to|count|
    +--------+------+-----+
    |     0.0|   5.0|    2|
    |     5.0|  10.0|    2|
    |    10.0|  15.0|    4|
    |    15.0|  20.0|    0|
    |    20.0|  25.0|    1|
    +--------+------+-----+
    

    现在,如果您有更多维度,只需将其添加到 groupBy-clause

    df
      .join(bins,$"trx">=$"bin_from" and $"trx"<$"bin_to","right")
      .groupBy($"M1",$"bin_from",$"bin_to")
      .agg(
        count($"trx").as("count")
      )
      .orderBy($"M1",$"bin_from",$"bin_to")
      .show()
    

    给予:

    +----+--------+------+-----+
    |  M1|bin_from|bin_to|count|
    +----+--------+------+-----+
    |null|    15.0|  20.0|    0|
    |  M1|     0.0|   5.0|    2|
    |  M1|    10.0|  15.0|    2|
    |  M1|    20.0|  25.0|    1|
    |  M2|     5.0|  10.0|    2|
    |  M2|    10.0|  15.0|    2|
    +----+--------+------+-----+
    

    您可以稍微调整一下代码以获得所需的输出,但这应该可以帮助您入门。您也可以使用我在此处发布的 UDAF 方法:Spark custom aggregation : collect_list+UDF vs UDAF

    【讨论】:

    • 感谢您的方法。这确实是一个好方法!但在我的情况下,删除空值可能很困难。我仍在试图弄清楚如何将 rdd.histogram() 函数用于多个 M1。我无法按 M1 进行分组,然后对其进行直方图。这才是真正的挑战!!!
    【解决方案3】:

    我认为使用 RDD 并不容易,因为 histogram 仅在 DoubleRDD 上可用,即 Double 的 RDD。如果你真的需要使用RDD API,你可以通过触发并行作业来并行执行,这可以使用 scalas 并行集合来完成:

    import scala.collection.parallel.immutable.ParSeq
    
    val List((rangeM1,histM1),(rangeM2,histM2)) = ParSeq("M1","M2")
      .map(c => df.where($"M1"===c)
      .select(col("trx"))
      .rdd.map(r => r.getDouble(0))
      .histogram(10)
    ).toList
    
    
    
    println(rangeM1.toSeq,histM1.toSeq)
    println(rangeM2.toSeq,histM2.toSeq)
    

    给予:

    (WrappedArray(3.4, 5.155, 6.91, 8.665000000000001, 10.42, 12.175, 13.930000000000001, 15.685, 17.44, 19.195, 20.95),WrappedArray(2, 0, 0, 0, 2, 0, 0, 0, 0, 1))
    (WrappedArray(9.65, 11.24, 12.83, 14.420000000000002, 16.01, 17.6, 19.19, 20.78, 22.37, 23.96, 25.55),WrappedArray(2, 1, 0, 1, 0, 0, 0, 0, 0, 1))
    

    请注意,此处 M1 和 M2 的 bin 不同

    【讨论】:

    • 这是一个很好的方法。但是有没有办法从这个格式中获取数据框。 M1,2, 0, 0, 0, 2, 0, 0, 0, 0, 1 和 M2,2, 1, 0, 1, 0, 0, 0, 0, 0, 1. 我不想要范围这里。这对我来说又是一个挑战......非常感谢任何帮助
    猜你喜欢
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多