在之前的教程 “Elasticsearch:pipeline aggregation 介绍 (一)” 中,我们讨论了 Elasticsearch 管道聚合的结构,并指导你设置了几个常见的管道,例如导数、累积和和平均桶聚合。
在本文中,我们将继续分析 Elasticsearch 管道聚合,重点关注统计(stats)、移动平均(moving averages)和移动函数(moving functions)、百分位数(percentiles)、桶排序和桶脚本等管道。 Kibana 支持本文中讨论的一些管道聚合,例如移动平均线,因此我们还将向你展示如何将它们可视化。 让我们开始吧!
准备数据
我们在 Kibana 中打入如下的命令来创建一个叫做 traffic_stats 的索引:
1. PUT traffic_stats
2. {
3. "mappings": {
4. "properties": {
5. "date": {
6. "type": "date",
7. "format": "dateOptionalTime"
8. },
9. "visits": {
10. "type": "integer"
11. },
12. "max_time_spent": {
13. "type": "integer"
14. }
15. }
16. }
17. }
由于一些原因,在最新的版本,比如 8.6.x 版本中,上述的命令可能不成功,你需要使用如下的格式来运行:
1. PUT traffic_stats
2. {
3. "mappings": {
4. "properties": {
5. "date": {
6. "type": "date",
7. "format": "date_optional_time"
8. },
9. "visits": {
10. "type": "integer"
11. },
12. "max_time_spent": {
13. "type": "integer"
14. }
15. }
16. }
17. }
创建索引映射后,让我们向其中保存一些任意数据:
1. PUT _bulk
2. {"index":{"_index":"traffic_stats"}}
3. {"visits":"488", "date":"2018-10-1", "max_time_spent":"900"}
4. {"index":{"_index":"traffic_stats"}}
5. {"visits":"783", "date":"2018-10-6", "max_time_spent":"928"}
6. {"index":{"_index":"traffic_stats"}}
7. {"visits":"789", "date":"2018-10-12", "max_time_spent":"1834"}
8. {"index":{"_index":"traffic_stats"}}
9. {"visits":"1299", "date":"2018-11-3", "max_time_spent":"592"}
10. {"index":{"_index":"traffic_stats"}}
11. {"visits":"394", "date":"2018-11-6", "max_time_spent":"1249"}
12. {"index":{"_index":"traffic_stats"}}
13. {"visits":"448", "date":"2018-11-24", "max_time_spent":"874"}
14. {"index":{"_index":"traffic_stats"}}
15. {"visits":"768", "date":"2018-12-18", "max_time_spent":"876"}
16. {"index":{"_index":"traffic_stats"}}
17. {"visits":"1194", "date":"2018-12-24", "max_time_spent":"1249"}
18. {"index":{"_index":"traffic_stats"}}
19. {"visits":"987", "date":"2018-12-28", "max_time_spent":"1599"}
20. {"index":{"_index":"traffic_stats"}}
21. {"visits":"872", "date":"2019-01-1", "max_time_spent":"828"}
22. {"index":{"_index":"traffic_stats"}}
23. {"visits":"972", "date":"2019-01-5", "max_time_spent":"723"}
24. {"index":{"_index":"traffic_stats"}}
25. {"visits":"827", "date":"2019-02-5", "max_time_spent":"1300"}
26. {"index":{"_index":"traffic_stats"}}
27. {"visits":"1584", "date":"2019-02-15", "max_time_spent":"1500"}
28. {"index":{"_index":"traffic_stats"}}
29. {"visits":"1604", "date":"2019-03-2", "max_time_spent":"1488"}
30. {"index":{"_index":"traffic_stats"}}
31. {"visits":"1499", "date":"2019-03-27", "max_time_spent":"1399"}
32. {"index":{"_index":"traffic_stats"}}
33. {"visits":"1392", "date":"2019-04-8", "max_time_spent":"1294"}
34. {"index":{"_index":"traffic_stats"}}
35. {"visits":"1247", "date":"2019-04-15", "max_time_spent":"1194"}
36. {"index":{"_index":"traffic_stats"}}
37. {"visits":"984", "date":"2019-05-15", "max_time_spent":"1184"}
38. {"index":{"_index":"traffic_stats"}}
39. {"visits":"1228", "date":"2019-05-18", "max_time_spent":"1485"}
40. {"index":{"_index":"traffic_stats"}}
41. {"visits":"1423", "date":"2019-06-14", "max_time_spent":"1452"}
42. {"index":{"_index":"traffic_stats"}}
43. {"visits":"1238", "date":"2019-06-24", "max_time_spent":"1329"}
44. {"index":{"_index":"traffic_stats"}}
45. {"visits":"1388", "date":"2019-07-14", "max_time_spent":"1542"}
46. {"index":{"_index":"traffic_stats"}}
47. {"visits":"1499", "date":"2019-07-24", "max_time_spent":"1742"}
48. {"index":{"_index":"traffic_stats"}}
49. {"visits":"1523", "date":"2019-08-13", "max_time_spent":"1552"}
50. {"index":{"_index":"traffic_stats"}}
51. {"visits":"1443", "date":"2019-08-19", "max_time_spent":"1511"}
52. {"index":{"_index":"traffic_stats"}}
53. {"visits":"1587", "date":"2019-09-14", "max_time_spent":"1497"}
54. {"index":{"_index":"traffic_stats"}}
55. {"visits":"1534", "date":"2019-09-27", "max_time_spent":"1434"}
太棒了! 现在,一切都已准备就绪,可以说明一些管道聚合。 让我们从统计桶聚合开始。为了下面可视化的需求,你需要在 Kibana 中为这个 traffic_stats 创建 index pattern (早期版本)或者 Data view:
Stats Bucket Aggregation
Stats 聚合计算一组统计指标,例如索引中某些数字字段的最小值(min)、最大值(Max)、平均值(avg)和总和(sum)。在 Elasticsearch 中,还可以计算由其他聚合生成的桶的统计数据。 当 stats 聚合所需的值必须首先使用其他聚合按桶计算时,这非常有用。
为了理解这个想法,让我们看一下下面的例子:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. }
16. }
17. },
18. "stats_monthly_visits": {
19. "stats_bucket": {
20. "buckets_path": "visits_per_month>total_visits"
21. }
22. }
23. }
24. }
在此查询中,我们首先为 “visits” 字段生成一个日期直方图,并计算每个生成的桶的每月总访问量。 接下来,我们使用 stats 管道为每个生成的桶计算统计数据。 响应应如下所示:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "total_visits": {
10. "value": 2060
11. }
12. },
13. {
14. "key_as_string": "2018-11-01T00:00:00.000Z",
15. "key": 1541030400000,
16. "doc_count": 3,
17. "total_visits": {
18. "value": 2141
19. }
20. },
21. {
22. "key_as_string": "2018-12-01T00:00:00.000Z",
23. "key": 1543622400000,
24. "doc_count": 3,
25. "total_visits": {
26. "value": 2949
27. }
28. },
29. {
30. "key_as_string": "2019-01-01T00:00:00.000Z",
31. "key": 1546300800000,
32. "doc_count": 2,
33. "total_visits": {
34. "value": 1844
35. }
36. },
37. {
38. "key_as_string": "2019-02-01T00:00:00.000Z",
39. "key": 1548979200000,
40. "doc_count": 2,
41. "total_visits": {
42. "value": 2411
43. }
44. },
45. {
46. "key_as_string": "2019-03-01T00:00:00.000Z",
47. "key": 1551398400000,
48. "doc_count": 2,
49. "total_visits": {
50. "value": 3103
51. }
52. },
53. {
54. "key_as_string": "2019-04-01T00:00:00.000Z",
55. "key": 1554076800000,
56. "doc_count": 2,
57. "total_visits": {
58. "value": 2639
59. }
60. },
61. {
62. "key_as_string": "2019-05-01T00:00:00.000Z",
63. "key": 1556668800000,
64. "doc_count": 2,
65. "total_visits": {
66. "value": 2212
67. }
68. },
69. {
70. "key_as_string": "2019-06-01T00:00:00.000Z",
71. "key": 1559347200000,
72. "doc_count": 2,
73. "total_visits": {
74. "value": 2661
75. }
76. },
77. {
78. "key_as_string": "2019-07-01T00:00:00.000Z",
79. "key": 1561939200000,
80. "doc_count": 2,
81. "total_visits": {
82. "value": 2887
83. }
84. },
85. {
86. "key_as_string": "2019-08-01T00:00:00.000Z",
87. "key": 1564617600000,
88. "doc_count": 2,
89. "total_visits": {
90. "value": 2966
91. }
92. },
93. {
94. "key_as_string": "2019-09-01T00:00:00.000Z",
95. "key": 1567296000000,
96. "doc_count": 2,
97. "total_visits": {
98. "value": 3121
99. }
100. }
101. ]
102. },
103. "stats_monthly_visits": {
104. "count": 12,
105. "min": 1844,
106. "max": 3121,
107. "avg": 2582.8333333333335,
108. "sum": 30994
109. }
110. }
111. }
在后台,stats 聚合对日期直方图生成的桶执行最小、最大、平均和求和管道聚合,计算结果,然后在响应结束时反映它们。
扩展统计(extended stats)桶聚合具有相同的逻辑,只是它返回额外的统计信息,如方差、平方和、标准差和标准差界限。 让我们稍微调整上面的查询以使用扩展的统计管道:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. }
16. }
17. },
18. "stats_monthly_visits": {
19. "extended_stats_bucket": {
20. "buckets_path": "visits_per_month>total_visits"
21. }
22. }
23. }
24. }
响应将包含比简单的统计桶聚合更多的统计信息:
1. "stats_monthly_visits": {
2. "count": 12,
3. "min": 1844,
4. "max": 3121,
5. "avg": 2582.8333333333335,
6. "sum": 30994,
7. "sum_of_squares": 82176700,
8. "variance": 177030.30555555597,
9. "variance_population": 177030.30555555597,
10. "variance_sampling": 193123.96969697016,
11. "std_deviation": 420.7496946588981,
12. "std_deviation_population": 420.7496946588981,
13. "std_deviation_sampling": 439.4587235417795,
14. "std_deviation_bounds": {
15. "upper": 3424.3327226511296,
16. "lower": 1741.3339440155373,
17. "upper_population": 3424.3327226511296,
18. "lower_population": 1741.3339440155373,
19. "upper_sampling": 3461.7507804168927,
20. "lower_sampling": 1703.9158862497745
21. }
22. }
Kibana 支持标准偏差边界的可视化,你可能会在上面的响应中看到这一点。 在下图中,我们看到日期直方图生成的每个桶的标准差上限(蓝色)和标准差下限(绿色):
Percentiles Bucket Aggregation
你还记得,百分位数(percentile)是一种统计量度,表示组中给定百分比的观察值低于该值的值。 例如,第 65 个百分位数是低于该值的 65% 的观测值。
一个简单的百分位数度量聚合计算索引数据中直接可用的值的百分位数。 但是,在某些情况下,你想要使用日期直方图生成存储桶并在应用百分位数之前计算这些存储桶的值。
当我们可以使用适用于父(parent)聚合生成的桶和兄弟(sibling)聚合计算的一些指标的百分位数桶管道时,情况就是如此。 看看下面的例子:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "aggs": {
4. "visits_per_month": {
5. "date_histogram": {
6. "field": "date",
7. "calendar_interval": "month"
8. },
9. "aggs": {
10. "total_visits": {
11. "sum": {
12. "field": "visits"
13. }
14. }
15. }
16. },
17. "percentiles_monthly_visits": {
18. "percentiles_bucket": {
19. "buckets_path": "visits_per_month>total_visits",
20. "percents": [
21. 15,
22. 50,
23. 75,
24. 99
25. ]
26. }
27. }
28. }
29. }
在这里,我们为日期直方图生成的每个存储桶中的 total_visits 指标的输出计算百分位数。 与常规百分位数聚合类似,百分位数管道聚合允许指定要返回的一组百分位数。 在这种情况下,我们选择计算第 15、50、75 和 99 个百分位数,这反映在聚合的百分比字段中。
上面的查询应该返回以下响应:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "total_visits": {
10. "value": 2060
11. }
12. },
13. {
14. "key_as_string": "2018-11-01T00:00:00.000Z",
15. "key": 1541030400000,
16. "doc_count": 3,
17. "total_visits": {
18. "value": 2141
19. }
20. },
21. {
22. "key_as_string": "2018-12-01T00:00:00.000Z",
23. "key": 1543622400000,
24. "doc_count": 3,
25. "total_visits": {
26. "value": 2949
27. }
28. },
29. {
30. "key_as_string": "2019-01-01T00:00:00.000Z",
31. "key": 1546300800000,
32. "doc_count": 2,
33. "total_visits": {
34. "value": 1844
35. }
36. },
37. {
38. "key_as_string": "2019-02-01T00:00:00.000Z",
39. "key": 1548979200000,
40. "doc_count": 2,
41. "total_visits": {
42. "value": 2411
43. }
44. },
45. {
46. "key_as_string": "2019-03-01T00:00:00.000Z",
47. "key": 1551398400000,
48. "doc_count": 2,
49. "total_visits": {
50. "value": 3103
51. }
52. },
53. {
54. "key_as_string": "2019-04-01T00:00:00.000Z",
55. "key": 1554076800000,
56. "doc_count": 2,
57. "total_visits": {
58. "value": 2639
59. }
60. },
61. {
62. "key_as_string": "2019-05-01T00:00:00.000Z",
63. "key": 1556668800000,
64. "doc_count": 2,
65. "total_visits": {
66. "value": 2212
67. }
68. },
69. {
70. "key_as_string": "2019-06-01T00:00:00.000Z",
71. "key": 1559347200000,
72. "doc_count": 2,
73. "total_visits": {
74. "value": 2661
75. }
76. },
77. {
78. "key_as_string": "2019-07-01T00:00:00.000Z",
79. "key": 1561939200000,
80. "doc_count": 2,
81. "total_visits": {
82. "value": 2887
83. }
84. },
85. {
86. "key_as_string": "2019-08-01T00:00:00.000Z",
87. "key": 1564617600000,
88. "doc_count": 2,
89. "total_visits": {
90. "value": 2966
91. }
92. },
93. {
94. "key_as_string": "2019-09-01T00:00:00.000Z",
95. "key": 1567296000000,
96. "doc_count": 2,
97. "total_visits": {
98. "value": 3121
99. }
100. }
101. ]
102. },
103. "percentiles_monthly_visits": {
104. "values": {
105. "15.0": 2141,
106. "50.0": 2661,
107. "75.0": 2949,
108. "99.0": 3121
109. }
110. }
111. }
112. }
例如,此数据显示我们存储桶中所有每月访问量的 99% 低于 3121。
Moving Average Aggregation
移动平均值或滚动平均值是一种计算技术,它构建了完整数据集的不同子集的一系列平均值。 子集通常被称为特定大小的窗口。 事实上,窗口的大小表示每次迭代时窗口覆盖的数据点数。 在每次迭代中,该算法计算适合窗口的所有数据点的平均值,然后通过排除前一个子集的第一个成员并包括下一个子集的第一个成员来向前滑动。 这就是为什么我们称这个平均线为移动平均线。
例如,给定数据 [1, 5, 8, 23, 34, 28, 7, 23, 20, 19],我们可以计算窗口大小为 5 的简单移动平均值,如下所示
1. (1 + 5 + 8 + 23 + 34) / 5 = 14.2
2. (5 + 8 + 23 + 34+ 28) / 5 = 19.6
3. (8 + 23 + 34 + 28 + 7) / 5 = 20
4. so on
移动平均线通常与时间序列数据(例如股票市场图表)一起使用,以平滑短期波动并突出长期趋势或周期。 平滑通常用于消除高频波动或随机噪声,因为它使较低频率的趋势更加明显。
支持的移动平均模型
moving_avg 聚合支持五种移动平均 “模型”:简单(simple)、线性(linear)、指数加权(exponentially weighted)、holt-linear 和 holt-winters。 这些模型的不同之处在于窗口值的加权方式。
随着数据点变得 “更旧”(即窗口从它们滑开),它们的权重可能会有所不同。 你可以通过设置聚合的模型参数来指定你选择的模型。
在下文中,我们将讨论适用于大多数用例的简单、线性和指数加权模型。 有关可用模型的更多信息,请参阅 Elasticsearch 官方文档。
必须指出的是:根据官方文档,在最新的发布中,移动平均聚合已被删除。 请改用移动函数聚合。与移动平均聚合一样,移动函数聚合允许处理数据点的子集,逐渐在数据集中滑动窗口。 但是,移动功能还允许指定在每个数据窗口上执行的自定义脚本。 Elasticsearch 支持诸如最小值/最大值、移动平均线等预定义脚本。为方便起见,一些函数已预先构建并可在 moving_fn 脚本上下文中使用:
1. max() 2. min() 3. sum() 4. stdDev() 5. unweightedAvg() 6. linearWeightedAvg() 7. ewma() 8. holt() 9. holtWinters()
Simple Model
simple 模型首先计算窗口中所有数据点的总和,然后将该总和除以窗口的大小。 换句话说,一个简单的模型为数据集中的每个窗口计算一个简单的算术平均值。
在下面的示例中,我们使用了一个窗口大小为 30 的简单模型。聚合将计算日期直方图生成的所有桶的移动平均值:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "aggs":{
4. "visits_per_month":{
5. "date_histogram":{
6. "field":"date",
7. "calendar_interval":"month"
8. },
9. "aggs":{
10. "total_visits":{
11. "sum":{
12. "field":"visits"
13. }
14. },
15. "the_movavg":{
16. "moving_avg":{
17. "buckets_path":"total_visits",
18. "window":30,
19. "model":"simple"
20. }
21. }
22. }
23. }
24. }
25. }
响应应如下所示:
1. "aggregations":{
2. "visits_per_month":{
3. "buckets":[
4. {
5. "key_as_string":"2019-08-01T00:00:00.000Z",
6. "key":1564617600000,
7. "doc_count":2,
8. "total_visits":{
9. "value":2966.0
10. },
11. "the_movavg":{
12. "value":2490.7
13. }
14. },
15. {
16. "key_as_string":"2019-09-01T00:00:00.000Z",
17. "key":1567296000000,
18. "doc_count":2,
19. "total_visits":{
20. "value":3121.0
21. },
22. "the_movavg":{
23. "value":2533.909090909091
24. }
25. }
26. ]
27. }
28. }
如果我们采用移动函数聚合:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "my_date_histo": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "1M"
9. },
10. "aggs": {
11. "the_sum": {
12. "sum": { "field": "visits" }
13. },
14. "the_movfn": {
15. "moving_fn": {
16. "buckets_path": "the_sum",
17. "window": 30,
18. "script": "MovingFunctions.unweightedAvg(values)"
19. }
20. }
21. }
22. }
23. }
24. }
它显示的结果是:
1. {
2. "aggregations": {
3. "my_date_histo": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "the_sum": {
10. "value": 2060
11. },
12. "the_movfn": {
13. "value": null
14. }
15. },
16. {
17. "key_as_string": "2018-11-01T00:00:00.000Z",
18. "key": 1541030400000,
19. "doc_count": 3,
20. "the_sum": {
21. "value": 2141
22. },
23. "the_movfn": {
24. "value": 2060
25. }
26. },
27. {
28. "key_as_string": "2018-12-01T00:00:00.000Z",
29. "key": 1543622400000,
30. "doc_count": 3,
31. "the_sum": {
32. "value": 2949
33. },
34. "the_movfn": {
35. "value": 2100.5
36. }
37. },
38. {
39. "key_as_string": "2019-01-01T00:00:00.000Z",
40. "key": 1546300800000,
41. "doc_count": 2,
42. "the_sum": {
43. "value": 1844
44. },
45. "the_movfn": {
46. "value": 2383.3333333333335
47. }
48. },
49. {
50. "key_as_string": "2019-02-01T00:00:00.000Z",
51. "key": 1548979200000,
52. "doc_count": 2,
53. "the_sum": {
54. "value": 2411
55. },
56. "the_movfn": {
57. "value": 2248.5
58. }
59. },
60. {
61. "key_as_string": "2019-03-01T00:00:00.000Z",
62. "key": 1551398400000,
63. "doc_count": 2,
64. "the_sum": {
65. "value": 3103
66. },
67. "the_movfn": {
68. "value": 2281
69. }
70. },
71. {
72. "key_as_string": "2019-04-01T00:00:00.000Z",
73. "key": 1554076800000,
74. "doc_count": 2,
75. "the_sum": {
76. "value": 2639
77. },
78. "the_movfn": {
79. "value": 2418
80. }
81. },
82. {
83. "key_as_string": "2019-05-01T00:00:00.000Z",
84. "key": 1556668800000,
85. "doc_count": 2,
86. "the_sum": {
87. "value": 2212
88. },
89. "the_movfn": {
90. "value": 2449.5714285714284
91. }
92. },
93. {
94. "key_as_string": "2019-06-01T00:00:00.000Z",
95. "key": 1559347200000,
96. "doc_count": 2,
97. "the_sum": {
98. "value": 2661
99. },
100. "the_movfn": {
101. "value": 2419.875
102. }
103. },
104. {
105. "key_as_string": "2019-07-01T00:00:00.000Z",
106. "key": 1561939200000,
107. "doc_count": 2,
108. "the_sum": {
109. "value": 2887
110. },
111. "the_movfn": {
112. "value": 2446.6666666666665
113. }
114. },
115. {
116. "key_as_string": "2019-08-01T00:00:00.000Z",
117. "key": 1564617600000,
118. "doc_count": 2,
119. "the_sum": {
120. "value": 2966
121. },
122. "the_movfn": {
123. "value": 2490.7
124. }
125. },
126. {
127. "key_as_string": "2019-09-01T00:00:00.000Z",
128. "key": 1567296000000,
129. "doc_count": 2,
130. "the_sum": {
131. "value": 3121
132. },
133. "the_movfn": {
134. "value": 2533.909090909091
135. }
136. }
137. ]
138. }
139. }
140. }
从上面的输出结果中,我们可以看出来它们的结果是一致的。
有关 Moving average 的可视化,请参照文章 “Elasticsearch:Moving average aggregation 介绍”。
Linear Model
该模型为序列中的数据点分配不同的线性权重,因此 “较旧” 的数据点(即靠近窗口开头的数据点)对最终平均计算的贡献较小。 这种方法可以减少数据平均值的“滞后”,因为较旧的数据点对最终结果的影响较小。
与简单模型一样,较小的窗口往往会消除小规模的波动,而较大的窗口往往只会消除高频波动。
此外,与简单模型类似,线性模型往往 “滞后” 于实际数据,尽管程度低于简单模型。
在下面的示例中,我们使用窗口大小为 30 的线性模型:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "the_movavg": {
17. "moving_avg": {
18. "buckets_path": "total_visits",
19. "window": 30,
20. "model": "linear"
21. }
22. }
23. }
24. }
25. }
26. }
上面命令返回的结果为:
1. "aggregations":{
2. "visits_per_month":{
3. "buckets":[
4. {
5. "key_as_string":"2019-08-01T00:00:00.000Z",
6. "key":1564617600000,
7. "doc_count":2,
8. "total_visits":{
9. "value":2966.0
10. },
11. "the_movavg":{
12. "value":2539.75
13. }
14. },
15. {
16. "key_as_string":"2019-09-01T00:00:00.000Z",
17. "key":1567296000000,
18. "doc_count":2,
19. "total_visits":{
20. "value":3121.0
21. },
22. "the_movavg":{
23. "value":2609.73134328358
24. }
25. }
26. ]
27. }
28. }
针对不支持 Moving Average 的版本,我们使用 Moving functions 来实现:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "my_date_histo": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "1M"
9. },
10. "aggs": {
11. "the_sum": {
12. "sum": { "field": "visits" }
13. },
14. "the_movfn": {
15. "moving_fn": {
16. "buckets_path": "the_sum",
17. "window": 30,
18. "script": "MovingFunctions.linearWeightedAvg(values)"
19. }
20. }
21. }
22. }
23. }
24. }
上述命令返回的结果为:
1. {
2. "aggregations": {
3. "my_date_histo": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "the_sum": {
10. "value": 2060
11. },
12. "the_movfn": {
13. "value": null
14. }
15. },
16. {
17. "key_as_string": "2018-11-01T00:00:00.000Z",
18. "key": 1541030400000,
19. "doc_count": 3,
20. "the_sum": {
21. "value": 2141
22. },
23. "the_movfn": {
24. "value": 1030
25. }
26. },
27. {
28. "key_as_string": "2018-12-01T00:00:00.000Z",
29. "key": 1543622400000,
30. "doc_count": 3,
31. "the_sum": {
32. "value": 2949
33. },
34. "the_movfn": {
35. "value": 1585.5
36. }
37. },
38. {
39. "key_as_string": "2019-01-01T00:00:00.000Z",
40. "key": 1546300800000,
41. "doc_count": 2,
42. "the_sum": {
43. "value": 1844
44. },
45. "the_movfn": {
46. "value": 2169.8571428571427
47. }
48. },
49. {
50. "key_as_string": "2019-02-01T00:00:00.000Z",
51. "key": 1548979200000,
52. "doc_count": 2,
53. "the_sum": {
54. "value": 2411
55. },
56. "the_movfn": {
57. "value": 2051.3636363636365
58. }
59. },
60. {
61. "key_as_string": "2019-03-01T00:00:00.000Z",
62. "key": 1551398400000,
63. "doc_count": 2,
64. "the_sum": {
65. "value": 3103
66. },
67. "the_movfn": {
68. "value": 2163.75
69. }
70. },
71. {
72. "key_as_string": "2019-04-01T00:00:00.000Z",
73. "key": 1554076800000,
74. "doc_count": 2,
75. "the_sum": {
76. "value": 2639
77. },
78. "the_movfn": {
79. "value": 2419.909090909091
80. }
81. },
82. {
83. "key_as_string": "2019-05-01T00:00:00.000Z",
84. "key": 1556668800000,
85. "doc_count": 2,
86. "the_sum": {
87. "value": 2212
88. },
89. "the_movfn": {
90. "value": 2472.793103448276
91. }
92. },
93. {
94. "key_as_string": "2019-06-01T00:00:00.000Z",
95. "key": 1559347200000,
96. "doc_count": 2,
97. "the_sum": {
98. "value": 2661
99. },
100. "the_movfn": {
101. "value": 2416.4054054054054
102. }
103. },
104. {
105. "key_as_string": "2019-07-01T00:00:00.000Z",
106. "key": 1561939200000,
107. "doc_count": 2,
108. "the_sum": {
109. "value": 2887
110. },
111. "the_movfn": {
112. "value": 2464.2608695652175
113. }
114. },
115. {
116. "key_as_string": "2019-08-01T00:00:00.000Z",
117. "key": 1564617600000,
118. "doc_count": 2,
119. "the_sum": {
120. "value": 2966
121. },
122. "the_movfn": {
123. "value": 2539.75
124. }
125. },
126. {
127. "key_as_string": "2019-09-01T00:00:00.000Z",
128. "key": 1567296000000,
129. "doc_count": 2,
130. "the_sum": {
131. "value": 3121
132. },
133. "the_movfn": {
134. "value": 2609.731343283582
135. }
136. }
137. ]
138. }
139. }
140. }
从输出的结果中,我们可以看出来,两种方法的结果是一样的。
Exponentially Weighted Moving Average (EWMA)
该模型与线性模型具有相同的行为,除了 “旧” 数据点的重要性呈指数下降 —— 而不是线性下降。 “较旧” 点的重要性降低的速度可以通过 alpha 设置来控制。 对于较小的 alpha 值,权重衰减缓慢,从而提供更好的平滑效果。 相反,较大的 alpha 值会使 “较旧” 的数据点的重要性迅速下降,从而减少它们对移动平均线的影响。 alpha 的默认值为 0.3,该设置接受从 0 到 1(含)的任何浮点数。
正如你在上面的查询中看到的,EWMA 聚合有一个额外的 “settings” 对象,可以在其中定义 alpha:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "the_movavg": {
17. "moving_avg": {
18. "buckets_path": "total_visits",
19. "window": 30,
20. "model": "ewma",
21. "settings": {
22. "alpha": 0.5
23. }
24. }
25. }
26. }
27. }
28. }
29. }
管道应产生以下响应:
1. "aggregations":{
2. "visits_per_month":{
3. "buckets":[
4. {
5. "key_as_string":"2019-08-01T00:00:00.000Z",
6. "key":1564617600000,
7. "doc_count":2,
8. "total_visits":{
9. "value":2966.0
10. },
11. "the_movavg":{
12. "value":2718.958984375
13. }
14. },
15. {
16. "key_as_string":"2019-09-01T00:00:00.000Z",
17. "key":1567296000000,
18. "doc_count":2,
19. "total_visits":{
20. "value":3121.0
21. },
22. "the_movavg":{
23. "value":2842.4794921875
24. }
25. }
26. ]
27. }
28. }
针对不支持 Moving Average 的发行版来说,我们可以使用 Moving functions 来实现:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "my_date_histo": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "1M"
9. },
10. "aggs": {
11. "the_sum": {
12. "sum": { "field": "visits" }
13. },
14. "the_movfn": {
15. "moving_fn": {
16. "buckets_path": "the_sum",
17. "window": 30,
18. "script": "MovingFunctions.ewma(values, 0.5)"
19. }
20. }
21. }
22. }
23. }
24. }
上面命令返回的结果是:
1. {
2. "aggregations": {
3. "my_date_histo": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "the_sum": {
10. "value": 2060
11. },
12. "the_movfn": {
13. "value": null
14. }
15. },
16. {
17. "key_as_string": "2018-11-01T00:00:00.000Z",
18. "key": 1541030400000,
19. "doc_count": 3,
20. "the_sum": {
21. "value": 2141
22. },
23. "the_movfn": {
24. "value": 2060
25. }
26. },
27. {
28. "key_as_string": "2018-12-01T00:00:00.000Z",
29. "key": 1543622400000,
30. "doc_count": 3,
31. "the_sum": {
32. "value": 2949
33. },
34. "the_movfn": {
35. "value": 2100.5
36. }
37. },
38. {
39. "key_as_string": "2019-01-01T00:00:00.000Z",
40. "key": 1546300800000,
41. "doc_count": 2,
42. "the_sum": {
43. "value": 1844
44. },
45. "the_movfn": {
46. "value": 2524.75
47. }
48. },
49. {
50. "key_as_string": "2019-02-01T00:00:00.000Z",
51. "key": 1548979200000,
52. "doc_count": 2,
53. "the_sum": {
54. "value": 2411
55. },
56. "the_movfn": {
57. "value": 2184.375
58. }
59. },
60. {
61. "key_as_string": "2019-03-01T00:00:00.000Z",
62. "key": 1551398400000,
63. "doc_count": 2,
64. "the_sum": {
65. "value": 3103
66. },
67. "the_movfn": {
68. "value": 2297.6875
69. }
70. },
71. {
72. "key_as_string": "2019-04-01T00:00:00.000Z",
73. "key": 1554076800000,
74. "doc_count": 2,
75. "the_sum": {
76. "value": 2639
77. },
78. "the_movfn": {
79. "value": 2700.34375
80. }
81. },
82. {
83. "key_as_string": "2019-05-01T00:00:00.000Z",
84. "key": 1556668800000,
85. "doc_count": 2,
86. "the_sum": {
87. "value": 2212
88. },
89. "the_movfn": {
90. "value": 2669.671875
91. }
92. },
93. {
94. "key_as_string": "2019-06-01T00:00:00.000Z",
95. "key": 1559347200000,
96. "doc_count": 2,
97. "the_sum": {
98. "value": 2661
99. },
100. "the_movfn": {
101. "value": 2440.8359375
102. }
103. },
104. {
105. "key_as_string": "2019-07-01T00:00:00.000Z",
106. "key": 1561939200000,
107. "doc_count": 2,
108. "the_sum": {
109. "value": 2887
110. },
111. "the_movfn": {
112. "value": 2550.91796875
113. }
114. },
115. {
116. "key_as_string": "2019-08-01T00:00:00.000Z",
117. "key": 1564617600000,
118. "doc_count": 2,
119. "the_sum": {
120. "value": 2966
121. },
122. "the_movfn": {
123. "value": 2718.958984375
124. }
125. },
126. {
127. "key_as_string": "2019-09-01T00:00:00.000Z",
128. "key": 1567296000000,
129. "doc_count": 2,
130. "the_sum": {
131. "value": 3121
132. },
133. "the_movfn": {
134. "value": 2842.4794921875
135. }
136. }
137. ]
138. }
139. }
140. }
从输出结果中,我们可以看到和之前的是一样的。
Extrapolation/Prediction - 外推/预测
有时,你可能希望根据当前趋势推断数据的行为。 所有移动平均模型都支持 “预测” 模式,该模式尝试根据当前平滑的移动平均预测数据的移动。 根据模型和参数,这些预测可能准确也可能不准确。 Simple、linear 和 ewma 模型都产生 “平坦” 的预测,这些预测收敛于一组中最后一个值的平均值。
你可以使用 predict 参数来指定要附加到系列末尾的预测数量。 这些预测将以与你的存储桶相同的间隔间隔开。 例如,对于线性模型:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "the_movavg": {
17. "moving_avg": {
18. "buckets_path": "total_visits",
19. "window": 30,
20. "model": "linear",
21. "predict": 3
22. }
23. }
24. }
25. }
26. }
27. }
此查询会将 3 个预测添加到存储桶列表的末尾:
1. "aggregations":{
2. "visits_per_month":{
3. "buckets":[
4. ... {
5. "key_as_string":"2019-10-01T00:00:00.000Z",
6. "key":1569888000000,
7. "doc_count":0,
8. "the_movavg":{
9. "value":2687.3924050632913
10. }
11. },
12. {
13. "key_as_string":"2019-11-01T00:00:00.000Z",
14. "key":1572566400000,
15. "doc_count":0,
16. "the_movavg":{
17. "value":2687.3924050632913
18. }
19. },
20. {
21. "key_as_string":"2019-12-01T00:00:00.000Z",
22. "key":1575158400000,
23. "doc_count":0,
24. "the_movavg":{
25. "value":2687.3924050632913
26. }
27. }
28. ]
29. }
30. }
太牛了! 我们已经预测了未来三个月的网站流量。 如你所见,预测是 “平淡的”。 也就是说,它们在所有 3 个月内返回相同的值。 如果你想根据本地或全球恒定趋势进行推断,你应该选择 holt 模型。
Bucket Script Aggregation
此父管道聚合允许执行脚本以对父多桶聚合中的某些指标执行每桶计算。 指定的指标必须是数字,脚本必须返回一个数值。 脚本可以是内联的、文件的或索引的。
例如,这里我们首先对日期直方图生成的桶使用 min 和 max 指标聚合。 然后将生成的最小值和最大值除以桶脚本聚合,以计算每个桶的最小/最大值比率:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "min_visits": {
12. "min": {
13. "field": "visits"
14. }
15. },
16. "max_visits": {
17. "max": {
18. "field": "visits"
19. }
20. },
21. "min_max_ratio": {
22. "bucket_script": {
23. "buckets_path": {
24. "min_visits": "min_visits",
25. "max_visits": "max_visits"
26. },
27. "script": "params.min_visits / params.max_visits"
28. }
29. }
30. }
31. }
32. }
33. }
上面的命令返回的结果为:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2018-10-01T00:00:00.000Z",
7. "key": 1538352000000,
8. "doc_count": 3,
9. "min_visits": {
10. "value": 488
11. },
12. "max_visits": {
13. "value": 789
14. },
15. "min_max_ratio": {
16. "value": 0.6185044359949303
17. }
18. },
19. {
20. "key_as_string": "2018-11-01T00:00:00.000Z",
21. "key": 1541030400000,
22. "doc_count": 3,
23. "min_visits": {
24. "value": 394
25. },
26. "max_visits": {
27. "value": 1299
28. },
29. "min_max_ratio": {
30. "value": 0.30331023864511164
31. }
32. },
33. {
34. "key_as_string": "2018-12-01T00:00:00.000Z",
35. "key": 1543622400000,
36. "doc_count": 3,
37. "min_visits": {
38. "value": 768
39. },
40. "max_visits": {
41. "value": 1194
42. },
43. "min_max_ratio": {
44. "value": 0.6432160804020101
45. }
46. },
47. {
48. "key_as_string": "2019-01-01T00:00:00.000Z",
49. "key": 1546300800000,
50. "doc_count": 2,
51. "min_visits": {
52. "value": 872
53. },
54. "max_visits": {
55. "value": 972
56. },
57. "min_max_ratio": {
58. "value": 0.897119341563786
59. }
60. },
61. {
62. "key_as_string": "2019-02-01T00:00:00.000Z",
63. "key": 1548979200000,
64. "doc_count": 2,
65. "min_visits": {
66. "value": 827
67. },
68. "max_visits": {
69. "value": 1584
70. },
71. "min_max_ratio": {
72. "value": 0.5220959595959596
73. }
74. },
75. {
76. "key_as_string": "2019-03-01T00:00:00.000Z",
77. "key": 1551398400000,
78. "doc_count": 2,
79. "min_visits": {
80. "value": 1499
81. },
82. "max_visits": {
83. "value": 1604
84. },
85. "min_max_ratio": {
86. "value": 0.9345386533665836
87. }
88. },
89. {
90. "key_as_string": "2019-04-01T00:00:00.000Z",
91. "key": 1554076800000,
92. "doc_count": 2,
93. "min_visits": {
94. "value": 1247
95. },
96. "max_visits": {
97. "value": 1392
98. },
99. "min_max_ratio": {
100. "value": 0.8958333333333334
101. }
102. },
103. {
104. "key_as_string": "2019-05-01T00:00:00.000Z",
105. "key": 1556668800000,
106. "doc_count": 2,
107. "min_visits": {
108. "value": 984
109. },
110. "max_visits": {
111. "value": 1228
112. },
113. "min_max_ratio": {
114. "value": 0.8013029315960912
115. }
116. },
117. {
118. "key_as_string": "2019-06-01T00:00:00.000Z",
119. "key": 1559347200000,
120. "doc_count": 2,
121. "min_visits": {
122. "value": 1238
123. },
124. "max_visits": {
125. "value": 1423
126. },
127. "min_max_ratio": {
128. "value": 0.8699929725931131
129. }
130. },
131. {
132. "key_as_string": "2019-07-01T00:00:00.000Z",
133. "key": 1561939200000,
134. "doc_count": 2,
135. "min_visits": {
136. "value": 1388
137. },
138. "max_visits": {
139. "value": 1499
140. },
141. "min_max_ratio": {
142. "value": 0.9259506337558372
143. }
144. },
145. {
146. "key_as_string": "2019-08-01T00:00:00.000Z",
147. "key": 1564617600000,
148. "doc_count": 2,
149. "min_visits": {
150. "value": 1443
151. },
152. "max_visits": {
153. "value": 1523
154. },
155. "min_max_ratio": {
156. "value": 0.9474720945502298
157. }
158. },
159. {
160. "key_as_string": "2019-09-01T00:00:00.000Z",
161. "key": 1567296000000,
162. "doc_count": 2,
163. "min_visits": {
164. "value": 1534
165. },
166. "max_visits": {
167. "value": 1587
168. },
169. "min_max_ratio": {
170. "value": 0.9666036546943919
171. }
172. }
173. ]
174. }
175. }
176. }
聚合计算每个桶的 min_max_ratio 并将结果附加到桶的末尾。更多阅读,请参阅 “Elasticsearch:Bucket script 聚合”。
Bucket Selector Aggregation
有时根据某些条件过滤日期直方图或其他聚合返回的存储桶很有用。 在这种情况下,你可以使用包含脚本的桶选择器聚合来确定当前桶是否应保留在父多桶聚合的输出中。
指定的指标必须是数字,脚本必须返回一个布尔值。 如果脚本语言是表达式,它可以返回一个数字布尔值。 在这种情况下, 0.0 将被评估为 false ,而所有其他值将被评估为 true 。
在下面的示例中,我们首先计算每月访问量的总和,然后评估这个总和是否大于 3000。如果为真,则将桶保留在桶列表中。 否则,它会从最终输出中删除:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "visits_bucket_filter": {
17. "bucket_selector": {
18. "buckets_path": {
19. "total_visits": "total_visits"
20. },
21. "script": "params.total_visits > 3000"
22. }
23. }
24. }
25. }
26. }
27. }
上面命令返回的值为:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2019-03-01T00:00:00.000Z",
7. "key": 1551398400000,
8. "doc_count": 2,
9. "total_visits": {
10. "value": 3103
11. }
12. },
13. {
14. "key_as_string": "2019-09-01T00:00:00.000Z",
15. "key": 1567296000000,
16. "doc_count": 2,
17. "total_visits": {
18. "value": 3121
19. }
20. }
21. ]
22. }
23. }
24. }
Bucket Sort Aggregation
桶排序是父管道聚合,它对父多桶聚合(例如日期直方图)返回的桶进行排序。 你可以指定多个排序字段以及相应的排序顺序。 此外,你可以根据每个桶的 _key、_count 或其子聚合对每个桶进行排序。 你还可以通过设置 from 和 size 参数来截断结果桶。
在下面的示例中,我们根据计算的 total_visits 值对父日期直方图聚合的桶进行排序。 存储桶按降序排序,因此首先返回具有最高 total_visits 值的存储桶。
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "visits_bucket_sort": {
17. "bucket_sort": {
18. "sort": [
19. {
20. "total_visits": {
21. "order": "desc"
22. }
23. }
24. ],
25. "size": 5
26. }
27. }
28. }
29. }
30. }
31. }
上面命令返回的结果为:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2019-09-01T00:00:00.000Z",
7. "key": 1567296000000,
8. "doc_count": 2,
9. "total_visits": {
10. "value": 3121
11. }
12. },
13. {
14. "key_as_string": "2019-03-01T00:00:00.000Z",
15. "key": 1551398400000,
16. "doc_count": 2,
17. "total_visits": {
18. "value": 3103
19. }
20. },
21. {
22. "key_as_string": "2019-08-01T00:00:00.000Z",
23. "key": 1564617600000,
24. "doc_count": 2,
25. "total_visits": {
26. "value": 2966
27. }
28. },
29. {
30. "key_as_string": "2018-12-01T00:00:00.000Z",
31. "key": 1543622400000,
32. "doc_count": 3,
33. "total_visits": {
34. "value": 2949
35. }
36. },
37. {
38. "key_as_string": "2019-07-01T00:00:00.000Z",
39. "key": 1561939200000,
40. "doc_count": 2,
41. "total_visits": {
42. "value": 2887
43. }
44. }
45. ]
46. }
47. }
48. }
如你所见,排序顺序在聚合的排序字段中指定。 我们还将 size 参数设置为 5 以仅返回响应中的前 5 个存储桶:
我们还可以使用此聚合来截断结果桶而不进行任何排序。 为此,只需使用不带排序的 from 和/或 size 参数。
以下示例只是截断结果,以便仅返回第二个和第三个存储桶:
1. GET traffic_stats/_search?filter_path=aggregations
2. {
3. "size": 0,
4. "aggs": {
5. "visits_per_month": {
6. "date_histogram": {
7. "field": "date",
8. "calendar_interval": "month"
9. },
10. "aggs": {
11. "total_visits": {
12. "sum": {
13. "field": "visits"
14. }
15. },
16. "visits_bucket_sort": {
17. "bucket_sort": {
18. "from": 2,
19. "size": 2
20. }
21. }
22. }
23. }
24. }
25. }
上面命令返回的值为:
1. {
2. "aggregations": {
3. "visits_per_month": {
4. "buckets": [
5. {
6. "key_as_string": "2018-12-01T00:00:00.000Z",
7. "key": 1543622400000,
8. "doc_count": 3,
9. "total_visits": {
10. "value": 2949
11. }
12. },
13. {
14. "key_as_string": "2019-01-01T00:00:00.000Z",
15. "key": 1546300800000,
16. "doc_count": 2,
17. "total_visits": {
18. "value": 1844
19. }
20. }
21. ]
22. }
23. }
24. }
结论
就是这样! 至此,我们已经了解了 Elasticsearch 支持的几乎所有管道聚合。 正如我们所见,管道聚合有助于实现涉及中间值和其他聚合生成的桶的复杂计算。 你还可以利用 Elasticsearch 脚本的强大功能对返回的指标进行编程操作。 例如,你可以评估存储桶是否匹配某些规则,并可能计算默认情况下不可用的任何自定义指标(例如,最小/最大比率)。