Apache spark 如何在ApacheSpark中计算百分位数

Apache spark 如何在ApacheSpark中计算百分位数,apache-spark,Apache Spark,我有一个整数的rdd(即,rdd[Int]),我想做的是计算以下十个百分位数:[0th,10th,20th,…,90th,100th]。最有效的方法是什么?将RDD转换为双精度RDD,然后使用.histogram(10)操作。请参见,您可以: 通过rdd.sortBy()对数据集进行排序 通过rdd.count()计算数据集的大小 带有索引的Zip,便于百分位检索 通过rdd.lookup()检索所需的百分比,例如,对于第10个百分比rdd.lookup(0.1*大小) 要计算中位数和第99百分

我有一个整数的rdd(即,
rdd[Int]
),我想做的是计算以下十个百分位数:
[0th,10th,20th,…,90th,100th]
。最有效的方法是什么?

将RDD转换为双精度RDD,然后使用
.histogram(10)
操作。请参见,您可以:

  • 通过rdd.sortBy()对数据集进行排序
  • 通过rdd.count()计算数据集的大小
  • 带有索引的Zip,便于百分位检索
  • 通过rdd.lookup()检索所需的百分比,例如,对于第10个百分比rdd.lookup(0.1*大小)
  • 要计算中位数和第99百分位: getPercentiles(rdd,新双[]{0.5,0.99},大小,百分比)

    在Java 8中:

    publicstaticdouble[]getPercentiles(JavaRDD-rdd,double[]percentiles,long-rddSize,int-numPartitions){
    双[]值=新的双[百分位数.长度];
    JavaRDD sorted=rdd.sortBy((双d)->d,true,numPartitions);
    javapairdd index=sorted.zipWithIndex().mapToPair((tuple2t)->t.swap());
    对于(int i=0;i
    请注意,这需要对数据集O(n.log(n))进行排序,并且在大型数据集上可能会很昂贵


    另一个答案是仅仅计算直方图无法正确计算百分位数:这里有一个反例:一个数据集由100个数字组成,99个数字为0,一个数字为1。最后一个bin中的99个0个,最后一个bin中的1个,中间有8个空的容器。

    < P>另一个替代方法可以是使用Rtop和Roud的双RDD。例如,val percentile_99th_value=scores.top((count/100).toInt).last


    这个方法更适合于单个百分位数。

    我发现了这个要点

    它包含以下函数:

      /**
       * compute percentile from an unsorted Spark RDD
       * @param data: input data set of Long integers
       * @param tile: percentile to compute (eg. 85 percentile)
       * @return value of input data at the specified percentile
       */
      def computePercentile(data: RDD[Long], tile: Double): Double = {
        // NIST method; data to be sorted in ascending order
        val r = data.sortBy(x => x)
        val c = r.count()
        if (c == 1) r.first()
        else {
          val n = (tile / 100d) * (c + 1d)
          val k = math.floor(n).toLong
          val d = n - k
          if (k <= 0) r.first()
          else {
            val index = r.zipWithIndex().map(_.swap)
            val last = c
            if (k >= c) {
              index.lookup(last - 1).head
            } else {
              index.lookup(k - 1).head + d * (index.lookup(k).head - index.lookup(k - 1).head)
            }
          }
        }
      }
    
    /**
    *从未排序的Spark RDD计算百分位
    *@param data:长整数的输入数据集
    *@param-tile:要计算的百分位(例如85个百分位)
    *@指定百分比处输入数据的返回值
    */
    def computePercentile(数据:RDD[Long],分幅:Double):Double={
    //NIST方法;数据按升序排序
    val r=data.sortBy(x=>x)
    val c=r.计数()
    如果(c==1)r.first()
    否则{
    val n=(瓷砖/100d)*(c+1d)
    val k=托隆(北)数学层
    val d=n-k
    if(k=c){
    查找(最后一个-1).head
    }否则{
    index.lookup(k-1).head+d*(index.lookup(k).head-index.lookup(k-1).head)
    }
    }
    }
    }
    
    这是我在Spark上的Python实现,用于计算包含感兴趣值的RDD的百分比

    def percentile_threshold(ardd, percentile):
        assert percentile > 0 and percentile <= 100, "percentile should be larger then 0 and smaller or equal to 100"
    
        return ardd.sortBy(lambda x: x).zipWithIndex().map(lambda x: (x[1], x[0])) \
                .lookup(np.ceil(ardd.count() / 100 * percentile - 1))[0]
    
    # Now test it out
    import numpy as np
    randlist = range(1,10001)
    np.random.shuffle(randlist)
    ardd = sc.parallelize(randlist)
    
    print percentile_threshold(ardd,0.001)
    print percentile_threshold(ardd,1)
    print percentile_threshold(ardd,60.11)
    print percentile_threshold(ardd,99)
    print percentile_threshold(ardd,99.999)
    print percentile_threshold(ardd,100)
    
    # output:
    # 1
    # 100
    # 6011
    # 9900
    # 10000
    # 10000
    

    t-digest怎么样

    一种新的数据结构,用于精确在线累积基于秩的统计数据,如分位数和修剪平均数。t-digest算法也是非常并行友好的,这使得它在map reduce和并行流应用程序中非常有用

    t-摘要构造算法使用一维k-均值聚类的变体来生成与Q-摘要相关的数据结构。此t摘要数据结构可用于估计分位数或计算其他秩统计。与Q摘要相比,t摘要的优势在于,t摘要可以处理浮点值,而Q摘要仅限于整数。只要稍作改动,t-digest就可以处理任何有序集合中类似于均值的任何值。尽管t-digests存储在磁盘上时更紧凑,但t-digests生成的分位数估计的精度比Q-digests生成的分位数估计的精度高出几个数量级

    总之,t-digest特别有趣的特点是

    • 比Q-digest有更小的摘要
    • 适用于双精度和整数
    • 提供极端分位数的百万分之一精度,通常为90%的测试覆盖率
    • 可以很容易地与map reduce一起使用,因为可以合并摘要

    使用Spark的参考Java实现应该相当容易。

    如果您不介意将RDD转换为数据帧,并使用Hive UDAF,您可以使用。假设您已将HiveContext HiveContext加载到范围中:

    hiveContext.sql(“从您的数据框中选择百分位(x,数组(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9))


    我在

    中发现了这个蜂巢UDAF,如果N%很小,比如10,20%,那么我将执行以下操作:

  • 计算数据集的大小rdd.count(),跳过它,可能您已经知道它,并将其作为参数

  • 我将从每个分区中找出top(N),而不是对整个数据集进行排序。为此,我必须找出N=rdd.count的N%是多少,然后对分区进行排序,并从每个分区中取顶部(N)。现在,要排序的数据集要小得多

  • 3.rdd.sortBy

    4.zipWithIndex


    5.filter(index根据这里给出的答案,我使用UDAF计算spark窗口(spark 2.1)上的百分位数:

    首先是用于其他聚合的抽象泛型UDAF

    import org.apache.spark.sql.Row
    import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
    import org.apache.spark.sql.types._
    
    import scala.collection.mutable
    import scala.collection.mutable.ArrayBuffer
    
    
    abstract class GenericUDAF extends UserDefinedAggregateFunction {
    
      def inputSchema: StructType =
        StructType(StructField("value", DoubleType) :: Nil)
    
      def bufferSchema: StructType = StructType(
        StructField("window_list", ArrayType(DoubleType, false)) :: Nil
      )
    
      def deterministic: Boolean = true
    
      def initialize(buffer: MutableAggregationBuffer): Unit = {
        buffer(0) = new ArrayBuffer[Double]()
      }
    
      def update(buffer: MutableAggregationBuffer,input: org.apache.spark.sql.Row): Unit = {
        var bufferVal = buffer.getAs[mutable.WrappedArray[Double]](0).toBuffer
        bufferVal+=input.getAs[Double](0)
        buffer(0) = bufferVal
      }
    
      def merge(buffer1: MutableAggregationBuffer, buffer2: org.apache.spark.sql.Row): Unit = {
        buffer1(0) = buffer1.getAs[ArrayBuffer[Double]](0) ++ buffer2.getAs[ArrayBuffer[Double]](0)
      }
    
      def dataType: DataType
      def evaluate(buffer: Row): Any
    
    }
    
    然后,为十分位数定制的百分位UDAF:

    import org.apache.spark.sql.Row
    import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
    import org.apache.spark.sql.types._
    
    import scala.collection.mutable
    import scala.collection.mutable.ArrayBuffer
    
    
    class DecilesUDAF extends GenericUDAF {
    
      override def dataType: DataType = ArrayType(DoubleType, false)
    
      override def evaluate(buffer: Row): Any = {
        val sortedWindow = buffer.getAs[mutable.WrappedArray[Double]](0).sorted.toBuffer
        val windowSize = sortedWindow.size
        if (windowSize == 0) return null
        if (windowSize == 1) return (0 to 10).map(_ => sortedWindow.head).toArray
    
        (0 to 10).map(i => sortedWindow(Math.min(windowSize-1, i*windowSize/10))).toArray
    
      }
    }
    
    然后通过分区有序窗口实例化和调用UDAF:

    val deciles = new DecilesUDAF()
    df.withColumn("mt_deciles", deciles(col("mt")).over(myWindow))
    
    然后,可以使用getItem将结果数组拆分为多列:

    def splitToColumns(size: Int, splitCol:String)(df: DataFrame) = {
      (0 to size).foldLeft(df) {
        case (df_arg, i) => df_arg.withColumn("mt_decile_"+i, col(splitCol).getItem(i))
      }
    }
    
    df.transform(splitToColumns(10, "mt_deciles" ))
    
    UDAF比本机spark函数慢,但只要每个分组包或每个窗口相对较小且适合单个执行器,就可以了。主要优点是使用spark并行。 不费吹灰之力,这个代码就可以扩展到n个分位数

    我使用这个函数测试了代码:

    def testDecilesUDAF = {
        val window = W.partitionBy("user")
        val deciles = new DecilesUDAF()
    
        val schema = StructType(StructField("mt", DoubleType) :: StructField("user", StringType) :: Nil)
    
        val rows1 = (1 to 20).map(i => Row(i.toDouble, "a"))
        val rows2 = (21 to 40).map(i => Row(i.toDouble, "b"))
    
        val df = spark.createDataFrame(spark.sparkContext.makeRDD[Row](rows1++rows2), schema)
    
        df.withColumn("deciles", deciles(col("mt")).over(window))
          .transform(splitToColumns(10, "deciles" ))
          .drop("deciles")
          .show(100, truncate=false)
      }
    
    前3升
    def testDecilesUDAF = {
        val window = W.partitionBy("user")
        val deciles = new DecilesUDAF()
    
        val schema = StructType(StructField("mt", DoubleType) :: StructField("user", StringType) :: Nil)
    
        val rows1 = (1 to 20).map(i => Row(i.toDouble, "a"))
        val rows2 = (21 to 40).map(i => Row(i.toDouble, "b"))
    
        val df = spark.createDataFrame(spark.sparkContext.makeRDD[Row](rows1++rows2), schema)
    
        df.withColumn("deciles", deciles(col("mt")).over(window))
          .transform(splitToColumns(10, "deciles" ))
          .drop("deciles")
          .show(100, truncate=false)
      }
    
    +----+----+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+------------+
    |mt  |user|mt_decile_0|mt_decile_1|mt_decile_2|mt_decile_3|mt_decile_4|mt_decile_5|mt_decile_6|mt_decile_7|mt_decile_8|mt_decile_9|mt_decile_10|
    +----+----+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+------------+
    |21.0|b   |21.0       |23.0       |25.0       |27.0       |29.0       |31.0       |33.0       |35.0       |37.0       |39.0       |40.0        |
    |22.0|b   |21.0       |23.0       |25.0       |27.0       |29.0       |31.0       |33.0       |35.0       |37.0       |39.0       |40.0        |
    |23.0|b   |21.0       |23.0       |25.0       |27.0       |29.0       |31.0       |33.0       |35.0       |37.0       |39.0       |40.0        |
    
    val percentiles = Array(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1)
    val accuracy = 1000000
    df.stat.approxQuantile("score", percentiles, 1.0/accuracy)
    
    scala> df.stat.approxQuantile("score", percentiles, 1.0/accuracy)
    res88: Array[Double] = Array(0.011044141836464405, 0.02022990956902504, 0.0317261666059494, 0.04638145491480827, 0.06498630344867706, 0.0892181545495987, 0.12161539494991302, 0.16825592517852783, 0.24740923941135406, 0.9188197255134583)