Skip to content

Commit f65dfdd

Browse files
committed
Implement Boxplot aggregation
Relates: elastic/elasticsearch#51948 This commit implements the boxplot aggregation. Integration tests run against XPackCluster because it requires a license to use.
1 parent 44135c1 commit f65dfdd

File tree

11 files changed

+298
-3
lines changed

11 files changed

+298
-3
lines changed

docs/aggregations.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ The values are typically extracted from the fields of the document (using the fi
3636

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

39+
* <<boxplot-aggregation-usage,Boxplot Aggregation Usage>>
40+
3941
* <<cardinality-aggregation-usage,Cardinality Aggregation Usage>>
4042

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

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

79+
include::aggregations/metric/boxplot/boxplot-aggregation-usage.asciidoc[]
80+
7781
include::aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc[]
7882

7983
include::aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc[]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/master
2+
3+
:github: https://github.com/elastic/elasticsearch-net
4+
5+
:nuget: https://www.nuget.org/packages
6+
7+
////
8+
IMPORTANT NOTE
9+
==============
10+
This file has been generated from https://github.com/elastic/elasticsearch-net/tree/master/src/Tests/Tests/Aggregations/Metric/Boxplot/BoxplotAggregationUsageTests.cs.
11+
If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file,
12+
please modify the original csharp file found at the link and submit the PR with that change. Thanks!
13+
////
14+
15+
[[boxplot-aggregation-usage]]
16+
=== Boxplot Aggregation Usage
17+
18+
A boxplot metrics aggregation that computes boxplot of numeric values extracted from the aggregated documents.
19+
These values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents.
20+
21+
boxplot aggregation returns essential information for making a box plot: minimum, maximum median, first quartile (25th percentile)
22+
and third quartile (75th percentile) values.
23+
24+
Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-metrics-boxplot-aggregation.html[Boxplot Aggregation]
25+
26+
==== Fluent DSL example
27+
28+
[source,csharp]
29+
----
30+
a => a
31+
.Boxplot("boxplot_commits", plot => plot
32+
.Meta(m => m
33+
.Add("foo", "bar")
34+
)
35+
.Field(p => p.NumberOfCommits)
36+
.Missing(10)
37+
.Compression(100)
38+
)
39+
----
40+
41+
==== Object Initializer syntax example
42+
43+
[source,csharp]
44+
----
45+
new BoxplotAggregation("boxplot_commits", Field<Project>(p => p.NumberOfCommits))
46+
{
47+
Meta = new Dictionary<string, object>
48+
{
49+
{ "foo", "bar" }
50+
},
51+
Missing = 10,
52+
Compression = 100
53+
}
54+
----
55+
56+
[source,javascript]
57+
.Example json output
58+
----
59+
{
60+
"boxplot_commits": {
61+
"meta": {
62+
"foo": "bar"
63+
},
64+
"boxplot": {
65+
"field": "numberOfCommits",
66+
"missing": 10.0,
67+
"compression": 100.0
68+
}
69+
}
70+
}
71+
----
72+
73+
==== Handling Responses
74+
75+
[source,csharp]
76+
----
77+
response.ShouldBeValid();
78+
var boxplot = response.Aggregations.Boxplot("boxplot_commits");
79+
boxplot.Should().NotBeNull();
80+
boxplot.Min.Should().BeGreaterOrEqualTo(0);
81+
boxplot.Max.Should().BeGreaterOrEqualTo(0);
82+
boxplot.Q1.Should().BeGreaterOrEqualTo(0);
83+
boxplot.Q2.Should().BeGreaterOrEqualTo(0);
84+
boxplot.Q3.Should().BeGreaterOrEqualTo(0);
85+
boxplot.Meta.Should().NotBeNull().And.HaveCount(1);
86+
boxplot.Meta["foo"].Should().Be("bar");
87+
----
88+

src/Nest/Aggregations/AggregateDictionary.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ public CompositeBucketAggregate Composite(string key)
236236

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

239+
public BoxplotAggregate Boxplot(string key) => TryGet<BoxplotAggregate>(key);
240+
239241
private TAggregate TryGet<TAggregate>(string key) where TAggregate : class, IAggregate =>
240242
BackingDictionary.TryGetValue(key, out var agg) ? agg as TAggregate : null;
241243

src/Nest/Aggregations/AggregateFormatter.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ internal class AggregateFormatter : IJsonFormatter<IAggregate>
5151
{ Parser.Hits, 8 },
5252
{ Parser.Location, 9 },
5353
{ Parser.Fields, 10 },
54+
{ Parser.Min, 11 }
5455
};
5556

