Skip to content

Implement Boxplot aggregation #4592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/aggregations.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The values are typically extracted from the fields of the document (using the fi

* <<average-aggregation-usage,Average Aggregation Usage>>

* <<boxplot-aggregation-usage,Boxplot Aggregation Usage>>

* <<cardinality-aggregation-usage,Cardinality Aggregation Usage>>

* <<extended-stats-aggregation-usage,Extended Stats Aggregation Usage>>
Expand Down Expand Up @@ -74,6 +76,8 @@ See the Elasticsearch documentation on {ref_current}/search-aggregations-metrics

include::aggregations/metric/average/average-aggregation-usage.asciidoc[]

include::aggregations/metric/boxplot/boxplot-aggregation-usage.asciidoc[]

include::aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc[]

include::aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/master

:github: https://github.com/elastic/elasticsearch-net

:nuget: https://www.nuget.org/packages

////
IMPORTANT NOTE
==============
This file has been generated from https://github.com/elastic/elasticsearch-net/tree/master/src/Tests/Tests/Aggregations/Metric/Boxplot/BoxplotAggregationUsageTests.cs.
If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file,
please modify the original csharp file found at the link and submit the PR with that change. Thanks!
////

[[boxplot-aggregation-usage]]
=== Boxplot Aggregation Usage

A boxplot metrics aggregation that computes boxplot of numeric values extracted from the aggregated documents.
These values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents.

boxplot aggregation returns essential information for making a box plot: minimum, maximum median, first quartile (25th percentile)
and third quartile (75th percentile) values.

Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-metrics-boxplot-aggregation.html[Boxplot Aggregation]

==== Fluent DSL example

[source,csharp]
----
a => a
.Boxplot("boxplot_commits", plot => plot
.Meta(m => m
.Add("foo", "bar")
)
.Field(p => p.NumberOfCommits)
.Missing(10)
.Compression(100)
)
----

==== Object Initializer syntax example

[source,csharp]
----
new BoxplotAggregation("boxplot_commits", Field<Project>(p => p.NumberOfCommits))
{
Meta = new Dictionary<string, object>
{
{ "foo", "bar" }
},
Missing = 10,
Compression = 100
}
----

[source,javascript]
.Example json output
----
{
"boxplot_commits": {
"meta": {
"foo": "bar"
},
"boxplot": {
"field": "numberOfCommits",
"missing": 10.0,
"compression": 100.0
}
}
}
----

==== Handling Responses

[source,csharp]
----
response.ShouldBeValid();
var boxplot = response.Aggregations.Boxplot("boxplot_commits");
boxplot.Should().NotBeNull();
boxplot.Min.Should().BeGreaterOrEqualTo(0);
boxplot.Max.Should().BeGreaterOrEqualTo(0);
boxplot.Q1.Should().BeGreaterOrEqualTo(0);
boxplot.Q2.Should().BeGreaterOrEqualTo(0);
boxplot.Q3.Should().BeGreaterOrEqualTo(0);
boxplot.Meta.Should().NotBeNull().And.HaveCount(1);
boxplot.Meta["foo"].Should().Be("bar");
----

2 changes: 2 additions & 0 deletions src/Nest/Aggregations/AggregateDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ public CompositeBucketAggregate Composite(string key)

public ValueAggregate MedianAbsoluteDeviation(string key) => TryGet<ValueAggregate>(key);

public BoxplotAggregate Boxplot(string key) => TryGet<BoxplotAggregate>(key);

private TAggregate TryGet<TAggregate>(string key) where TAggregate : class, IAggregate =>
BackingDictionary.TryGetValue(key, out var agg) ? agg as TAggregate : null;

Expand Down
31 changes: 31 additions & 0 deletions src/Nest/Aggregations/AggregateFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal class AggregateFormatter : IJsonFormatter<IAggregate>
{ Parser.Hits, 8 },
{ Parser.Location, 9 },
{ Parser.Fields, 10 },
{ Parser.Min, 11 },
{ Parser.Top, 12 },
};

