首先简单介绍下百分位数,我们称位于 n% 位置的值称为第 n 百分位数。在不同的应用程序中百分位的计算方式会有略微的差异,但不影响我们对百分位数的应用,比如找出数据中异常等。比如:Excel、Mysql,Es等。这里我们介绍两种百分位的计算方法。
1、百分位计算方法
1)计算方式一
- 将一组数据 data 从小到大排序(该数据的数量为m)
- 计算第 n 百分位数所在位置 p :p = 1 + (m - 1) * n%
- 计算第 n 百分位数的值 x,得到位置 p 所在数据组中的数据左右相邻的数值 data[p-1] 和 data[p+1] ,则 x = data[p-1] + (data[p+1] - data[p-1])*(p - p向下取整的整数)。
以上图的数据为例,计算第80位百分数:
- 计算位置:p = 1 + (12 -1)*0.8 = 9.8
- 计算值:x = 8999.65 + (9759.25 - 8999.65) * (9.8 - 9) = 9607.330
2)计算方式二
- 将一组数据 data 从小到大排序(该数据的数量为m)
- 计算第 n 百分位数所在位置 p : p = (m - 1) * n% = a(整数) + b(小数)
- 计算第 n 百分位数的值 x,x = data[a+1] * (1 - b) + data[a+2] * b
以上图的数据为例,计算第80位百分数:
- 计算位置:p = (12 - 1)* 0.8 = 8.8 = 8 + 0.8
- 计算值:x = 8999.65 * (1 - 0.8) + 9759.25 * 0.8 = 9607.330
2、Percentiles
ES 的百分比聚合可以从文档中数值类型的字段中提取一个或多个百分位数,默认情况下会按照 [ 1, 5, 25, 50, 75, 95, 99 ] 进行返回。
1)基础用法
{ "aggs" : { "{percentiles_name}" : { "percentiles" : { "field" : "{percentiles_field_name}" , "percents" : "[{percent1},{percent2},...]", "keyed" : "{true/false}" } } } }
percentiles_name 自定义 percentiles 的名称
- percentiles_field_name 参与计算的字段名
- percents 指定返回的百分比数值
- keyed 返回格式按照{key:value}的方式返回,而非数组 {key:xxx,value:xxx} ,默认为true
获取产品销售价格的百分数:
POST /order/_search?size=0 { "aggs" : { "price_percentiles" : { "percentiles" : { "field" : "price" } } } }
返回结果:
{ ... "aggregations" : { "price_percentiles" : { "values" : { "1.0" : 100.0, "5.0" : 100.0, "25.0" : 3974.73, "50.0" : 8176.825000000001, "75.0" : 9379.45, "95.0" : 9996.914, "99.0" : 9999.2 } } } }
获取产品销售价格的百分数,并指定百分数 [10,80,90] :
POST /order/_search?size=0 { "aggs" : { "price_percentiles" : { "percentiles" : { "field" : "price" , "percents" : [10, 80, 90] } } } }
返回结果:
{ ... "aggregations" : { "price_percentiles" : { "values" : { "10.0" : 100.0, "80.0" : 9780.959, "90.0" : 9983.198 } } } }
使用数组方式返回结果:
POST /order/_search?size=0 { "aggs" : { "price_percentiles" : { "percentiles" : { "field" : "price" , "percents" : [10, 80, 90], "keyed": false } } } }
返回结果:
{ ... "aggregations" : { "price_percentiles" : { "values" : [ { "key" : 10.0, "value" : 100.0 }, { "key" : 80.0, "value" : 9780.959 }, { "key" : 90.0, "value" : 9983.198 } ] } } }
2)缺失值
默认情况下,当文档中缺失计算字段值时,该文档将会被忽略,若我们希望使用这部分文档,可以通过设置 missing 参数。以下例子中,缺失字段的 price 将按照 0 来计算。
POST /order/_search?size=0 { "aggs" : { "price_percentiles" : { "percentiles" : { "field" : "price" , "missing" : 0 } } } }
3)精确度
百分位度量使用 TDigest 近似算法来计算,该算法具有以下几个特性:
- 百分位的准确度与百分位的位置极端程度成正比,也就是位于始末的1 或 99 的百分位要比中间位置的要更准确
- 数据集越小,百分位数的准确度越高;当数据集较小时,可达到100%准确
- 随着数据集的增长,算法开始对百分位进行估算。根据聚合数据的分布及数据量的大小,可以有效的在准确度和节省内存之间作出权衡
需要注意的是,在实时性、精确度、数据量三个特性中,我们最多只能满足两个,而近似算法选择了实时性和大数据量,所以会损失一定的精确度,提供相对准确的分析结果。
4)压缩
压缩参数 compression 可以帮助近似算法(tdigest)平衡内存的使用和精度的估算。
POST /order/_search?size=0 { "aggs" : { "price_percentiles" : { "percentiles" : { "field" : "price" , "tdigest": { "compression": 200 } } } } }
TDigest 算法使用多个节点来执行百分位的计算,使用的节点越多,占用的内存越大,计算的结果越精确。设置该参数后,节点数量限制为最多 20 * compression,默认值为100,也就是 20 * 100 = 2000 个节点。每个节点大约占用32字节的内存,那么在最糟糕的情况下(大量的数据依次到达) 2000个节点就占用 64KB。实际上,数据往往更随机,而 TDigest 将使用更少的内存。
5)HDR 直方图
HDR直方图(High Dynamic Range Histogram,高动态范围直方图)是一种替代实现,在计算延迟度量的百分位数时非常有用,因为它比 t-digest 实现更快,但需要更大的内存占用。此实现维护一个固定的最坏情况百分比错误。这意味着如果数据记录值从1微秒到1小时(3600000000毫秒)直方图设置为3位有效数字,它将维持一个价值1微秒的分辨率值1毫秒,3.6秒(或更好的)最大跟踪值(1小时)。
GET /order/_search?size=0 { "size": 0, "aggs": { "price_percentiles": { "percentiles": { "field": "price", "percents": [ 95, 99, 99.9 ], "hdr": { "number_of_significant_value_digits": 3 } } } } }
- hdr 指定直方图相关的参数
- number_of_significant_value_digits 指定以有效位数为单位的直方图值的分辨率
注意:hdr直方图只支持正值,如果传递负值,则会出错。如果值的范围是未知的,那么使用HDRHistogram也不是一个好主意,因为这可能会导致内存的大量使用。