5657
private static readonly byte[] SumOtherDocCount = JsonWriter.GetEncodedPropertyNameWithoutQuotation(Parser.SumOtherDocCount);
@@ -151,6 +152,9 @@ private IAggregate ReadAggregate(ref JsonReader reader, IJsonFormatterResolver f
151152
case 10:
152153
aggregate = GetMatrixStatsAggregate(ref reader, formatterResolver, meta);
153154
break;
155+
case 11:
156+
aggregate = GetBoxplotAggregate(ref reader, formatterResolver, meta);
157+
break;
154158
}
155159
}
156160
else
@@ -212,6 +216,32 @@ private IAggregate GetMatrixStatsAggregate(ref JsonReader reader, IJsonFormatter
212216
return matrixStats;
213217
}
214218

219+
private IAggregate GetBoxplotAggregate(ref JsonReader reader, IJsonFormatterResolver formatterResolver, IReadOnlyDictionary<string, object> meta)
220+
{
221+
var boxplot = new BoxplotAggregate
222+
{
223+
Min = reader.ReadDouble(),
224+
Meta = meta
225+
};
226+
reader.ReadNext(); // ,
227+
reader.ReadNext(); // "max"
228+
reader.ReadNext(); // :
229+
boxplot.Max = reader.ReadDouble();
230+
reader.ReadNext(); // ,
231+
reader.ReadNext(); // "q1"
232+
reader.ReadNext(); // :
233+
boxplot.Q1 = reader.ReadDouble();
234+
reader.ReadNext(); // ,
235+
reader.ReadNext(); // "q2"
236+
reader.ReadNext(); // :
237+
boxplot.Q2 = reader.ReadDouble();
238+
reader.ReadNext(); // ,
239+
reader.ReadNext(); // "q3"
240+
reader.ReadNext(); // :
241+
boxplot.Q3 = reader.ReadDouble();
242+
return boxplot;
243+
}
244+
215245
private IAggregate GetTopHitsAggregate(ref JsonReader reader, IJsonFormatterResolver formatterResolver, IReadOnlyDictionary<string, object> meta)
216246
{
217247
var count = 0;
@@ -982,6 +1012,7 @@ private static class Parser
9821012
public const string Location = "location";
9831013
public const string MaxScore = "max_score";
9841014
public const string Meta = "meta";
1015+
public const string Min = "min";
9851016
public const string MinLength = "min_length";
9861017

9871018
public const string Score = "score";

src/Nest/Aggregations/AggregationContainer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public interface IAggregationContainer
9292
[DataMember(Name = "avg_bucket")]
9393
IAverageBucketAggregation AverageBucket { get; set; }
9494

95+
[DataMember(Name = "boxplot")]
96+
IBoxplotAggregation Boxplot { get; set; }
97+
9598
[DataMember(Name = "bucket_script")]
9699
IBucketScriptAggregation BucketScript { get; set; }
97100

@@ -274,6 +277,9 @@ public class AggregationContainer : IAggregationContainer
274277

275278
public IAverageBucketAggregation AverageBucket { get; set; }
276279

280+
/// <inheritdoc cref="IBoxplotAggregation"/>
281+
public IBoxplotAggregation Boxplot { get; set; }
282+
277283
public IBucketScriptAggregation BucketScript { get; set; }
278284

279285
public IBucketSelectorAggregation BucketSelector { get; set; }
@@ -422,6 +428,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta
422428

423429
IAverageBucketAggregation IAggregationContainer.AverageBucket { get; set; }
424430

431+
IBoxplotAggregation IAggregationContainer.Boxplot { get; set; }
432+
425433
IBucketScriptAggregation IAggregationContainer.BucketScript { get; set; }
426434

427435
IBucketSelectorAggregation IAggregationContainer.BucketSelector { get; set; }
@@ -831,6 +839,12 @@ Func<StringStatsAggregationDescriptor<T>, IStringStatsAggregation> selector
831839
) =>
832840
_SetInnerAggregation(name, selector, (a, d) => a.StringStats = d);
833841