Expand Down Expand Up @@ -152,6 +153,9 @@ private IAggregate ReadAggregate(ref JsonReader reader, IJsonFormatterResolver f
case 10:
aggregate = GetMatrixStatsAggregate(ref reader, formatterResolver, meta);
break;
case 11:
aggregate = GetBoxplotAggregate(ref reader, formatterResolver, meta);
break;
case 12:
aggregate = GetTopMetricsAggregate(ref reader, formatterResolver, meta);
break;
Expand Down Expand Up @@ -216,6 +220,32 @@ private IAggregate GetMatrixStatsAggregate(ref JsonReader reader, IJsonFormatter
return matrixStats;
}

private IAggregate GetBoxplotAggregate(ref JsonReader reader, IJsonFormatterResolver formatterResolver, IReadOnlyDictionary<string, object> meta)
{
var boxplot = new BoxplotAggregate
{
Min = reader.ReadDouble(),
Meta = meta
};
reader.ReadNext(); // ,
reader.ReadNext(); // "max"
reader.ReadNext(); // :
boxplot.Max = reader.ReadDouble();
reader.ReadNext(); // ,
reader.ReadNext(); // "q1"
reader.ReadNext(); // :
boxplot.Q1 = reader.ReadDouble();
reader.ReadNext(); // ,
reader.ReadNext(); // "q2"
reader.ReadNext(); // :
boxplot.Q2 = reader.ReadDouble();
reader.ReadNext(); // ,
reader.ReadNext(); // "q3"
reader.ReadNext(); // :
boxplot.Q3 = reader.ReadDouble();
return boxplot;
}

private IAggregate GetTopMetricsAggregate(ref JsonReader reader, IJsonFormatterResolver formatterResolver, IReadOnlyDictionary<string, object> meta)
{
var topMetrics = new TopMetricsAggregate { Meta = meta };
Expand Down Expand Up @@ -995,6 +1025,7 @@ private static class Parser
public const string Location = "location";
public const string MaxScore = "max_score";
public const string Meta = "meta";
public const string Min = "min";
public const string MinLength = "min_length";

public const string Score = "score";
Expand Down
14 changes: 14 additions & 0 deletions src/Nest/Aggregations/AggregationContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public interface IAggregationContainer
[DataMember(Name = "avg_bucket")]
IAverageBucketAggregation AverageBucket { get; set; }

[DataMember(Name = "boxplot")]
IBoxplotAggregation Boxplot { get; set; }

[DataMember(Name = "bucket_script")]
IBucketScriptAggregation BucketScript { get; set; }

Expand Down Expand Up @@ -277,6 +280,9 @@ public class AggregationContainer : IAggregationContainer

public IAverageBucketAggregation AverageBucket { get; set; }

/// <inheritdoc cref="IBoxplotAggregation"/>
public IBoxplotAggregation Boxplot { get; set; }

public IBucketScriptAggregation BucketScript { get; set; }

public IBucketSelectorAggregation BucketSelector { get; set; }
Expand Down Expand Up @@ -427,6 +433,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta

IAverageBucketAggregation IAggregationContainer.AverageBucket { get; set; }

IBoxplotAggregation IAggregationContainer.Boxplot { get; set; }

IBucketScriptAggregation IAggregationContainer.BucketScript { get; set; }

IBucketSelectorAggregation IAggregationContainer.BucketSelector { get; set; }
Expand Down Expand Up @@ -838,6 +846,12 @@ Func<StringStatsAggregationDescriptor<T>, IStringStatsAggregation> selector
) =>
_SetInnerAggregation(name, selector, (a, d) => a.StringStats = d);

/// <inheritdoc cref="IBoxplotAggregation"/>
public AggregationContainerDescriptor<T> Boxplot(string name,
Func<BoxplotAggregationDescriptor<T>, IBoxplotAggregation> selector
) =>
_SetInnerAggregation(name, selector, (a, d) => a.Boxplot = d);

/// <inheritdoc cref="ITopMetricsAggregation"/>
public AggregationContainerDescriptor<T> TopMetrics(string name,
Func<TopMetricsAggregationDescriptor<T>, ITopMetricsAggregation> selector
Expand Down
14 changes: 14 additions & 0 deletions src/Nest/Aggregations/Metric/Boxplot/BoxplotAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Nest {
public class BoxplotAggregate : MetricAggregateBase
{
public double Min { get; set; }

public double Max { get; set; }

public double Q1 { get; set; }

public double Q2 { get; set; }

public double Q3 { get; set; }
}
}
51 changes: 51 additions & 0 deletions src/Nest/Aggregations/Metric/Boxplot/BoxplotAggregation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Runtime.Serialization;
using Elasticsearch.Net.Utf8Json;

namespace Nest
{
/// <summary>
/// A metrics aggregation that computes boxplot of numeric values extracted from the aggregated documents.
/// These values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents.
/// <para />
/// Available in Elasticsearch 7.7.0+ with at least basic license level
/// </summary>
[InterfaceDataContract]
[ReadAs(typeof(BoxplotAggregation))]
public interface IBoxplotAggregation : IMetricAggregation
{
/// <summary>
/// Balances memory utilization with estimation accuracy.
/// Increasing compression, increases the accuracy of percentiles at the cost
/// of more memory. Larger compression values also make the algorithm slower since the underlying tree data structure grows in size,
/// resulting in more expensive operations.
/// </summary>
[DataMember(Name = "compression")]
double? Compression { get; set; }
}

/// <inheritdoc cref="IBoxplotAggregation"/>
public class BoxplotAggregation : MetricAggregationBase, IBoxplotAggregation
{
internal BoxplotAggregation() { }

public BoxplotAggregation(string name, Field field) : base(name, field) { }

internal override void WrapInContainer(AggregationContainer c) => c.Boxplot = this;

/// <inheritdoc />
public double? Compression { get; set; }
}

/// <inheritdoc cref="IBoxplotAggregation"/>
public class BoxplotAggregationDescriptor<T>
: MetricAggregationDescriptorBase<BoxplotAggregationDescriptor<T>, IBoxplotAggregation, T>
, IBoxplotAggregation
where T : class
{
double? IBoxplotAggregation.Compression { get; set; }

/// <inheritdoc cref="IBoxplotAggregation.Compression"/>
public BoxplotAggregationDescriptor<T> Compression(double? compression) =>
Assign(compression, (a, v) => a.Compression = v);
}
}
4 changes: 4 additions & 0 deletions src/Nest/Aggregations/Visitor/AggregationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public interface IAggregationVisitor

void Visit(IStringStatsAggregation aggregation);

void Visit(IBoxplotAggregation aggregation);

void Visit(ITopMetricsAggregation aggregation);
}

Expand Down Expand Up @@ -265,6 +267,8 @@ public virtual void Visit(IMovingFunctionAggregation aggregation) { }

public virtual void Visit(IStringStatsAggregation aggregation) { }

public virtual void Visit(IBoxplotAggregation aggregation) { }

public virtual void Visit(ITopMetricsAggregation aggregation) { }

public virtual void Visit(IAggregation aggregation) { }
Expand Down
1 change: 1 addition & 0 deletions src/Nest/Aggregations/Visitor/AggregationWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor)
visitor.Visit(aggregation);
AcceptAggregation(aggregation.Average, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.AverageBucket, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.Boxplot, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.BucketScript, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.BucketSort, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.BucketSelector, visitor, (v, d) => v.Visit(d));
Expand Down
1 change: 1 addition & 0 deletions tests/Tests/Aggregations/AggregationUsageTestBase.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Elastic.Managed.Ephemeral;
using Elastic.Xunit.XunitPlumbing;
using Elasticsearch.Net;
using Nest;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Elastic.Xunit.XunitPlumbing;
using FluentAssertions;
using Nest;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
using Tests.Framework.EndpointTests.TestState;
using static Nest.Infer;

