文章目录
$percentile
聚合运算符返回与指定百分位值相对应的标量值数组。 $percentile
可以用作 $group
阶段的累加器或聚合表达式。
语法
{
$percentile: {
input: <expression>,
p: [ <expression1>, <expression2>, ... ],
method: <string>
}
}
参数字段说明
字段 | 类型 | 是否必须 | 说明 |
---|---|---|---|
input |
表达式 | 是 | $percentile 计算input 数据的百分位值。input 必须是字段或数值表达式,如果表达式无法转换为数值类型将被忽略 |
p |
表达式 | 是 | $percentile 计算p 中每个元素的百分位值,这些元素表示百分比,并且计算结果必须为0.0 到1.0 (含)范围内的数值。$percentile 返回结果的顺序与p 中元素的顺序相同 |
method |
字符串 | 是 | mongod用于计算百分位值的方法,值必须是"approximate" |
使用
$percentile
可以在$group
和$setWindowFields
作为累加器使用,在$project
阶段可以作为聚合表达式使用。$percentile
做为累加器使用时,具有下面的特性:- 在结算阶段,所有的文档只有一个结果。
- 使用 t-digest 算法计算基于百分位数的近似指标。
- 使用近似方法来扩展大量数据。
$percentile
做为表达式使用时,具有下面的特性:- 接受数组作为输入
- 为每个输入文档计算单独的结果
操作类型
- 在
$group
阶段,$percentile
是一个累加器,用于计算窗口中所有文档的值。 - 在
$project
阶段,$percentile
是一个聚合表达式,用于计算每个文档的值。 - 在
$setWindowFields
阶段,$percentile
会像聚合表达式一样返回每个文档的结果,但其结果会像累加器一样在文档组中计算。
计算时的注意事项
- 在
$group
阶段,$percentile
总是使用近似计算方法。 - 在
$project
阶段,即使指定了近似计算方法,$percentile
也可能使用离散计算方法。 - 在
$setWindowFields
阶段,计算量决定了$percentile
使用的计算方法。 - 因为算法计算的是近似值,所以即使在相同的数据集上,
$percentile
返回的计算百分位数也可能不同。 - 重复样本会导致模糊性。如果存在大量重复样本,百分位数值可能无法代表实际的样本分布,如果一个所有样本都相同的数据集,数据集中的所有值都位于或低于任何一个百分位数,50%的百分位数值实际上代表
0
或100%
的样本。 - 当
p = 0.0
时,$percentile
返回最小值。 - 当
p = 1.0
时,$percentile
返回最大值。*
input为数组
如果在$project
阶段使用$percentile
作为聚合表达式,则可以使用数组作为输入。语法是:
{
$percentile: {
input: [ <expression1, <expression2>, .., <expressionN> ],
p: [ <expression1>, <expression2>, ... ],
method: <string>
}
}
窗口功能
通过窗口函数可以计算出相邻文档移动 "窗口 "的结果。。当每个文档通过管道时,当每个文件通过管道时,$setWindowFields 阶段就会:
- 重新计算当前窗口中的文档集合
- 计算集合中所有文档的值
- 返回该文档的单个值
可以在$setWindowFields
阶段使用$percentile
来计算时间序列或其他相关数据的滚动统计信息。
在$setWindowField
阶段使用$percentile
时,输入值必须是字段名称,否则操作将失败。
举例
使用下面的脚本创建testScores
集合:
db.testScores.insertMany( [
{ studentId: "2345", test01: 62, test02: 81, test03: 80 },
{ studentId: "2356", test01: 60, test02: 83, test03: 79 },
{ studentId: "2358", test01: 67, test02: 82, test03: 78 },
{ studentId: "2367", test01: 64, test02: 72, test03: 77 },
{ studentId: "2369", test01: 60, test02: 53, test03: 72 }
] )
作为累加器计算单个值
下面的聚合,使用$percentile
创建一个累加器,计算单个百分位值:
db.testScores.aggregate( [
{
$group: {
_id: null,
test01_percentiles: {
$percentile: {
input: "$test01",
p: [ 0.95 ],
method: 'approximate'
}
},
}
}
] )
聚合结果:
{ _id: null, test01_percentiles: [ 67 ] }
在本例中:
_id
字段值为空,所以$group
选择了集合中的所有文档。- 百分位数累加器
$percentile
的输入数据来自于test01
字段。 - 百分位数组和
p
,有一个值,所以$percentile
操作符值只计算test01
数据的一项,第95百分位数值为67。
作为累加器计算多个值
创建可计算多个百分位值的累加器:
db.testScores.aggregate( [
{
$group: {
_id: null,
test01_percentiles: {
$percentile: {
input: "$test01",
p: [ 0.5, 0.75, 0.9, 0.95 ],
method: 'approximate'
}
},
test02_percentiles: {
$percentile: {
input: "$test02",
p: [ 0.5, 0.75, 0.9, 0.95 ],
method: 'approximate'
}
},
test03_percentiles: {
$percentile: {
input: "$test03",
p: [ 0.5, 0.75, 0.9, 0.95 ],
method: 'approximate'
}
},
test03_percent_alt: {
$percentile: {
input: "$test03",
p: [ 0.9, 0.5, 0.75, 0.95 ],
method: 'approximate'
}
},
}
}
] )
聚合结果:
{
_id: null,
test01_percentiles: [ 62, 64, 67, 67 ],
test02_percentiles: [ 81, 82, 83, 83 ],
test03_percentiles: [ 78, 79, 80, 80 ],
test03_percent_alt: [ 80, 78, 79, 80 ]
}
在本例中:
_id
字段值为null
,因此$group
选中了集合中的所有文档。- 百分位数累加器计算三个字段
test01
、test02
和test03
的值。 - 累加器计算每个输入字段的第
50、75、90 和 95
个百分位值。 - 百分位数值的返回顺序与
p
的元素相同。test03_percentiles
和test03_percent_alt
中的值相同,但顺序不同。每个结果数组中元素的顺序与p
中相应元素的顺序相匹配。
在project阶段使用$percentile
在$project
阶段,$percentile
作为聚合表达式,用于计算每个文档的值。可以使用字段名称或数组作为$project
阶段的输入,如下:
db.testScores.aggregate( [
{
$project: {
_id: 0,
studentId: 1,
testPercentiles: {
$percentile: {
input: [ "$test01", "$test02", "$test03" ],
p: [ 0.5, 0.95 ],
method: 'approximate'
}
}
}
}
] )
聚合结果:
{ studentId: '2345', testPercentiles: [ 80, 81 ] },
{ studentId: '2356', testPercentiles: [ 79, 83 ] },
{ studentId: '2358', testPercentiles: [ 78, 82 ] },
{ studentId: '2367', testPercentiles: [ 72, 77 ] },
{ studentId: '2369', testPercentiles: [ 60, 72 ] }
当$percentile
为聚合表达式时,每个StudentId
都会有一个结果。
在$setWindowFields
阶段使用$percentile
要根据本地数据趋势确定百分位值,可在$setWindowField
聚合管道阶段使用$percentile
,下面的示例创建了一个用于筛选分数的窗口:
db.testScores.aggregate( [
{
$setWindowFields: {
sortBy: { test01: 1 },
output: {
test01_95percentile: {
$percentile: {
input: "$test01",
p: [ 0.95 ],
method: 'approximate'
},
window: {
range: [ -3, 3 ]
}
}
}
}
},
{
$project: {
_id: 0,
studentId: 1,
test01_95percentile: 1
}
}
] )
聚合结果为:
{ studentId: '2356', test01_95percentile: [ 62 ] },
{ studentId: '2369', test01_95percentile: [ 62 ] },
{ studentId: '2345', test01_95percentile: [ 64 ] },
{ studentId: '2367', test01_95percentile: [ 67 ] },
{ studentId: '2358', test01_95percentile: [ 67 ] }
在本例中,每个文档的百分位数计算也包含了前后三个文档的数据。