842+
/// <inheritdoc cref="IBoxplotAggregation"/>
843+
public AggregationContainerDescriptor<T> Boxplot(string name,
844+
Func<BoxplotAggregationDescriptor<T>, IBoxplotAggregation> selector
845+
) =>
846+
_SetInnerAggregation(name, selector, (a, d) => a.Boxplot = d);
847+
834848
/// <summary>
835849
/// Fluent methods do not assign to properties on `this` directly but on IAggregationContainers inside
836850
/// `this.Aggregations[string, IContainer]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Nest {
2+
public class BoxplotAggregate : MetricAggregateBase
3+
{
4+
public double Min { get; set; }
5+
6+
public double Max { get; set; }
7+
8+
public double Q1 { get; set; }
9+
10+
public double Q2 { get; set; }
11+
12+
public double Q3 { get; set; }
13+
}
14+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Runtime.Serialization;
2+
using Elasticsearch.Net.Utf8Json;
3+
4+
namespace Nest
5+
{
6+
/// <summary>
7+
/// A metrics aggregation that computes boxplot of numeric values extracted from the aggregated documents.
8+
/// These values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents.
9+
/// <para />
10+
/// Available in Elasticsearch 7.7.0+ with at least basic license level
11+
/// </summary>
12+
[InterfaceDataContract]
13+
[ReadAs(typeof(BoxplotAggregation))]
14+
public interface IBoxplotAggregation : IMetricAggregation
15+
{
16+
/// <summary>
17+
/// Balances memory utilization with estimation accuracy.
18+
/// Increasing compression, increases the accuracy of percentiles at the cost
19+
/// of more memory. Larger compression values also make the algorithm slower since the underlying tree data structure grows in size,
20+
/// resulting in more expensive operations.
21+
/// </summary>
22+
[DataMember(Name = "compression")]
23+
double? Compression { get; set; }
24+
}
25+
26+
/// <inheritdoc cref="IBoxplotAggregation"/>
27+
public class BoxplotAggregation : MetricAggregationBase, IBoxplotAggregation
28+
{
29+
internal BoxplotAggregation() { }
30+
31+
public BoxplotAggregation(string name, Field field) : base(name, field) { }
32+
33+
internal override void WrapInContainer(AggregationContainer c) => c.Boxplot = this;
34+
35+
/// <inheritdoc />
36+
public double? Compression { get; set; }
37+
}
38+
39+
/// <inheritdoc cref="IBoxplotAggregation"/>
40+
public class BoxplotAggregationDescriptor<T>
41+
: MetricAggregationDescriptorBase<BoxplotAggregationDescriptor<T>, IBoxplotAggregation, T>
42+
, IBoxplotAggregation
43+
where T : class
44+
{
45+
double? IBoxplotAggregation.Compression { get; set; }
46+
47+
/// <inheritdoc cref="IBoxplotAggregation.Compression"/>
48+
public BoxplotAggregationDescriptor<T> Compression(double? compression) =>
49+
Assign(compression, (a, v) => a.Compression = v);
50+
}
51+
}

src/Nest/Aggregations/Visitor/AggregationVisitor.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ public interface IAggregationVisitor
139139
void Visit(IMovingFunctionAggregation aggregation);
140140

141141
void Visit(IStringStatsAggregation aggregation);
142+
143+
void Visit(IBoxplotAggregation aggregation);
142144
}
143145

144146
public class AggregationVisitor : IAggregationVisitor
@@ -263,6 +265,8 @@ public virtual void Visit(IMovingFunctionAggregation aggregation) { }
263265

264266
public virtual void Visit(IStringStatsAggregation aggregation) { }
265267

268+
public virtual void Visit(IBoxplotAggregation aggregation) { }
269+
266270
public virtual void Visit(IAggregation aggregation) { }
267271

268272
public virtual void Visit(IAggregationContainer aggregationContainer) { }

src/Nest/Aggregations/Visitor/AggregationWalker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor)
3838
visitor.Visit(aggregation);
3939
AcceptAggregation(aggregation.Average, visitor, (v, d) => v.Visit(d));
4040
AcceptAggregation(aggregation.AverageBucket, visitor, (v, d) => v.Visit(d));
41+
AcceptAggregation(aggregation.Boxplot, visitor, (v, d) => v.Visit(d));
4142
AcceptAggregation(aggregation.BucketScript, visitor, (v, d) => v.Visit(d));
4243
AcceptAggregation(aggregation.BucketSort, visitor, (v, d) => v.Visit(d));
4344
AcceptAggregation(aggregation.BucketSelector, visitor, (v, d) => v.Visit(d));

tests/Tests/Aggregations/AggregationUsageTestBase.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Elastic.Managed.Ephemeral;
34
using Elastic.Xunit.XunitPlumbing;
45
using Elasticsearch.Net;
56
using Nest;
@@ -13,10 +14,15 @@
1314

1415
namespace Tests.Aggregations
1516
{
16-
public abstract class AggregationUsageTestBase
17-
: ApiIntegrationTestBase<ReadOnlyCluster, ISearchResponse<Project>, ISearchRequest, SearchDescriptor<Project>, SearchRequest<Project>>
18-
{
17+
public abstract class AggregationUsageTestBase : AggregationUsageTestBase<ReadOnlyCluster> {
1918
protected AggregationUsageTestBase(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
19+
}
20+
21+
public abstract class AggregationUsageTestBase<TCluster>
22+
: ApiIntegrationTestBase<TCluster, ISearchResponse<Project>, ISearchRequest, SearchDescriptor<Project>, SearchRequest<Project>>
23+
where TCluster : IEphemeralCluster<EphemeralClusterConfiguration>, INestTestCluster, new()
24+
{
25+
protected AggregationUsageTestBase(TCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
2026

2127
protected virtual Nest.Indices AgainstIndex { get; } = Index<Project>();
2228

0 commit comments

Comments
 (0)