聚合aggregations

聚合aggregations

聚合可以讓我們極其方便的實現對數據的統計、分析。例如:

  • 什麼品牌的手機最受歡迎?
  • 這些手機的平均價格、最高價格、最低價格?
  • 這些手機每月的銷售情況如何?

實現這些統計功能的比數據庫的sql要方便的多,而且查詢速度非常快,可以實現實時搜索效果。

1 基本概念

Elasticsearch中的聚合,包含多種類型,最常用的兩種,一個叫,一個叫度量

桶(bucket)

桶的作用,是按照某種方式對數據進行分組,每一組數據在ES中稱爲一個,例如我們根據國籍對人劃分,可以得到中國桶英國桶日本桶……或者我們按照年齡段對人進行劃分:0~10,10~20,20~30,30~40等。

Elasticsearch中提供的劃分桶的方式有很多:

  • Date Histogram Aggregation:根據日期階梯分組,例如給定階梯爲周,會自動每週分爲一組
  • Histogram Aggregation:根據數值階梯分組,與日期類似
  • Terms Aggregation:根據詞條內容分組,詞條內容完全匹配的爲一組
  • Range Aggregation:數值和日期的範圍分組,指定開始和結束,然後按段分組
  • ……

綜上所述,發現bucket aggregations 只負責對數據進行分組,並不進行計算,因此往往bucket中往往會嵌套另一種聚合:metrics aggregations即度量

度量(metrics)

分組完成以後,我們一般會對組中的數據進行聚合運算,例如求平均值、最大、最小、求和等,這些在ES中稱爲度量

比較常用的一些度量聚合方式:

  • Avg Aggregation:求平均值
  • Max Aggregation:求最大值
  • Min Aggregation:求最小值
  • Percentiles Aggregation:求百分比
  • Stats Aggregation:同時返回avg、max、min、sum、count等
  • Sum Aggregation:求和
  • Top hits Aggregation:求前幾
  • Value Count Aggregation:求總數
  • ……

爲了測試聚合,我們先批量導入一些數據

創建索引:

PUT /cars
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "transactions": {
      "properties": {
        "color": {
          "type": "keyword"
        },
        "make": {
          "type": "keyword"
        }
      }
    }
  }
}

注意:在ES中,需要進行聚合、排序、過濾的字段其處理方式比較特殊,因此不能被分詞。這裏我們將color和make這兩個文字類型的字段設置爲keyword類型,這個類型不會被分詞,將來就可以參與聚合

導入數據

POST /cars/transactions/_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" }

2 聚合爲桶

首先,我們按照 汽車的顏色color來劃分

GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            }
        }
    }
}
  • size: 查詢條數,這裏設置爲0,因爲我們不關心搜索到的數據,只關心聚合結果,提高效率
  • aggs:聲明這是一個聚合查詢,是aggregations的縮寫
    • popular_colors:給這次聚合起一個名字,任意。
      • terms:劃分桶的方式,這裏是根據詞條劃分
        • field:劃分桶的字段

結果:

{
  "took": 33,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 8,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "popular_colors": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "red",
          "doc_count": 4
        },
        {
          "key": "blue",
          "doc_count": 2
        },
        {
          "key": "green",
          "doc_count": 2
        }
      ]
    }
  }
}
  • hits:查詢結果爲空,因爲設置了size爲0
  • aggregations:聚合的結果
  • popular_colors:定義的聚合名稱
  • buckets:查找到的桶,每個不同的color字段值都會形成一個桶
    • key:這個桶對應的color字段的值
    • doc_count:這個桶中的文檔數量

通過聚合的結果我們發現,目前紅色的小車比較暢銷!

3 桶內度量

前面的例子告訴我們每個桶裏面的文檔數量,這很有用。 但通常,我們的應用需要提供更復雜的文檔度量。 例如,每種顏色汽車的平均價格是多少?

因此,我們需要告訴Elasticsearch使用哪個字段使用何種度量方式進行運算,這些信息要嵌套在內,度量的運算會基於內的文檔進行

現在,我們爲剛剛的聚合結果添加 求價格平均值的度量:

GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
            }
        }
    }
}
  • aggs:我們在上一個aggs(popular_colors)中添加新的aggs。可見度量也是一個聚合,度量是在桶內的聚合
  • avg_price:聚合的名稱
  • avg:度量的類型,這裏是求平均值
  • field:度量運算的字段

結果:

...
  "aggregations": {
    "popular_colors": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "red",
          "doc_count": 4,
          "avg_price": {
            "value": 32500
          }
        },
        {
          "key": "blue",
          "doc_count": 2,
          "avg_price": {
            "value": 20000
          }
        },
        {
          "key": "green",
          "doc_count": 2,
          "avg_price": {
            "value": 21000
          }
        }
      ]
    }
  }
...

可以看到每個桶中都有自己的avg_price字段,這是度量聚合的結果

4 桶內嵌套桶

剛剛的案例中,我們在桶內嵌套度量運算。事實上桶不僅可以嵌套運算, 還可以再嵌套其它桶。也就是說在每個分組中,再分更多組。

比如:我們想統計每種顏色的汽車中,分別屬於哪個製造商,按照make字段再進行分桶

GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "maker":{
                    "terms":{
                        "field":"make"
                    }
                }
            }
        }
    }
}
  • 原來的color桶和avg計算我們不變
  • maker:在嵌套的aggs下新添一個桶,叫做maker
  • terms:桶的劃分類型依然是詞條
  • filed:這裏根據make字段進行劃分

部分結果:

...
{"aggregations": {
    "popular_colors": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "red",
          "doc_count": 4,
          "maker": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "honda",
                "doc_count": 3
              },
              {
                "key": "bmw",
                "doc_count": 1
              }
            ]
          },
          "avg_price": {
            "value": 32500
          }
        },
        {
          "key": "blue",
          "doc_count": 2,
          "maker": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "ford",
                "doc_count": 1
              },
              {
                "key": "toyota",
                "doc_count": 1
              }
            ]
          },
          "avg_price": {
            "value": 20000
          }
        },
        {
          "key": "green",
          "doc_count": 2,
          "maker": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "ford",
                "doc_count": 1
              },
              {
                "key": "toyota",
                "doc_count": 1
              }
            ]
          },
          "avg_price": {
            "value": 21000
          }
        }
      ]
    }
  }
}
...
  • 我們可以看到,新的聚合maker被嵌套在原來每一個color的桶中。
  • 每個顏色下面都根據 make字段進行了分組
  • 我們能讀取到的信息:
    • 紅色車共有4輛
    • 紅色車的平均售價是 $32,500 美元。
    • 其中3輛是 Honda 本田製造,1輛是 BMW 寶馬製造。

5.劃分桶的其它方式

前面講了,劃分桶的方式有很多,例如:

  • Date Histogram Aggregation:根據日期階梯分組,例如給定階梯爲周,會自動每週分爲一組
  • Histogram Aggregation:根據數值階梯分組,與日期類似
  • Terms Aggregation:根據詞條內容分組,詞條內容完全匹配的爲一組
  • Range Aggregation:數值和日期的範圍分組,指定開始和結束,然後按段分組

剛剛的案例中,我們採用的是Terms Aggregation,即根據詞條劃分桶。

接下來,我們再學習幾個比較實用的:

5.1.階梯分桶Histogram

原理:

histogram是把數值類型的字段,按照一定的階梯大小進行分組。你需要指定一個階梯值(interval)來劃分階梯大小。

舉例:

比如你有價格字段,如果你設定interval的值爲200,那麼階梯就會是這樣的:

0,200,400,600,…

上面列出的是每個階梯的key,也是區間的啓點。

如果一件商品的價格是450,會落入哪個階梯區間呢?計算公式如下:

bucket_key = Math.floor((value - offset) / interval) * interval + offset

value:就是當前數據的值,本例中是450

offset:起始偏移量,默認爲0

interval:階梯間隔,比如200

因此你得到的key = Math.floor((450 - 0) / 200) * 200 + 0 = 400

操作一下:

比如,我們對汽車的價格進行分組,指定間隔interval爲5000:

GET /cars/_search
{
  "size":0,
  "aggs":{
    "price":{
      "histogram": {
        "field": "price",
        "interval": 5000
      }
    }
  }
}

結果:

{
  "took": 21,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 8,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "price": {
      "buckets": [
        {
          "key": 10000,
          "doc_count": 2
        },
        {
          "key": 15000,
          "doc_count": 1
        },
        {
          "key": 20000,
          "doc_count": 2
        },
        {
          "key": 25000,
          "doc_count": 1
        },
        {
          "key": 30000,
          "doc_count": 1
        },
        {
          "key": 35000,
          "doc_count": 0
        },
        {
          "key": 40000,
          "doc_count": 0
        },
        {
          "key": 45000,
          "doc_count": 0
        },
        {
          "key": 50000,
          "doc_count": 0
        },
        {
          "key": 55000,
          "doc_count": 0
        },
        {
          "key": 60000,
          "doc_count": 0
        },
        {
          "key": 65000,
          "doc_count": 0
        },
        {
          "key": 70000,
          "doc_count": 0
        },
        {
          "key": 75000,
          "doc_count": 0
        },
        {
          "key": 80000,
          "doc_count": 1
        }
      ]
    }
  }
}

你會發現,中間有大量的文檔數量爲0 的桶,看起來很醜。

我們可以增加一個參數min_doc_count爲1,來約束最少文檔數量爲1,這樣文檔數量爲0的桶會被過濾

示例:

GET /cars/_search
{
  "size":0,
  "aggs":{
    "price":{
      "histogram": {
        "field": "price",
        "interval": 5000,
        "min_doc_count": 1
      }
    }
  }
}

結果:

{
  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 8,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "price": {
      "buckets": [
        {
          "key": 10000,
          "doc_count": 2
        },
        {
          "key": 15000,
          "doc_count": 1
        },
        {
          "key": 20000,
          "doc_count": 2
        },
        {
          "key": 25000,
          "doc_count": 1
        },
        {
          "key": 30000,
          "doc_count": 1
        },
        {
          "key": 80000,
          "doc_count": 1
        }
      ]
    }
  }
}

完美,!

如果你用kibana將結果變爲柱形圖,會更好看:
在這裏插入圖片描述

5.2.範圍分桶range

範圍分桶與階梯分桶類似,也是把數字按照階段進行分組,只不過range方式需要你自己指定每一組的起始和結束大小。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章