ヒストグラム集約
文書から抽出された数値または数値範囲の値に適用できるマルチバケット値ソースに基づく集約です。値に対して固定サイズ(別名:間隔)のバケットを動的に構築します。たとえば、文書に価格(数値)を保持するフィールドがある場合、この集約を構成して、間隔 5
でバケットを動的に構築できます(価格の場合、$5を表すことがあります)。集約が実行されると、各文書の価格フィールドが評価され、最も近いバケットに切り捨てられます。たとえば、価格が 32
でバケットサイズが 5
の場合、切り捨ては 30
になり、したがって文書はキー 30
に関連付けられたバケットに「落ちる」ことになります。これをより正式にするために、使用される切り捨て関数は次のとおりです:
Java
bucket_key = Math.floor((value - offset) / interval) * interval + offset
範囲値の場合、文書は複数のバケットに落ちることがあります。最初のバケットは、単一の値のバケットが計算されるのと同じ方法で、範囲の下限から計算されます。最終バケットは、範囲の上限から同じ方法で計算され、その範囲はその2つの間およびそれを含むすべてのバケットでカウントされます。
interval
は正の小数でなければならず、offset
は [0, interval)
の小数でなければなりません(0
以上かつ interval
未満の小数)
次のスニペットは、price
によって製品を 50
の間隔で「バケット化」します:
Python
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
size: 0,
aggs: {
prices: {
histogram: {
field: "price",
interval: 50,
},
},
},
});
console.log(response);
コンソール
POST /sales/_search?size=0
{
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50
}
}
}
}
コンソール-結果
{
...
"aggregations": {
"prices": {
"buckets": [
{
"key": 0.0,
"doc_count": 1
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 100.0,
"doc_count": 0
},
{
"key": 150.0,
"doc_count": 2
},
{
"key": 200.0,
"doc_count": 3
}
]
}
}
}
最小文書数
上記の応答は、[100, 150)
の範囲内に価格がある文書がないことを示しています。デフォルトでは、応答はヒストグラムのギャップを空のバケットで埋めます。それを変更し、min_doc_count
設定のおかげで、より高い最小カウントのバケットを要求することが可能です:
Python
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"min_doc_count": 1
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
min_doc_count: 1
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
size: 0,
aggs: {
prices: {
histogram: {
field: "price",
interval: 50,
min_doc_count: 1,
},
},
},
});
console.log(response);
コンソール
POST /sales/_search?size=0
{
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"min_doc_count": 1
}
}
}
}
コンソール-結果
{
...
"aggregations": {
"prices": {
"buckets": [
{
"key": 0.0,
"doc_count": 1
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 150.0,
"doc_count": 2
},
{
"key": 200.0,
"doc_count": 3
}
]
}
}
}
デフォルトでは、histogram
はデータ自体の範囲内のすべてのバケットを返します。つまり、最小値を持つ文書(ヒストグラムに基づく)は最小バケット(最小キーを持つバケット)を決定し、最大値を持つ文書は最大バケット(最大キーを持つバケット)を決定します。空のバケットを要求する際、これは混乱を引き起こすことがよくあります。特に、データがフィルタリングされている場合にそうです。
なぜそうなるのかを理解するために、例を見てみましょう:
リクエストをフィルタリングして、0
と 500
の間の値を持つすべての文書を取得し、さらに価格ごとにヒストグラムを使用してデータをスライスしたいとします。間隔は 50
です。また、空のバケットも取得したいので、"min_doc_count" : 0
を指定します。すべての製品(文書)の価格が 100
より高い場合、最初に取得するバケットはキーが 100
のものになります。これは混乱を引き起こします。なぜなら、多くの場合、0 - 100
の間のバケットも取得したいからです。
extended_bounds
設定を使用すると、特定の min
値でヒストグラム集約を「強制的に」開始し、max
値までバケットを構築し続けることができます(文書がもう存在しなくても)。extended_bounds
を使用するのは、min_doc_count
が 0 の場合にのみ意味があります(min_doc_count
が 0 より大きい場合、空のバケットは決して返されません)。
(名前が示すように)extended_bounds
はバケットをフィルタリングしていないことに注意してください。つまり、extended_bounds.min
が文書から抽出された値よりも高い場合、文書は最初のバケットを決定します(extended_bounds.max
と最後のバケットについても同様です)。バケットをフィルタリングするには、適切な from
/to
設定を持つ範囲 filter
集約の下にヒストグラム集約をネストする必要があります。
例:
Python
resp = client.search(
index="sales",
size="0",
query={
"constant_score": {
"filter": {
"range": {
"price": {
"to": "500"
}
}
}
}
},
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"extended_bounds": {
"min": 0,
"max": 500
}
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
query: {
constant_score: {
filter: {
range: {
price: {
to: '500'
}
}
}
}
},
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
extended_bounds: {
min: 0,
max: 500
}
}
}
}
}
)
puts response
コンソール
POST /sales/_search?size=0
{
"query": {
"constant_score": { "filter": { "range": { "price": { "to": "500" } } } }
},
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"extended_bounds": {
"min": 0,
"max": 500
}
}
}
}
}
範囲を集約する場合、バケットは返された文書の値に基づいています。これは、応答にクエリの範囲外のバケットが含まれる可能性があることを意味します。たとえば、クエリが100より大きい値を探していて、範囲が50から150までで、間隔が50の場合、その文書は3つのバケット - 50、100、および150に入ります。一般的に、クエリと集約のステップは独立していると考えるのが最善です。クエリは文書のセットを選択し、その後、集約は選択された方法に関係なくそれらの文書をバケット化します。詳細と例については、範囲フィールドのバケット化に関する注意を参照してください。
hard_bounds
は extended_bounds
の対となり、ヒストグラム内のバケットの範囲を制限できます。これは、非常に多くのバケットを生成する可能性のあるオープン データ範囲 の場合に特に便利です。
例:
Python
resp = client.search(
index="sales",
size="0",
query={
"constant_score": {
"filter": {
"range": {
"price": {
"to": "500"
}
}
}
}
},
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"hard_bounds": {
"min": 100,
"max": 200
}
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
query: {
constant_score: {
filter: {
range: {
price: {
to: '500'
}
}
}
}
},
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
hard_bounds: {
min: 100,
max: 200
}
}
}
}
}
)
puts response
コンソール
POST /sales/_search?size=0
{
"query": {
"constant_score": { "filter": { "range": { "price": { "to": "500" } } } }
},
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"hard_bounds": {
"min": 100,
"max": 200
}
}
}
}
}
この例では、クエリで指定された範囲が500までであるにもかかわらず、ヒストグラムには100と150から始まる2つのバケットのみが含まれます。他のすべてのバケットは、省略されます。たとえそのバケットに入るべき文書が結果に存在していても。
順序
デフォルトでは、返されたバケットはその key
に従って昇順にソートされますが、順序の動作は order
設定を使用して制御できます。Terms Aggregation
と同じ order
機能をサポートします。
オフセット
デフォルトでは、バケットキーは0から始まり、その後 interval
の均等な間隔で続きます。たとえば、間隔が 10
の場合、最初の3つのバケット(それらの中にデータがあると仮定すると)は [0, 10)
、[10, 20)
、[20, 30)
になります。バケットの境界は、offset
オプションを使用してシフトできます。
これは、例を使って最もよく説明できます。値が5から14までの10の文書がある場合、間隔 10
を使用すると、各5文書の2つのバケットが生成されます。追加のオフセット 5
を使用すると、すべての10の文書を含む単一のバケット [5, 15)
のみが生成されます。
応答形式
デフォルトでは、バケットは順序付き配列として返されます。バケットキーによってキー付けされたハッシュとして応答を要求することも可能です:
Python
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"keyed": True
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
keyed: true
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
size: 0,
aggs: {
prices: {
histogram: {
field: "price",
interval: 50,
keyed: true,
},
},
},
});
console.log(response);
コンソール
POST /sales/_search?size=0
{
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"keyed": true
}
}
}
}
コンソール-結果
{
...
"aggregations": {
"prices": {
"buckets": {
"0.0": {
"key": 0.0,
"doc_count": 1
},
"50.0": {
"key": 50.0,
"doc_count": 1
},
"100.0": {
"key": 100.0,
"doc_count": 0
},
"150.0": {
"key": 150.0,
"doc_count": 2
},
"200.0": {
"key": 200.0,
"doc_count": 3
}
}
}
}
}
欠損値
missing
パラメータは、値が欠損している文書がどのように扱われるべきかを定義します。デフォルトでは無視されますが、値があるかのように扱うことも可能です。
Python
resp = client.search(
index="sales",
size="0",
aggs={
"quantity": {
"histogram": {
"field": "quantity",
"interval": 10,
"missing": 0
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
quantity: {
histogram: {
field: 'quantity',
interval: 10,
missing: 0
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
size: 0,
aggs: {
quantity: {
histogram: {
field: "quantity",
interval: 10,
missing: 0,
},
},
},
});
console.log(response);
コンソール
POST /sales/_search?size=0
{
"aggs": {
"quantity": {
"histogram": {
"field": "quantity",
"interval": 10,
"missing": 0
}
}
}
}
quantity フィールドに値がない文書は、値が 0 である文書と同じバケットに入ります。 |
ヒストグラムフィールド
ヒストグラムフィールドに対してヒストグラム集約を実行すると、各間隔の合計カウントが計算されます。
たとえば、異なるネットワークのレイテンシメトリック(ミリ秒)を持つ事前集約されたヒストグラムを格納する次のインデックスに対してヒストグラム集約を実行します:
Python
resp = client.indices.create(
index="metrics_index",
mappings={
"properties": {
"network": {
"properties": {
"name": {
"type": "keyword"
}
}
},
"latency_histo": {
"type": "histogram"
}
}
},
)
print(resp)
resp1 = client.index(
index="metrics_index",
id="1",
refresh=True,
document={
"network.name": "net-1",
"latency_histo": {
"values": [
1,
3,
8,
12,
15
],
"counts": [
3,
7,
23,
12,
6
]
}
},
)
print(resp1)
resp2 = client.index(
index="metrics_index",
id="2",
refresh=True,
document={
"network.name": "net-2",
"latency_histo": {
"values": [
1,
6,
8,
12,
14
],
"counts": [
8,
17,
8,
7,
6
]
}
},
)
print(resp2)
resp3 = client.search(
index="metrics_index",
size="0",
aggs={
"latency_buckets": {
"histogram": {
"field": "latency_histo",
"interval": 5
}
}
},
)
print(resp3)
Ruby
response = client.indices.create(
index: 'metrics_index',
body: {
mappings: {
properties: {
network: {
properties: {
name: {
type: 'keyword'
}
}
},
latency_histo: {
type: 'histogram'
}
}
}
}
)
puts response
response = client.index(
index: 'metrics_index',
id: 1,
refresh: true,
body: {
'network.name' => 'net-1',
latency_histo: {
values: [
1,
3,
8,
12,
15
],
counts: [
3,
7,
23,
12,
6
]
}
}
)
puts response
response = client.index(
index: 'metrics_index',
id: 2,
refresh: true,
body: {
'network.name' => 'net-2',
latency_histo: {
values: [
1,
6,
8,
12,
14
],
counts: [
8,
17,
8,
7,
6
]
}
}
)
puts response
response = client.search(
index: 'metrics_index',
size: 0,
body: {
aggregations: {
latency_buckets: {
histogram: {
field: 'latency_histo',
interval: 5
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "metrics_index",
mappings: {
properties: {
network: {
properties: {
name: {
type: "keyword",
},
},
},
latency_histo: {
type: "histogram",
},
},
},
});
console.log(response);
const response1 = await client.index({
index: "metrics_index",
id: 1,
refresh: "true",
document: {
"network.name": "net-1",
latency_histo: {
values: [1, 3, 8, 12, 15],
counts: [3, 7, 23, 12, 6],
},
},
});
console.log(response1);
const response2 = await client.index({
index: "metrics_index",
id: 2,
refresh: "true",
document: {
"network.name": "net-2",
latency_histo: {
values: [1, 6, 8, 12, 14],
counts: [8, 17, 8, 7, 6],
},
},
});
console.log(response2);
const response3 = await client.search({
index: "metrics_index",
size: 0,
aggs: {
latency_buckets: {
histogram: {
field: "latency_histo",
interval: 5,
},
},
},
});
console.log(response3);
コンソール
PUT metrics_index
{
"mappings": {
"properties": {
"network": {
"properties": {
"name": {
"type": "keyword"
}
}
},
"latency_histo": {
"type": "histogram"
}
}
}
}
PUT metrics_index/_doc/1?refresh
{
"network.name" : "net-1",
"latency_histo" : {
"values" : [1, 3, 8, 12, 15],
"counts" : [3, 7, 23, 12, 6]
}
}
PUT metrics_index/_doc/2?refresh
{
"network.name" : "net-2",
"latency_histo" : {
"values" : [1, 6, 8, 12, 14],
"counts" : [8, 17, 8, 7, 6]
}
}
POST /metrics_index/_search?size=0
{
"aggs": {
"latency_buckets": {
"histogram": {
"field": "latency_histo",
"interval": 5
}
}
}
}
histogram
集約は、values
に基づいて計算された各間隔のカウントを合計し、次の出力を返します:
コンソール-結果
{
...
"aggregations": {
"latency_buckets": {
"buckets": [
{
"key": 0.0,
"doc_count": 18
},
{
"key": 5.0,
"doc_count": 48
},
{
"key": 10.0,
"doc_count": 25
},
{
"key": 15.0,
"doc_count": 6
}
]
}
}
}
ヒストグラム集約はバケット集約であり、文書をバケットに分割します。これは、メトリック集約がフィールドに対してメトリックを計算するのとは異なります。各バケットは、サブ集約が実行できる文書のコレクションを表します。一方、ヒストグラムフィールドは、単一のフィールド内の複数の値を表す事前集約されたフィールドです:数値データのバケットと各バケットのアイテム/文書のカウント。このヒストグラム集約の期待される入力(生の文書を期待)とヒストグラムフィールド(要約情報を提供)の間の不一致は、集約の結果を各バケットの文書カウントのみに制限します。
したがって、ヒストグラムフィールドに対してヒストグラム集約を実行する場合、サブ集約は許可されていません。
また、ヒストグラムフィールドに対してヒストグラム集約を実行する場合、missing
パラメータはサポートされていません。