ElasticSearch基础6:Bucket桶聚合
Bucket aggregations 桶聚合
Bucket聚合不像metrics聚合那样计算字段上的度量,而是创建文档的Bucket。每个bucket都与一个标准(取决于聚合类型)相关联,该标准确定当前上下文中的文档是否“落入”其中。换句话说,bucket有效地定义了文档集。除了bucket本身,bucket聚合还计算并返回“落入”每个bucket的文档数。
与度量聚合不同,Bucket聚合可以保存子聚合。这些子聚合将针对由其“父”bucket聚合创建的bucket进行聚合。
有不同的bucket聚合器,每个都有不同的“bucketing”策略。有的定义单个bucket,有的定义固定数量的多个bucket,还有的在聚合过程中动态创建bucket。
时间相关聚合
Date histogram aggregation
这种多bucket聚合只能与日期或日期范围值一起使用。因为在Elasticsearch中,日期在内部表示为long值,可以使用日期/时间表达式指定间隔。基于时间的数据需要特殊的支持,因为基于时间的间隔并不总是固定的长度。
日历感知间隔可以理解日光节约会改变特定日期的长度,月份有不同的天数,闰秒可以固定在特定年份上。
相比之下,固定间隔总是国际单位的倍数,并且不会根据日历上下文而更改。calendar_interval类型如下:
- minute
- hour
- day
- week
- month
- quarter
- year
数据:
PUT my_index/_doc/1?refresh { "date": "2015-10-01T00:30:00Z" } PUT my_index/_doc/2?refresh { "date": "2015-10-01T01:30:00Z" } PUT my_index/_doc/2?refresh { "date": "2015-10-02T11:05:00Z" }
聚合:
GET my_index/_search?size=0 { "aggs": { "by_day": { "date_histogram": { "field": "date", "calendar_interval": "day" } } } }
按天来聚合数量,结果:
"aggregations" : { "by_day" : { "buckets" : [ { "key_as_string" : "2015-10-01T00:00:00.000Z", "key" : 1443657600000, "doc_count" : 2 }, { "key_as_string" : "2015-10-02T00:00:00.000Z", "key" : 1443744000000, "doc_count" : 1 } ] } }
10-1有2条,10-2有1条
time_zone时区位移1小时:
GET my_index/_search?size=0 { "aggs": { "by_day": { "date_histogram": { "field": "date", "calendar_interval": "day", "time_zone": "-01:00" } } } }
结果:
"aggregations" : { "by_day" : { "buckets" : [ { "key_as_string" : "2015-09-30T00:00:00.000-01:00", "key" : 1443574800000, "doc_count" : 1 }, { "key_as_string" : "2015-10-01T00:00:00.000-01:00", "key" : 1443661200000, "doc_count" : 1 }, { "key_as_string" : "2015-10-02T00:00:00.000-01:00", "key" : 1443747600000, "doc_count" : 1 } ] } }
因为ID为1的记录时间为 2015-10-01T00:30:00Z ,向前1小时,那就是往前一天了,所以9-30就有1条记录了。
移位6小时:
GET my_index/_search?size=0 { "aggs": { "by_day": { "date_histogram": { "field": "date", "calendar_interval": "day", "offset": "+6h" } } } }
结果:
"aggregations" : { "by_day" : { "buckets" : [ { "key_as_string" : "2015-09-30T06:00:00.000Z", "key" : 1443592800000, "doc_count" : 2 }, { "key_as_string" : "2015-10-01T06:00:00.000Z", "key" : 1443679200000, "doc_count" : 0 }, { "key_as_string" : "2015-10-02T06:00:00.000Z", "key" : 1443765600000, "doc_count" : 1 } ] } }
日期范围聚合 Date Range Aggregation
专用于日期值的范围聚合。此聚合与普通范围聚合的主要区别在于,from和to值可以用日期数学表达式表示,还可以指定返回from和to响应字段的日期格式。请注意,此聚合包括每个范围的from值,而不包括to值。
POST /sales/_search?size=0 { "aggs": { "range": { "date_range": { "field": "date", "missing": "1976/11/30", "ranges": [ { "key": "Older", "to": "2016/02/01" }, { "key": "Newer", "from": "2016/02/01", "to" : "now/d" } ] } } } }
聚合早于2016/02/01(不含)的到"Older"
聚合从2016/02/01(包含)到现在(不含)的到"Newer"
更多用法见:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-daterange-aggregation.html
Filter Aggregation
顾名思义,用于过滤后聚合,这将用于将当前聚合上下文缩小到一组特定的文档。
比如只聚合hat类型的平均价格
POST /sales/_search?size=0 { "aggs" : { "t_shirts" : { "filter" : { "term": { "type": "hat" } }, "aggs" : { "avg_price" : { "avg" : { "field" : "price" } } } } } }
结果:
"aggregations" : { "t_shirts" : { "doc_count" : 3, "avg_price" : { "value" : 92.5 } } }
帽子的平均价格92.5,数量有3个
Filters Aggregation
定义一个多bucket聚合,其中每个bucket与一个过滤器关联。每个bucket将收集与其关联筛选器匹配的所有文档。
PUT /logs/_bulk?refresh { "index" : { "_id" : 1 } } { "body" : "warning: page could not be rendered" } { "index" : { "_id" : 2 } } { "body" : "warning:authentication error" } { "index" : { "_id" : 3 } } { "body" : "warning: connection timed out" } { "index" : { "_id" : 4 } } { "body" : "error: database disconnectioned " }
聚合:
GET /logs/_search { "size": 0, "aggs" : { "messages" : { "filters" : { "filters" : { "errors" : { "match" : { "body" : "error" }}, "warnings" : { "match" : { "body" : "warning" }} } } } } }
结果:
"aggregations" : { "messages" : { "buckets" : { "errors" : { "doc_count" : 2 }, "warnings" : { "doc_count" : 2 } } } }
为啥errors有2个?warnings有2个(难道不是3个吗)?想一下为什么。
增加一条记录
PUT logs/_doc/5?refresh { "body": "info: user Bob logged out" }
聚合,增加一个其他项"other_bucket_key"
GET logs/_search { "size": 0, "aggs" : { "messages" : { "filters" : { "other_bucket_key": "other_messages", "filters" : { "errors" : { "match" : { "body" : "error" }}, "warnings" : { "match" : { "body" : "warning" }} } } } } }
结果:
"aggregations" : { "messages" : { "buckets" : { "errors" : { "doc_count" : 2 }, "warnings" : { "doc_count" : 2 }, "other_messages" : { "doc_count" : 1 } } } }
Global Aggregation
定义搜索执行上下文中所有文档的单个存储桶。此上下文由您正在搜索的索引和文档类型定义,但不受搜索查询本身的影响。
也就是说要对索引全部聚合,也聚合单个搜索。
POST /sales/_search?size=0 { "query" : { "match" : { "type" : "hat" } }, "aggs" : { "all_products" : { "global" : {}, "aggs" : { "avg_price" : { "avg" : { "field" : "price" } } } }, "hats": { "avg" : { "field" : "price" } } } }
可以看到,搜索命中3个,全部产品7个
{ "took" : 1, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 3, "relation" : "eq" }, "max_score" : null, "hits" : [ ] }, "aggregations" : { "all_products" : { "doc_count" : 7, "avg_price" : { "value" : 92.25 } }, "hats" : { "value" : 92.5 } } }
间隔直方图聚合 Histogram Aggregation
从文档中提取的数值或数值范围值。它动态地在值上构建固定大小(也称为间隔)的存储桶。例如,如果文档有一个包含价格(数字)的字段,我们可以将此聚合配置为动态构建间隔为5的存储桶(如果价格为5美元)。执行聚合时,将对每个文档的价格字段进行评估,并将其舍入到最接近的存储桶-例如,如果价格为32,存储桶大小为5,则舍入将产生30,因此文档将“落入”与键30关联的存储桶中。为了使其更正式,下面是使用的舍入函数:
bucket_key = Math.floor((value - offset) / interval) * interval + offset
以10元一个间隔进行个数统计
POST /sales/_search?size=0 { "aggs" : { "prices" : { "histogram" : { "field" : "price", "interval" : 10 } } } }
80元(到90元不含)的有2个,90元(到100元不含)的有1个,100元以上的有1个
"aggregations" : { "prices" : { "buckets" : [ { "key" : 80.0, "doc_count" : 2 }, { "key" : 90.0, "doc_count" : 1 }, { "key" : 100.0, "doc_count" : 1 } ] } }
范围聚合 Range Aggregation
允许用户定义一组范围,每个范围代表一个bucket。在聚合过程中,将根据每个bucket范围和“bucket”相关/匹配文档检查从每个文档提取的值。
请注意,此聚合包括每个范围的from值,而不包括to值。
聚合:
GET /sales/_search { "aggs" : { "price_ranges" : { "range" : { "field" : "price", "ranges" : [ { "to" : 80.0 }, { "from" : 80.0, "to" : 90.0 }, { "from" : 90.0 } ] } } } }
小于80的0个,80-90(不含)2个,90(含)以上的2个
"aggregations" : { "price_ranges" : { "buckets" : [ { "key" : "*-80.0", "to" : 80.0, "doc_count" : 0 }, { "key" : "80.0-90.0", "from" : 80.0, "to" : 90.0, "doc_count" : 2 }, { "key" : "90.0-*", "from" : 90.0, "doc_count" : 2 } ] } }
带key标签的写法:
GET /_search { "aggs" : { "price_ranges" : { "range" : { "field" : "price", "keyed" : true, "ranges" : [ { "key" : "cheap", "to" : 100 }, { "key" : "average", "from" : 100, "to" : 200 }, { "key" : "expensive", "from" : 200 } ] } } } }
带状态的范围分组
GET /_search { "aggs" : { "price_ranges" : { "range" : { "field" : "price", "ranges" : [ { "to" : 100 }, { "from" : 100, "to" : 200 }, { "from" : 200 } ] }, "aggs" : { "price_stats" : { "stats" : { "field" : "price" } } } } } }
状态结果:
{ ... "aggregations": { "price_ranges": { "buckets": [ { "key": "*-100.0", "to": 100.0, "doc_count": 2, "price_stats": { "count": 2, "min": 10.0, "max": 50.0, "avg": 30.0, "sum": 60.0 } }, { "key": "100.0-200.0", "from": 100.0, "to": 200.0, "doc_count": 2, "price_stats": { "count": 2, "min": 150.0, "max": 175.0, "avg": 162.5, "sum": 325.0 } }, { "key": "200.0-*", "from": 200.0, "doc_count": 3, "price_stats": { "count": 3, "min": 200.0, "max": 200.0, "avg": 200.0, "sum": 600.0 } } ] } } }
内置排序
这些排序模式是桶 固有的 能力:它们操作桶生成的数据 ,比如 doc_count 。 它们共享相同的语法,但是根据使用桶的不同会有些细微差别。
让我们做一个 terms 聚合但是按 doc_count 值的升序排序:
GET /cars/_search { "size" : 0, "aggs" : { "colors" : { "terms" : { "field" : "color", "order": { "_count" : "asc" } } } } }
首先需要对text字段允许fielddata,运行聚合操作会报错,官方的说法是text是会分词,如果text中一个文本为New York,那么就会被分成2个桶,一个New桶,一个York桶,那么显然不能聚合操作,要么你把该类型替换成keyword类型,因为keyword类型是不会分词的,可以用来做聚合操作。
PUT /cars/_mapping { "properties": { "color": { "type": "text", "fielddata": true } } }
POST /cars/_bulk { "index": {}} { "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" } { "index": {}} { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } { "index": {}} { "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" } { "index": {}} { "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" } { "index": {}} { "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" } { "index": {}} { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } { "index": {}} { "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" } { "index": {}} { "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
用关键字 _count ,我们可以按 doc_count 值的升序排序。
我们为聚合引入了一个 order 对象, 它允许我们可以根据以下几个值中的一个值进行排序:
- _count
- 按文档数排序。对 terms 、 histogram 、 date_histogram 有效。
- _term
- 按词项的字符串值的字母顺序排序。只在 terms 内使用。
- _key
- 按每个桶的键值数值排序(理论上与 _term 类似)。 只在 histogram 和 date_histogram 内使用。
按度量排序
有时,我们会想基于度量计算的结果值进行排序。 在我们的汽车销售分析仪表盘中,我们可能想按照汽车颜色创建一个销售条状图表,但按照汽车平均售价的升序进行排序。
我们可以增加一个度量,再指定 order 参数引用这个度量即可:
GET /cars/_search { "size" : 0, "aggs" : { "colors" : { "terms" : { "field" : "color", "order": { "avg_price" : "asc" } }, "aggs": { "avg_price": { "avg": {"field": "price"} } } } } }
- 计算每种颜色桶的平均售价。
- 桶按照计算平均值的升序排序。
结果:
"aggregations" : { "colors" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "blue", "doc_count" : 2, "avg_price" : { "value" : 20000.0 } }, { "key" : "green", "doc_count" : 2, "avg_price" : { "value" : 21000.0 } }, { "key" : "red", "doc_count" : 4, "avg_price" : { "value" : 32500.0 } } ] } }
我们可以采用这种方式用任何度量排序,只需简单的引用度量的名字。不过有些度量会输出多个值。 extended_stats 度量是一个很好的例子:它输出好几个度量值。
邻接矩阵 Adjacency Matrix Aggregation
返回邻接矩阵形式的桶聚集。该请求提供一个命名筛选器表达式的集合,类似于筛选器聚合请求。响应中的每个bucket表示相交过滤器矩阵中的非空单元格。
给定名为A、B和C的筛选器,响应将返回具有以下名称的存储桶:
A | B | C | |
---|---|---|---|
A |
A |
A&B |
A&C |
B |
B |
B&C |
|
C |
C |
PUT /emails/_bulk?refresh { "index" : { "_id" : 1 } } { "accounts" : ["hillary", "sidney"]} { "index" : { "_id" : 2 } } { "accounts" : ["hillary", "donald"]} { "index" : { "_id" : 3 } } { "accounts" : ["vladimir", "donald"]} { "index" : { "_id" : 4 } } { "accounts" : ["vladimir", "sidney"]}
结果:
"aggregations" : { "interactions" : { "buckets" : [ { "key" : "grpA", "doc_count" : 3 }, { "key" : "grpA&grpB", "doc_count" : 1 }, { "key" : "grpA&grpC", "doc_count" : 1 }, { "key" : "grpB", "doc_count" : 2 }, { "key" : "grpB&grpC", "doc_count" : 1 }, { "key" : "grpC", "doc_count" : 2 } ] } }
重要术语聚合 Significant Terms Aggregation
返回集合中有趣或异常出现的集合。
举一些使用场景:
- 找出爆胎次数不成比例的轮胎制造商
- 找出各个警察部门(或某区域)中不成比例的犯罪率(犯罪指数)
- 找出银行各支行(或某区域、或某时段)中不成比例的客户流失率
举个例子,通过聚合发现:
- 自行车盗窃只占全体警察查案总犯罪的1%(66799/5064554)
- 对英国交通警察部门来说,自行车盗窃只占总犯罪7%(3640/47347)。
这是一个显着的7倍的频率增加,所以这一异常被强调为最高的犯罪类型。结果表明,英国交通警察部队在处理自行车盗窃案是比较牛逼的。
使用查询来发现异常的问题是,它只提供一个子集用于(和总集)比较。
GET /crimes/_search { "query" : { "terms" : {"force" : [ "英国交通警察" ]} }, "aggregations" : { "significant_crime_types" : { "significant_terms" : { "field" : "犯罪类型" } } } }
结果
{ ... "aggregations" : { "significant_crime_types" : { "doc_count": 47347, "bg_count": 5064554, "buckets" : [ { "key": "自行车盗窃", "doc_count": 3640, "score": 0.371235374214817, "bg_count": 66799 } ...其他犯罪类型 ... ] } } }
多集合分析
跨多个类别执行分析的一种更简单的方法是使用“父级聚合”对准备分析的数据进行分段。
使用父聚合进行分段的示例:
GET /crimes/_search
GET /crimes/_search { "aggregations": { "forces": { "terms": {"field": "force"}, "aggregations": { "significant_crime_types": { "significant_terms": {"field": "犯罪类型"} } } } } }
结果:
{ "aggregations": { "forces": { "doc_count_error_upper_bound": 1375, "sum_other_doc_count": 7879845, "buckets": [ { "key": "大都会警察局", "doc_count": 894038, "significant_crime_types": { "doc_count": 894038, "bg_count": 5064554, "buckets": [ { "key": "抢劫", "doc_count": 27617, "score": 0.0599, "bg_count": 53182 } ... ] } }, { "key": "英国交通警察", "doc_count": 47347, "significant_crime_types": { "doc_count": 47347, "bg_count": 5064554, "buckets": [ { "key": "自行车盗窃", "doc_count": 3640, "score": 0.371, "bg_count": 66799 } ... ] } } ] } } }
这样,我们可以使用一个请求对每个警察部队进行异常检测,而无需像第一个例子那样指定单一范围(英国交通警察)
我们可以使用其他形式的顶级聚合来分割我们的数据,例如按地理区域分割,以确定特定犯罪类型的异常热点:
GET /crimes/_search { "aggs": { "hotspots": { "geohash_grid": { "field": "location", "precision": 5 }, "aggs": { "significant_crime_types": { "significant_terms": {"field": "crime_type"} } } } } }
本例使用geohash_grid aggregation(地理网格聚合)创建表示地理区域的结果存储桶,在每个存储桶中,我们可以在这些高度集中的区域(例如)中识别犯罪类型的异常级别。
比如:
- 机场的武器没收数量异常
- 大学里偷自行车数量异常
还有另外一种聚合,即基于时间的顶级细分将有助于确定每个时间点的当前趋势,其中一个简单的术语聚合通常会显示在所有时间段中持续存在的非常流行的“常量”。
返回的分数主要是为了对不同的建议进行合理的排名,而不是为了便于最终用户理解。分数来自当前和背景中的doc频率。简言之,如果一个术语出现在子集和背景中的频率存在显著差异,则该术语被认为是显著的。
重要文本聚合 Significant Text Aggregation
返回集合中出现的有趣或不寻常的自由文本项的聚合。它类似于重要术语聚合,但不同之处在于:
- 它专门设计用于类型文本字段
- 它不需要字段数据或文档值
- 它可以动态地重新分析文本内容,这意味着它还可以过滤掉重复的噪音文本,否则会使统计数据失真。
举例:
- 当用户搜索“禽流感”时建议 "H5N1"
- 为自动新闻分类器建议与股票符号相关的关键字
最流行的词往往是非常无聊的(和,的,我们,我,他们…)。有意义的词是指那些在前景和背景之间受欢迎程度发生了重大变化的词。如果“H5N1”一词仅存在于1000万个文档索引中的5个文档中,则在构成用户搜索结果的100个文档中的4个中找到了这两个文档,它们的搜索结果可能非常重要并且可能非常相关。
例子:
GET news/_search { "query" : { "match" : {"content" : "禽流感"} }, "aggregations" : { "my_sample" : { "sampler" : { "shard_size" : 100 }, "aggregations": { "keywords" : { "significant_text" : { "field" : "content" } } } } } }
结果:
{ "took": 9, "timed_out": false, "_shards": ..., "hits": ..., "aggregations" : { "my_sample": { "doc_count": 100, "keywords" : { "doc_count": 100, "buckets" : [ { "key": "h5n1", "doc_count": 4, "score": 4.71235374214817, "bg_count": 5 } ... ] } } } }
结果表明,h5n1是与禽流感密切相关的几个术语之一。它只在我们的索引中总共出现5次(bg_count背景数据),
但其中4次“幸运地”出现在我们的100份“禽流感”结果文件样本中(doc_count前景数据)。这意味着这是一个和禽流感关联的关键字。
一句话总结,Significant Aggregation用于在浩瀚的数据中找出异常或关联性数据。
目前维护的开源产品:https://gitee.com/475660