我有一个整数rdd(即RDD[Int]
),我想要做的是计算以下十个百分位:[0th, 10th, 20th, ..., 90th, 100th]
。最有效的方法是什么?
答案 0 :(得分:21)
你可以:
计算中位数和第99百分位数: getPercentiles(rdd,new double [] {0.5,0.99},size,numPartitions);
在Java 8中:
public static double[] getPercentiles(JavaRDD<Double> rdd, double[] percentiles, long rddSize, int numPartitions) {
double[] values = new double[percentiles.length];
JavaRDD<Double> sorted = rdd.sortBy((Double d) -> d, true, numPartitions);
JavaPairRDD<Long, Double> indexed = sorted.zipWithIndex().mapToPair((Tuple2<Double, Long> t) -> t.swap());
for (int i = 0; i < percentiles.length; i++) {
double percentile = percentiles[i];
long id = (long) (rddSize * percentile);
values[i] = indexed.lookup(id).get(0);
}
return values;
}
请注意,这需要对数据集O(n.log(n))进行排序,并且在大型数据集上可能会很昂贵。
另一个答案表明,简单地计算直方图不会正确计算百分位数:这里是一个反例:一个由100个数字组成的数据集,99个数字为0,一个数字为1.你最终得到了所有99个0在第一个箱子里,最后一个箱子里的1个,中间有8个空箱子。
答案 1 :(得分:6)
t-digest 怎么样?
https://github.com/tdunning/t-digest
一种新的数据结构,用于精确在线累积基于排名的统计数据,例如分位数和修剪均值。 t-digest算法也非常友好并行,使其在地图缩减和并行流应用程序中很有用。
t-digest构造算法使用一维k均值聚类的变体来产生与Q-摘要相关的数据结构。该t-摘要数据结构可用于估计分位数或计算其他秩统计数据。 t-digest相对于Q-digest的优点是t-digest可以处理浮点值,而Q-digest仅限于整数。通过较小的更改,t-digest可以处理任何具有类似于均值的有序集合中的任何值。尽管t-digest存储在磁盘上时t-digest更紧凑,但是t-digest产生的分位数估计的准确性可以比Q-digest产生的精度高几个数量级。
总之,t-digest特别有趣的特征是它
- 的摘要小于Q-digest
- 适用于双打和整数。
- 为极端分位数提供百万分之一的精度,对于中等分位数通常<1000 ppm精度
- 很快
- 非常简单
- 的参考实现具有&gt; 90%的测试覆盖率
- 可以很容易地与map-reduce一起使用,因为摘要可以合并
使用Spark的参考Java实现应该相当容易。
答案 2 :(得分:4)
我发现了这个要点
https://gist.github.com/felixcheung/92ae74bc349ea83a9e29
包含以下功能:
/**
* 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)
}
}
}
}
答案 3 :(得分:3)
这是我在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
另外,我定义了以下函数来获得第10到第100百分位数。
def get_percentiles(rdd, stepsize=10):
percentiles = []
rddcount100 = rdd.count() / 100
sortedrdd = ardd.sortBy(lambda x: x).zipWithIndex().map(lambda x: (x[1], x[0]))
for p in range(0, 101, stepsize):
if p == 0:
pass
# I am not aware of a formal definition of 0 percentile,
# you can put a place holder like this if you want
# percentiles.append(sortedrdd.lookup(0)[0] - 1)
elif p == 100:
percentiles.append(sortedrdd.lookup(np.ceil(rddcount100 * 100 - 1))[0])
else:
pv = sortedrdd.lookup(np.ceil(rddcount100 * p) - 1)[0]
percentiles.append(pv)
return percentiles
randlist = range(1,10001)
np.random.shuffle(randlist)
ardd = sc.parallelize(randlist)
get_percentiles(ardd, 10)
# [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
答案 4 :(得分:3)
如果您不介意将RDD转换为DataFrame并使用Hive UDAF,则可以使用percentile。假设您已将HiveContext hiveContext 加载到范围内:
hiveContext.sql("SELECT percentile(x, array(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9)) FROM yourDataFrame")
答案 5 :(得分:1)
将RDD转换为Double的RDD,然后使用.histogram(10)
操作。见DoubleRDD ScalaDoc
答案 6 :(得分:1)
如果N%小,如10%,20%那么我会做以下事情:
计算数据集的大小rdd.count(),跳过它可能你已经知道并作为参数。
而不是对整个数据集进行排序,我会从每个分区中找到顶部(N)。为此,我必须找出N =什么是rdd.count的N%,然后对分区进行排序并从每个分区取顶部(N)。现在您有一个小得多的数据集要进行排序。
3.rdd.sortBy
4.zipWithIndex
5.filter(index&lt; topN)
答案 7 :(得分:0)
另一种替代方法是使用top和RDD of double。例如,val percentile_99th_value = scores.top((count / 100).toInt).last
此方法更适合个人百分位数。
答案 8 :(得分:0)
基于此处Median UDAF in Spark/Scala给出的答案,我使用了UDAF来计算火花窗口(火花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函数要慢,但是只要每个分组的包或每个窗口相对较小并且适合单个执行程序,就可以了。主要优点是使用火花并行性。 毫不费力地,此代码可以扩展到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)
}
输出的前三行:
+----+----+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+------------+
|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 |
答案 9 :(得分: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)
精度:精度参数(默认值:10000)是一个正数值文字,它以存储器为代价控制近似精度。精度值越高,精度越好,近似值的相对误差为1.0 /精度。