namespace Tests.Aggregations.Metric.Boxplot
{
/**
* A boxplot metrics aggregation that computes boxplot of numeric values extracted from the aggregated documents.
* These values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents.
*
* boxplot aggregation returns essential information for making a box plot: minimum, maximum median, first quartile (25th percentile)
* and third quartile (75th percentile) values.
*
* Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-metrics-boxplot-aggregation.html[Boxplot Aggregation]
*/
[SkipVersion("<7.7.0", "introduced in 7.7.0")]
public class BoxplotAggregationUsageTests : AggregationUsageTestBase
{
public BoxplotAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }

protected override object AggregationJson => new
{
boxplot_commits = new
{
meta = new
{
foo = "bar"
},
boxplot = new
{
field = "numberOfCommits",
missing = 10.0,
compression = 100.0
}
}
};

protected override Func<AggregationContainerDescriptor<Project>, IAggregationContainer> FluentAggs => a => a
.Boxplot("boxplot_commits", plot => plot
.Meta(m => m
.Add("foo", "bar")
)
.Field(p => p.NumberOfCommits)
.Missing(10)
.Compression(100)
);

protected override AggregationDictionary InitializerAggs =>
new BoxplotAggregation("boxplot_commits", Field<Project>(p => p.NumberOfCommits))
{
Meta = new Dictionary<string, object>
{
{ "foo", "bar" }
},
Missing = 10,
Compression = 100
};

protected override void ExpectResponse(ISearchResponse<Project> response)
{
response.ShouldBeValid();
var boxplot = response.Aggregations.Boxplot("boxplot_commits");
boxplot.Should().NotBeNull();
boxplot.Min.Should().BeGreaterOrEqualTo(0);
boxplot.Max.Should().BeGreaterOrEqualTo(0);
boxplot.Q1.Should().BeGreaterOrEqualTo(0);
boxplot.Q2.Should().BeGreaterOrEqualTo(0);
boxplot.Q3.Should().BeGreaterOrEqualTo(0);
boxplot.Meta.Should().NotBeNull().And.HaveCount(1);
boxplot.Meta["foo"].Should().Be("bar");
}
}
}