本文首發於公眾號:Hunter後端
原文連結:es筆記七之聚合操作之桶聚合和矩陣聚合
桶(bucket)聚合並不像指標(metric)聚合一樣在欄位上計算,而是會建立資料的桶,我們可以理解為分組,根據某個欄位進行分組,將符合條件的資料分到同一個組裡。
桶聚合可以有子聚合,意思就是在分組之後,可以在每個組裡再次進行聚合操作,聚合的資料就是每個組的資料。
以下是本篇筆記目錄:
我們可以簡單的先來進行一下桶聚合的操作,比如我們根據 age 欄位對資料進行分組操作:
GET /bank/_search
{
"size": 0,
"aggs": {
"bucket_age": {
"terms": {
"field": "age",
"size": 20
}
}
}
}
返回的資料如下:
{
...
"aggregations" : {
"bucket_age" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 35,
"buckets" : [
{
"key" : 31,
"doc_count" : 61
},
{
"key" : 39,
"doc_count" : 60
},
{
"key" : 26,
"doc_count" : 59
},
...
]
}
}
}
所有的資料在 aggregations.bucket_age.buckets 下,這是一個陣列,key 的內容為 age 的值,doc_count 為該 age 值的資料條數。
其中,bucket_age 為我們定義的桶聚合的名稱。
接下來我們介紹桶聚合和指標聚合的其他操作。
如果我們想針對某特定的資料進行聚合,那麼就涉及資料的過濾,篩選出特定的資料進行聚合。
比如我們想篩選出 gender 的值為 "F" 的資料,然後對其進行取平均數的操作,我們可以使用 filter 來如下操作:
GET /bank/_search
{
"size": 0,
"aggs": {
"bucket_gender": {
"filter": {"term": {"gender.keyword": "F"}},
"aggs": {
"avg_balance": {"avg": {"field": "balance"}}
}
}
}
}
aggs.bucket_gender 我們使用 filter 對資料進行了一個過濾,篩選出 gender 的值為 "F" 的資料。
注意,在這裡,因為我們寫入資料前,沒有預先定義欄位的型別,所以 es 中將其自動轉化成 text 屬性的欄位,所以在查詢的時候用到的是 gender.keyword,意思是對 gender 欄位的內容作為整體進行篩選。
如果本身是 keyword 屬性,就不用加 .keyword 來操作。
與 filter 同級的 aggs,進行鍼對篩選出的資料進行聚合的操作,這裡我們用到的是平均值。
返回的資料如下:
...
"aggregations" : {
"bucket_gender" : {
"doc_count" : 493,
"avg_balance" : {
"value" : 25623.34685598377
}
}
}
}
在上一點我們過濾的是單個條件,gender='F' 的情況,如果我們想要實現多個過濾來操作,可以使用 filters,使用方法也不一樣。
比如我們想分別對 gender 的值為 F 和 M 的資料進行均值操作,我們可以一步步來操作,我們先來通過 filters 實現兩個桶的聚合:
GET /bank/_search
{
"size": 0,
"aggs": {
"bucket_gender": {
"filters": {
"filters": {
"female": {"term": {"gender.keyword": "F"}},
"male": {"term": {"gender.keyword": "M"}}
}
}
}
}
}
返回的資料就是兩個桶,包含了兩類資料的總數:
...
"aggregations" : {
"bucket_gender" : {
"buckets" : {
"female" : {
"doc_count" : 493
},
"male" : {
"doc_count" : 507
}
}
}
}
}
如果想在此基礎上接著對其進行均值計算,和前面的 filter 操作一樣,在第一個 filters 同級的地方,加上我們的指標聚合操作:
GET /bank/_search
{
"size": 0,
"aggs": {
"bucket_gender": {
"filters": {
"filters": {
"female": {"term": {"gender.keyword": "F"}},
"male": {"term": {"gender.keyword": "M"}}
}
},
"aggs": {
"avg_balance": {"avg": {"field": "balance"}}
}
}
}
}
這樣,在返回的桶的資料之內,還包含了一個均值的結果:
...
"aggregations" : {
"bucket_gender" : {
"buckets" : {
"female" : {
"doc_count" : 493,
"avg_balance" : {
"value" : 25623.34685598377
}
},
"male" : {
"doc_count" : 507,
"avg_balance" : {
"value" : 25803.800788954635
}
}
}
}
}
}
這裡我們因為 gender 只有 F 和 M 兩個值,所以沒有第三類資料,對於其他資料,比如 age,有很多值,除了某幾種特定的值外,我們還想獲取剩下的值的資訊,如何操作呢?
這裡使用到 other_bucket_key 這個引數,比如我們除了定義的 female 和 male,我們還定義一個 non_gender 欄位來統計非 M 和 F 的值,我們可以這樣操作:
GET /bank/_search
{
"size": 0,
"aggs": {
"bucket_gender": {
"filters": {
"other_bucket_key": "non_gender",
"filters": {
"female": {"term": {"gender.keyword": "F"}},
"male": {"term": {"gender.keyword": "M"}}
}
}
}
}
}
返回的值如下:
...
"aggregations" : {
"bucket_gender" : {
"buckets" : {
"female" : {
"doc_count" : 493,
"avg_balance" : {
"value" : 25623.34685598377
}
},
"male" : {
"doc_count" : 507,
"avg_balance" : {
"value" : 25803.800788954635
}
},
"non_gender" : {
"doc_count" : 0,
"avg_balance" : {
"value" : null
}
}
}
}
}
}
如果我們要在限定的範圍內進行聚合,但是又想在全域性範圍內獲取聚合資料進行比對。
比如說,我們在 gender='F' 的範圍進行聚合操作:
GET /bank/_search
{
"size": 0,
"query": {"match": {"gender.keyword": "F"}},
"aggs": {
"female_balance_avg": {
"avg": {
"field": "balance"
}
}
}
}
這裡通過 query 操作篩選 gender='F' 的資料,然後對 balance 欄位進行聚合,如果同時我們想要獲取所有資料的 balance 的平均值,我們可以使用 global 來操作,如下:
GET /bank/_search
{
"size": 0,
"query": {"match": {"gender.keyword": "F"}},
"aggs": {
"total_balance_avg": {
"global": {},
"aggs": {
"avg_balance": {
"avg": {"field": "balance"}
}
}
},
"female_balance_avg": {
"avg": {
"field": "balance"
}
}
}
}
這樣就有兩個資料來比對,結果如下:
...
"aggregations" : {
"female_balance_avg" : {
"value" : 25623.34685598377
},
"total_balance_avg" : {
"doc_count" : 1000,
"avg_balance" : {
"value" : 25714.837
}
}
}
}
這是個類似於直方圖的區間桶的聚合操作。
比如對於 age 欄位,我們想以 5 為步長進行聚合,如果 age 欄位在 20-50 之間,那麼返回的資料就會類似於 20-24,25-29,30-34... 以及落在這些區間的資料的數量。
而返回的每條資料並不會是一個區間,而是一個開始的資料,也就是說上面的例子會返回的 key 是 20,25,30 等。
比如我們想對 age 欄位進行直方圖聚合,步長為 5,用到的聚合的欄位為 histogram,範例如下:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_histogram": {
"histogram": {
"field": "age",
"interval": 5
}
}
}
}
在 histogram 聚合欄位下,field 欄位為我們要進行直方圖聚合的欄位,這裡是 age 欄位,interval 欄位為進行劃分的區間,我們定義為 5。
返回的資料如下:
...
"aggregations" : {
"age_histogram" : {
"buckets" : [
{
"key" : 20.0,
"doc_count" : 225
},
{
"key" : 25.0,
"doc_count" : 226
}
...
]
}
}
注意: 如果我們進行聚合的區間,比如說 25-29 之間聚合的資料是 0,那麼 es 還是會返回這個區間,不過 doc_count 是 0,不會存在不返回這個區間 key 的情況。
前面我們說了就算區間 count 數是0,這個區間也會返回,但同時我們也可以規定 min_doc_count 這個引數來返回只有當區間 count 數大於等於這個值的時候才返回資料。
假設 age 的區間資料如下:
20-24:5
25-29:0
30-34:2
...
如果我們設定 min_doc_count=2,那麼返回的區間 25-29則不會被返回,使用範例如下:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_histogram": {
"histogram": {
"field": "age",
"interval": 5,
"min_doc_count": 2
}
}
}
}
返回資料:
...
"aggregations" : {
"age_histogram" : {
"buckets" : [
{
"key" : 20.0,
"doc_count" : 5
},
{
"key" : 30.0,
"doc_count" : 2
},
...
]
}
}
前面介紹的範例中,如果資料在 20-50 之間,那麼返回的區間資料就從 20 開始計數(具體的 key 會根據 interval 的設定不一樣,比如設定 Interval=5,key 就會是 20, 25, 30...,如果是設定 Interval=3,那麼 key 就會是 18, 21, 24...)。
如果我們想從 0 開始計數,即便是 0-20 之間的計數為 0,也想要返回20之前 0-4,5-9 的數,或者想要返回 50 之後的資料,包括 50-54,55-59 這種,我們可以使用extended_bounds.min 和 extended_bounds.max 來限定返回資料的最大最小值,範例如下:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_histogram": {
"histogram": {
"field": "age",
"interval": 5,
"extended_bounds": {
"min": 0,
"max": 90
}
}
}
}
}
這樣返回的資料的區間就會在 0-90 之間,即便在全量資料的範圍之外。
注意: 因為在資料區間之外的資料為 0,想要擴充套件的區間返回顯示,記得要將最小返回計數值 min_doc_count 置為 0。
巢狀聚合,這裡針對的是 es 中資料欄位為陣列,陣列元素裡又巢狀為物件的情況,官方檔案舉了個例子,新建一個 products 的 index,資料結構如下:
PUT /products
{
"mappings": {
"properties" : {
"resellers" : {
"type" : "nested",
"properties" : {
"reseller" : { "type" : "text" },
"price" : { "type" : "double" }
}
}
}
}
}
接下來我們往裡新增兩條條資料:
PUT /products/_doc/0
{
"name": "LED TV",
"resellers": [
{
"reseller": "companyA",
"price": 350
},
{
"reseller": "companyB",
"price": 500
}
]
}
PUT /products/_doc/1
{
"name": "LED TV",
"resellers": [
{
"reseller": "companyA",
"price": 400
},
{
"reseller": "companyB",
"price": 250
}
]
}
然後我們想要在這兩條資料裡的 resellers 陣列欄位裡的四個元素裡獲取 price 欄位最小值,可以通過 nested.path 來指定 resellers 欄位,然後進行聚合,使用範例如下:
GET /products/_search
{
"size": 0,
"query" : {
"match" : { "name" : "led tv" }
},
"aggs" : {
"resellers" : {
"nested" : {
"path" : "resellers"
},
"aggs" : {
"min_price" : { "min" : { "field" : "resellers.price" } }
}
}
}
}
範圍聚合,即 range 聚合。我們可以通過指定範圍來返回各個桶的資料,這個操作和直方圖聚合是類似的,不過這個操作更靈活,聚合的範圍不會寫死。
如果是希望步長固定,我們可以使用直方圖聚合,比如0-4,5-9 這種,如果我們直接想要自定義的 0-7,8-19 這種我們想要定義的可以使用範圍聚合。
還是使用 age 欄位來操作,比如我們想要獲取 小於27,28-35,大於36 這個範圍,我們可以如下操作:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{"to": 27},
{"from": 27, "to": 35},
{"from": 35}
]
}
}
}
}
需要注意的是,from 的引數是開區間的,比如我們這裡 from=27,那麼邏輯就是 >27,如果區間兩邊沒有限制,不填寫相應的 from 和 to 引數即可,返回的 key 也會是 *-27 這種形式。
上面的命令返回的資料如下:r
...
"aggregations" : {
"age_range" : {
"buckets" : [
{
"key" : "*-27.0",
"to" : 27.0,
"doc_count" : 326
},
{
"key" : "27.0-35.0",
"from" : 27.0,
"to" : 35.0,
"doc_count" : 384
},
{
"key" : "35.0-*",
"from" : 35.0,
"doc_count" : 290
}
]
}
}
}
如果想要返回的資料以 key:{} 的形式返回,可以加上 keyed=true 引數:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_range": {
"range": {
"field": "age",
"keyed": true,
"ranges": [
{"to": 27},
{"from": 27, "to": 35},
{"from": 35}
]
}
}
}
}
在上面的桶聚合操作之後,我們還可以對每個桶進行子指標聚合,比如說最大最小值,平均值,或者統計值等,以下是個操作範例:
GET /bank/_search
{
"size": 0,
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{"to": 27},
{"from": 27, "to": 35},
{"from": 35}
]
},
"aggs": {
"age_stats": {
"stats": {
"field": "age"
}
}
}
}
}
}
進行指標聚合的範圍是分到每個桶的資料。
rare terms aggregation,這個的概念大概是這樣的,比如我們根據 age 欄位進行聚合,統計他們在檔案中出現的次數,我們想要獲取出現次數最少的幾個,或者指定出現次數少於 50 的 age 值,就可以用到這個操作。
接下來我們對 age 欄位進行這樣的操作,只獲取出現次數少於 50 的資料,範例如下:
GET /bank/_search
{
"size": 0,
"aggs": {
"rare_age": {
"rare_terms": {
"field": "age",
"max_doc_count": 50
}
}
}
}
這個的關鍵字是 rare_terms,rare_age 是我們指定的聚合名稱,其下 field 是我們進行聚合欄位,在這裡是 age 欄位,max_doc_count 則是我們指定的出現次數最大的值。
返回的資料會按照 doc_count 正序排列返回,大致如下:
...
"aggregations" : {
"rare_age" : {
"buckets" : [
{
"key" : 29,
"doc_count" : 35
},
{
"key" : 27,
"doc_count" : 39
},
{
"key" : 38,
"doc_count" : 39
},
...
我們還可以使用過濾的方式來指定或者排除某些值,這個操作是支援正則的,但經過測試,發現按照官方檔案使用正則的 * 來篩選資料並不能真正起作用,所以這裡我們介紹使用列表來實現過濾。
比如我們指定的 age 範圍是 [29, 27, 24],使用 include:
GET /bank/_search
{
"size": 0,
"aggs": {
"rare_age": {
"rare_terms": {
"field": "age",
"max_doc_count": 51,
"include": [29, 27, 24]
}
}
}
}
如果我們要排除的 age 範圍是 [29, 27, 24],使用 exclude:
GET /bank/_search
{
"size": 0,
"aggs": {
"rare_age": {
"rare_terms": {
"field": "age",
"max_doc_count": 51,
"exclude": [29, 27, 24]
}
}
}
}
矩陣聚合是很小的一部分,這裡直接介紹一下。
前面在指標聚合的介紹中,有一個聚合統計彙總,其中介紹了一個引數是 stats,會返回對應欄位的最大值、最小值、總數等資料,矩陣聚合 matrix 可以理解成是多個欄位的 stats 的集合,會缺少一些統計值,但是返回的值更偏統計學方面的用途。
使用範例如下:
GET /bank/_search
{
"size": 0,
"aggs": {
"field_statis": {
"matrix_stats": {
"fields": ["age", "balance"]
}
}
}
}
返回的資料如下:
...
"aggregations" : {
"field_statis" : {
"doc_count" : 1000,
"fields" : [
{
"name" : "balance",
"count" : 1000,
"mean" : 25714.837000000014,
"variance" : 1.9757153733576667E8,
"skewness" : -0.009992486755643138,
"kurtosis" : 1.8088323899074914,
"covariance" : {
"balance" : 1.9757153733576667E8,
"age" : -2845.650777777781
},
"correlation" : {
"balance" : 1.0,
"age" : -0.033676422195874786
}
},
{
"name" : "age",
"count" : 1000,
...
}
...
其中,各引數的釋義如下:
count: 總數
mean: 平均值
variance: 方差
skewness: 偏度
kurtosis: 峰度
covariance: 協方差
correlation: 與其他欄位的相關性,比如 age 到 age 欄位的相關性就是 1.0
如果想獲取更多相關文章,可掃碼關注閱讀: