Агрегация Histogram
Агрегация на основе множества групп значений, которая может применяться к числовым значениям или числовым диапазонам значений, извлечённым из документов. Она динамически создаёт группы фиксированного размера (также известные как интервалы) над значениями. Например, если документы содержат поле, которое хранит цену (числовое), мы можем настроить эту агрегацию на динамическое создание групп с интервалом 5 (в случае цены он может представлять 5 долларов). При выполнении агрегации поле «цена» каждого документа будет вычисляться и округляться до ближайшей группы – например, если цена равна 32, а размер группы равен 5, то округление даст 30, и таким образом документ попадет в группу, связанную с ключом 30. Для большей формализации, вот функция округления, которая используется:
bucket_key = Math.floor((value - offset) / interval) * interval + offset
Для диапазонов значений документ может попадать в несколько групп. Первая группа вычисляется из нижней границы диапазона таким же образом, как и группа для одного значения. Последняя группа вычисляется таким же образом из верхней границы диапазона, и диапазон учитывается во всех группах между этими двумя включительно.
interval должно быть положительной десятичной дробью, а offset должно быть десятичной дробью в [0, interval) (десятичная дробь больше или равна 0 и меньше interval)
Следующий фрагмент «группирует» продукты по их price с интервалом 50:
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50
}
}
},
)
print(resp) response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50
}
}
}
}
)
puts response 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:
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"min_doc_count": 1
}
}
},
)
print(resp) response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
min_doc_count: 1
}
}
}
}
)
puts response 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 и последней группе). Для фильтрации групп необходимо вложить агрегацию гистограммы в агрегацию диапазона filter с соответствующими настройками from/to.
Пример:
resp = client.search(
index="sales",
size="0",
query={
"constant_score": {
"filter": {
"range": {
"price": {
"lte": "500"
}
}
}
}
},
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"extended_bounds": {
"min": 0,
"max": 500
}
}
}
},
)
print(resp) const response = await client.search({
index: "sales",
size: 0,
query: {
constant_score: {
filter: {
range: {
price: {
lte: "500",
},
},
},
},
},
aggs: {
prices: {
histogram: {
field: "price",
interval: 50,
extended_bounds: {
min: 0,
max: 500,
},
},
},
},
});
console.log(response); POST /sales/_search?size=0
{
"query": {
"constant_score": { "filter": { "range": { "price": { "lte": "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 и может ограничить диапазон групп в гистограмме. Это особенно полезно в случае открытых диапазонов данных, которые могут привести к очень большому количеству групп.
Пример:
resp = client.search(
index="sales",
size="0",
query={
"constant_score": {
"filter": {
"range": {
"price": {
"lte": "500"
}
}
}
}
},
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"hard_bounds": {
"min": 100,
"max": 200
}
}
}
},
)
print(resp) const response = await client.search({
index: "sales",
size: 0,
query: {
constant_score: {
filter: {
range: {
price: {
lte: "500",
},
},
},
},
},
aggs: {
prices: {
histogram: {
field: "price",
interval: 50,
hard_bounds: {
min: 100,
max: 200,
},
},
},
},
});
console.log(response); POST /sales/_search?size=0
{
"query": {
"constant_score": { "filter": { "range": { "price": { "lte": "500" } } } }
},
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"hard_bounds": {
"min": 100,
"max": 200
}
}
}
}
} В этом примере, даже если диапазон, указанный в запросе, простирается до 500, гистограмма будет содержать только 2 группы, начинающиеся с 100 и 150. Все остальные группы будут опущены, даже если документы, которые должны попасть в эти группы, присутствуют в результатах.
Порядок
По умолчанию возвращаемые группы отсортированы по их key в восходящем порядке, хотя поведение порядка можно контролировать с помощью настройки order. Поддерживает ту же функциональность order, что и Terms Aggregation.
Смещение
По умолчанию ключи групп начинаются с 0 и затем продолжаются равномерными шагами по interval, например, если интервал равен 10, то первые три группы (предполагая, что в них есть данные) будут [0, 10), [10, 20), [20, 30). Границы групп можно сдвинуть, используя опцию offset.
Это лучше всего проиллюстрировать примером. Если существует 10 документов со значениями от 5 до 14, использование интервала 10 приведёт к двум группам по 5 документов в каждой. Если используется дополнительное смещение 5, будет только одна группа [5, 15), содержащая все 10 документов.
Формат ответа
По умолчанию группы возвращаются в виде упорядоченного массива. Также можно запросить ответ в виде хеша, индексированного ключами групп:
resp = client.search(
index="sales",
size="0",
aggs={
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"keyed": True
}
}
},
)
print(resp) response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
prices: {
histogram: {
field: 'price',
interval: 50,
keyed: true
}
}
}
}
)
puts response 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 определяет, как должны обрабатываться документы, у которых отсутствует значение. По умолчанию они будут игнорироваться, но также можно обработать их так, как будто у них есть значение.
resp = client.search(
index="sales",
size="0",
aggs={
"quantity": {
"histogram": {
"field": "quantity",
"interval": 10,
"missing": 0
}
}
},
)
print(resp) response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
quantity: {
histogram: {
field: 'quantity',
interval: 10,
missing: 0
}
}
}
}
)
puts response 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
}
}
}
} | Документы без значения в поле |
Поля гистограмм
Выполнение агрегации гистограмм по полям гистограмм вычисляет общее количество счётчиков для каждого интервала.
Например, выполнение агрегации гистограмм по следующему индексу, хранящему предварительно агрегированные гистограммы с метриками задержки (в миллисекундах) для разных сетей:
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) 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 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 не поддерживается.
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-aggregations-bucket-histogram-aggregation.html