範囲集約
ユーザーが一連の範囲を定義できるマルチバケット値ソースに基づく集約で、各範囲はバケットを表します。集約プロセス中に、各ドキュメントから抽出された値は各バケット範囲と照合され、関連する/一致するドキュメントが「バケット」されます。この集約には、各範囲の from
値が含まれ、to
値は除外されることに注意してください。
例:
Python
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response);
コンソール
GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100.0 },
{ "from": 100.0, "to": 200.0 },
{ "from": 200.0 }
]
}
}
}
}
コンソール結果
{
...
"aggregations": {
"price_ranges": {
"buckets": [
{
"key": "*-100.0",
"to": 100.0,
"doc_count": 2
},
{
"key": "100.0-200.0",
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
}
キー付きレスポンス
keyed
フラグを true
に設定すると、各バケットに一意の文字列キーが関連付けられ、範囲が配列ではなくハッシュとして返されます:
Python
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"keyed": True,
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
keyed: true,
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
keyed: true,
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response);
コンソール
GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"keyed": true,
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
}
コンソール結果
{
...
"aggregations": {
"price_ranges": {
"buckets": {
"*-100.0": {
"to": 100.0,
"doc_count": 2
},
"100.0-200.0": {
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
"200.0-*": {
"from": 200.0,
"doc_count": 3
}
}
}
}
}
各範囲のキーをカスタマイズすることも可能です:
Python
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"keyed": True,
"ranges": [
{
"key": "cheap",
"to": 100
},
{
"key": "average",
"from": 100,
"to": 200
},
{
"key": "expensive",
"from": 200
}
]
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
keyed: true,
ranges: [
{
key: 'cheap',
to: 100
},
{
key: 'average',
from: 100,
to: 200
},
{
key: 'expensive',
from: 200
}
]
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
keyed: true,
ranges: [
{
key: "cheap",
to: 100,
},
{
key: "average",
from: 100,
to: 200,
},
{
key: "expensive",
from: 200,
},
],
},
},
},
});
console.log(response);
コンソール
GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"keyed": true,
"ranges": [
{ "key": "cheap", "to": 100 },
{ "key": "average", "from": 100, "to": 200 },
{ "key": "expensive", "from": 200 }
]
}
}
}
}
コンソール結果
{
...
"aggregations": {
"price_ranges": {
"buckets": {
"cheap": {
"to": 100.0,
"doc_count": 2
},
"average": {
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
"expensive": {
"from": 200.0,
"doc_count": 3
}
}
}
}
}
スクリプト
ドキュメント内のデータが集約したい内容と正確に一致しない場合は、ランタイムフィールドを使用してください。たとえば、特定の通貨換算レートを適用する必要がある場合:
Python
resp = client.search(
index="sales",
runtime_mappings={
"price.euros": {
"type": "double",
"script": {
"source": "\n emit(doc['price'].value * params.conversion_rate)\n ",
"params": {
"conversion_rate": 0.835526591
}
}
}
},
aggs={
"price_ranges": {
"range": {
"field": "price.euros",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
body: {
runtime_mappings: {
'price.euros' => {
type: 'double',
script: {
source: "\n emit(doc['price'].value * params.conversion_rate)\n ",
params: {
conversion_rate: 0.835526591
}
}
}
},
aggregations: {
price_ranges: {
range: {
field: 'price.euros',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
runtime_mappings: {
"price.euros": {
type: "double",
script: {
source:
"\n emit(doc['price'].value * params.conversion_rate)\n ",
params: {
conversion_rate: 0.835526591,
},
},
},
},
aggs: {
price_ranges: {
range: {
field: "price.euros",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response);
コンソール
GET sales/_search
{
"runtime_mappings": {
"price.euros": {
"type": "double",
"script": {
"source": """
emit(doc['price'].value * params.conversion_rate)
""",
"params": {
"conversion_rate": 0.835526591
}
}
}
},
"aggs": {
"price_ranges": {
"range": {
"field": "price.euros",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
}
サブ集約
次の例では、ドキュメントを異なるバケットに「バケット」するだけでなく、各価格範囲の価格に関する統計も計算します。
Python
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
},
aggregations: {
price_stats: {
stats: {
field: 'price'
}
}
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
aggs: {
price_stats: {
stats: {
field: "price",
},
},
},
},
},
});
console.log(response);
コンソール
GET sales/_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
}
}
]
}
}
}
ヒストグラムフィールド
ヒストグラムフィールドに対して範囲集約を実行すると、各設定された範囲の合計カウントが計算されます。
これは、ヒストグラムフィールドの値の間で補間することなく行われます。その結果、2つのヒストグラム値の「間」にある範囲を持つことが可能です。結果として得られる範囲バケットは、ドキュメントカウントがゼロになります。
以下のインデックスに対して範囲集約を実行する例を示します。このインデックスは、異なるネットワークのレイテンシメトリック(ミリ秒単位)を持つ事前集約されたヒストグラムを保存しています:
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",
filter_path="aggregations",
aggs={
"latency_ranges": {
"range": {
"field": "latency_histo",
"ranges": [
{
"to": 2
},
{
"from": 2,
"to": 3
},
{
"from": 3,
"to": 10
},
{
"from": 10
}
]
}
}
},
)
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,
filter_path: 'aggregations',
body: {
aggregations: {
latency_ranges: {
range: {
field: 'latency_histo',
ranges: [
{
to: 2
},
{
from: 2,
to: 3
},
{
from: 3,
to: 10
},
{
from: 10
}
]
}
}
}
}
)
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,
filter_path: "aggregations",
aggs: {
latency_ranges: {
range: {
field: "latency_histo",
ranges: [
{
to: 2,
},
{
from: 2,
to: 3,
},
{
from: 3,
to: 10,
},
{
from: 10,
},
],
},
},
},
});
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]
}
}
GET metrics_index/_search?size=0&filter_path=aggregations
{
"aggs": {
"latency_ranges": {
"range": {
"field": "latency_histo",
"ranges": [
{"to": 2},
{"from": 2, "to": 3},
{"from": 3, "to": 10},
{"from": 10}
]
}
}
}
}
range
集約は、values
に基づいて計算された各範囲のカウントを合計し、次の出力を返します:
コンソール結果
{
"aggregations": {
"latency_ranges": {
"buckets": [
{
"key": "*-2.0",
"to": 2.0,
"doc_count": 11
},
{
"key": "2.0-3.0",
"from": 2.0,
"to": 3.0,
"doc_count": 0
},
{
"key": "3.0-10.0",
"from": 3.0,
"to": 10.0,
"doc_count": 55
},
{
"key": "10.0-*",
"from": 10.0,
"doc_count": 31
}
]
}
}
}
範囲集約はバケット集約であり、ドキュメントをバケットに分割するもので、メトリクス集約のようにフィールドに対してメトリクスを計算するのではありません。各バケットは、サブ集約が実行できるドキュメントのコレクションを表します。一方、ヒストグラムフィールドは、単一のフィールド内の複数の値を表す事前集約されたフィールドです:数値データのバケットと各バケットのアイテム/ドキュメントのカウント。この範囲集約が期待する入力(生のドキュメントを期待)とヒストグラムフィールド(要約情報を提供)の間の不一致により、集約の結果は各バケットのドキュメントカウントのみに制限されます。
したがって、ヒストグラムフィールドに対して範囲集約を実行する際には、サブ集約は許可されません。