箱线图聚合

编辑

boxplot 指标聚合,计算从聚合文档中提取的数值的箱线图。这些值可以从文档中的特定数值或 直方图字段 生成。

boxplot 聚合返回用于绘制 箱线图 的基本信息:最小值、最大值、中位数、第一四分位数(第 25 个百分位数)和第三四分位数(第 75 个百分位数)值。

语法

编辑

一个独立的 boxplot 聚合看起来像这样:

{
  "boxplot": {
    "field": "load_time"
  }
}

让我们看一下表示加载时间的箱线图

resp = client.search(
    index="latency",
    size=0,
    aggs={
        "load_time_boxplot": {
            "boxplot": {
                "field": "load_time"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'latency',
  body: {
    size: 0,
    aggregations: {
      load_time_boxplot: {
        boxplot: {
          field: 'load_time'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "latency",
  size: 0,
  aggs: {
    load_time_boxplot: {
      boxplot: {
        field: "load_time",
      },
    },
  },
});
console.log(response);
GET latency/_search
{
  "size": 0,
  "aggs": {
    "load_time_boxplot": {
      "boxplot": {
        "field": "load_time" 
      }
    }
  }
}

字段 load_time 必须是数字字段

响应将如下所示:

{
  ...

 "aggregations": {
    "load_time_boxplot": {
      "min": 0.0,
      "max": 990.0,
      "q1": 167.5,
      "q2": 445.0,
      "q3": 722.5,
      "lower": 0.0,
      "upper": 990.0
    }
  }
}

在这种情况下,下须线值和上须线值等于最小值和最大值。通常,这些值是 1.5 * IQR 范围,也就是说,最接近 q1 - (1.5 * IQR)q3 + (1.5 * IQR) 的值。由于这是一种近似值,给定的值可能实际上不是数据中观察到的值,但应该在它们的合理误差范围内。虽然箱线图聚合不会直接返回异常值,但您可以检查 lower > minupper < max 以查看任一侧是否存在异常值,然后直接查询它们。

脚本

编辑

如果需要为未精确索引的值创建箱线图,则应创建一个 运行时字段 并获取该字段的箱线图。例如,如果您的加载时间以毫秒为单位,但您想要以秒为单位计算的值,请使用运行时字段进行转换

resp = client.search(
    index="latency",
    size=0,
    runtime_mappings={
        "load_time.seconds": {
            "type": "long",
            "script": {
                "source": "emit(doc['load_time'].value / params.timeUnit)",
                "params": {
                    "timeUnit": 1000
                }
            }
        }
    },
    aggs={
        "load_time_boxplot": {
            "boxplot": {
                "field": "load_time.seconds"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'latency',
  body: {
    size: 0,
    runtime_mappings: {
      'load_time.seconds' => {
        type: 'long',
        script: {
          source: "emit(doc['load_time'].value / params.timeUnit)",
          params: {
            "timeUnit": 1000
          }
        }
      }
    },
    aggregations: {
      load_time_boxplot: {
        boxplot: {
          field: 'load_time.seconds'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "latency",
  size: 0,
  runtime_mappings: {
    "load_time.seconds": {
      type: "long",
      script: {
        source: "emit(doc['load_time'].value / params.timeUnit)",
        params: {
          timeUnit: 1000,
        },
      },
    },
  },
  aggs: {
    load_time_boxplot: {
      boxplot: {
        field: "load_time.seconds",
      },
    },
  },
});
console.log(response);
GET latency/_search
{
  "size": 0,
  "runtime_mappings": {
    "load_time.seconds": {
      "type": "long",
      "script": {
        "source": "emit(doc['load_time'].value / params.timeUnit)",
        "params": {
          "timeUnit": 1000
        }
      }
    }
  },
  "aggs": {
    "load_time_boxplot": {
      "boxplot": { "field": "load_time.seconds" }
    }
  }
}

箱线图值(通常)是近似值

编辑

boxplot 指标使用的算法称为 TDigest(由 Ted Dunning 在 使用 T-Digests 计算精确分位数 中引入)。

箱线图和其它百分位聚合也是 非确定性的。这意味着使用相同的数据可能会得到略微不同的结果。

压缩

编辑

近似算法必须在内存利用率和估计精度之间取得平衡。可以使用 compression 参数控制这种平衡

resp = client.search(
    index="latency",
    size=0,
    aggs={
        "load_time_boxplot": {
            "boxplot": {
                "field": "load_time",
                "compression": 200
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'latency',
  body: {
    size: 0,
    aggregations: {
      load_time_boxplot: {
        boxplot: {
          field: 'load_time',
          compression: 200
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "latency",
  size: 0,
  aggs: {
    load_time_boxplot: {
      boxplot: {
        field: "load_time",
        compression: 200,
      },
    },
  },
});
console.log(response);
GET latency/_search
{
  "size": 0,
  "aggs": {
    "load_time_boxplot": {
      "boxplot": {
        "field": "load_time",
        "compression": 200    
      }
    }
  }
}

压缩控制内存使用和近似误差

TDigest 算法使用多个“节点”来近似百分位数 — 可用的节点越多,精度越高(并且内存占用量越大),与数据量成正比。compression 参数将最大节点数限制为 20 * compression

因此,通过增加压缩值,您可以在占用更多内存的情况下提高百分位数的精度。较大的压缩值还会使算法速度变慢,因为底层树数据结构的大小会增长,从而导致更昂贵的操作。默认压缩值为 100

一个“节点”大约使用 32 字节的内存,因此在最坏的情况下(大量数据按排序顺序到达),默认设置将生成一个大小约为 64KB 的 TDigest。实际上,数据往往更随机,TDigest 将使用更少的内存。

执行提示

编辑

TDigest 的默认实现针对性能进行了优化,可以扩展到数百万甚至数十亿个样本值,同时保持可接受的精度水平(在某些情况下,对于数百万个样本,相对误差接近 1%)。可以通过将参数 execution_hint 设置为值 high_accuracy 来使用针对精度优化的实现

resp = client.search(
    index="latency",
    size=0,
    aggs={
        "load_time_boxplot": {
            "boxplot": {
                "field": "load_time",
                "execution_hint": "high_accuracy"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'latency',
  body: {
    size: 0,
    aggregations: {
      load_time_boxplot: {
        boxplot: {
          field: 'load_time',
          execution_hint: 'high_accuracy'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "latency",
  size: 0,
  aggs: {
    load_time_boxplot: {
      boxplot: {
        field: "load_time",
        execution_hint: "high_accuracy",
      },
    },
  },
});
console.log(response);
GET latency/_search
{
  "size": 0,
  "aggs": {
    "load_time_boxplot": {
      "boxplot": {
        "field": "load_time",
        "execution_hint": "high_accuracy"    
      }
    }
  }
}

优化 TDigest 的精度,以牺牲性能为代价

此选项可以提高精度(在某些情况下,对于数百万个样本,相对误差接近 0.01%),但随后百分位查询完成时间会增加 2 倍-10 倍。

缺失值

编辑

missing 参数定义了应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有某个值。

resp = client.search(
    index="latency",
    size=0,
    aggs={
        "grade_boxplot": {
            "boxplot": {
                "field": "grade",
                "missing": 10
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'latency',
  body: {
    size: 0,
    aggregations: {
      grade_boxplot: {
        boxplot: {
          field: 'grade',
          missing: 10
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "latency",
  size: 0,
  aggs: {
    grade_boxplot: {
      boxplot: {
        field: "grade",
        missing: 10,
      },
    },
  },
});
console.log(response);
GET latency/_search
{
  "size": 0,
  "aggs": {
    "grade_boxplot": {
      "boxplot": {
        "field": "grade",
        "missing": 10     
      }
    }
  }
}

grade 字段中没有值的文档将与值为 10 的文档落在同一个桶中。