Oliver Eilhard 2 years ago
parent
commit
1da5bfda4f
5 changed files with 329 additions and 2 deletions
  1. 2
    2
      README.md
  2. 55
    0
      search_aggs.go
  3. 120
    0
      search_aggs_matrix_stats.go
  4. 53
    0
      search_aggs_matrix_stats_test.go
  5. 99
    0
      search_aggs_test.go

+ 2
- 2
README.md View File

@@ -290,8 +290,8 @@ See the [wiki](https://github.com/olivere/elastic/wiki) for more details.
290 290
   - [x] Bucket Script
291 291
   - [x] Bucket Selector
292 292
   - [x] Serial Differencing
293
-- [ ] Matrix Aggregations
294
-  - [ ] Matrix Stats
293
+- [x] Matrix Aggregations
294
+  - [x] Matrix Stats
295 295
 - [x] Aggregation Metadata
296 296
 
297 297
 ### Indices APIs

+ 55
- 0
search_aggs.go View File

@@ -143,6 +143,21 @@ func (a Aggregations) ExtendedStats(name string) (*AggregationExtendedStatsMetri
143 143
 	return nil, false
144 144
 }
145 145
 
146
+// MatrixStats returns matrix stats aggregation results.
147
+// https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-aggregations-matrix-stats-aggregation.html
148
+func (a Aggregations) MatrixStats(name string) (*AggregationMatrixStats, bool) {
149
+	if raw, found := a[name]; found {
150
+		agg := new(AggregationMatrixStats)
151
+		if raw == nil {
152
+			return agg, true
153
+		}
154
+		if err := json.Unmarshal(*raw, agg); err == nil {
155
+			return agg, true
156
+		}
157
+	}
158
+	return nil, false
159
+}
160
+
146 161
 // Percentiles returns percentiles results.
147 162
 // See: https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-aggregations-metrics-percentile-aggregation.html
148 163
 func (a Aggregations) Percentiles(name string) (*AggregationPercentilesMetric, bool) {
@@ -746,6 +761,46 @@ func (a *AggregationExtendedStatsMetric) UnmarshalJSON(data []byte) error {
746 761
 	return nil
747 762
 }
748 763
 
764
+// -- Matrix Stats --
765
+
766
+// AggregationMatrixStats is returned by a MatrixStats aggregation.
767
+type AggregationMatrixStats struct {
768
+	Aggregations
769
+
770
+	Fields []*AggregationMatrixStatsField // `json:"field,omitempty"`
771
+	Meta   map[string]interface{}         // `json:"meta,omitempty"`
772
+}
773
+
774
+// AggregationMatrixStatsField represents running stats of a single field
775
+// returned from MatrixStats aggregation.
776
+type AggregationMatrixStatsField struct {
777
+	Name        string             `json:"name"`
778
+	Count       int64              `json:"count"`
779
+	Mean        float64            `json:"mean,omitempty"`
780
+	Variance    float64            `json:"variance,omitempty"`
781
+	Skewness    float64            `json:"skewness,omitempty"`
782
+	Kurtosis    float64            `json:"kurtosis,omitempty"`
783
+	Covariance  map[string]float64 `json:"covariance,omitempty"`
784
+	Correlation map[string]float64 `json:"correlation,omitempty"`
785
+}
786
+
787
+// UnmarshalJSON decodes JSON data and initializes an AggregationMatrixStats structure.
788
+func (a *AggregationMatrixStats) UnmarshalJSON(data []byte) error {
789
+	var aggs map[string]*json.RawMessage
790
+	if err := json.Unmarshal(data, &aggs); err != nil {
791
+		return err
792
+	}
793
+	if v, ok := aggs["fields"]; ok && v != nil {
794
+		// RunningStats for every field
795
+		json.Unmarshal(*v, &a.Fields)
796
+	}
797
+	if v, ok := aggs["meta"]; ok && v != nil {
798
+		json.Unmarshal(*v, &a.Meta)
799
+	}
800
+	a.Aggregations = aggs
801
+	return nil
802
+}
803
+
749 804
 // -- Percentiles metric --
750 805
 
751 806
 // AggregationPercentilesMetric is a multi-value metric, returned by a Percentiles aggregation.

+ 120
- 0
search_aggs_matrix_stats.go View File

@@ -0,0 +1,120 @@
1
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
2
+// Use of this source code is governed by a MIT-license.
3
+// See http://olivere.mit-license.org/license.txt for details.
4
+
5
+package elastic
6
+
7
+// MatrixMatrixStatsAggregation ...
8
+// See https://www.elastic.co/guide/en/elasticsearch/reference/5.3/search-aggregations-metrics-stats-aggregation.html
9
+// for details.
10
+type MatrixStatsAggregation struct {
11
+	fields          []string
12
+	missing         interface{}
13
+	format          string
14
+	valueType       interface{}
15
+	mode            string
16
+	subAggregations map[string]Aggregation
17
+	meta            map[string]interface{}
18
+}
19
+
20
+// NewMatrixStatsAggregation initializes a new MatrixStatsAggregation.
21
+func NewMatrixStatsAggregation() *MatrixStatsAggregation {
22
+	return &MatrixStatsAggregation{
23
+		subAggregations: make(map[string]Aggregation),
24
+	}
25
+}
26
+
27
+func (a *MatrixStatsAggregation) Fields(fields ...string) *MatrixStatsAggregation {
28
+	a.fields = append(a.fields, fields...)
29
+	return a
30
+}
31
+
32
+// Missing configures the value to use when documents miss a value.
33
+func (a *MatrixStatsAggregation) Missing(missing interface{}) *MatrixStatsAggregation {
34
+	a.missing = missing
35
+	return a
36
+}
37
+
38
+// Mode specifies how to operate. Valid values are: sum, avg, median, min, or max.
39
+func (a *MatrixStatsAggregation) Mode(mode string) *MatrixStatsAggregation {
40
+	a.mode = mode
41
+	return a
42
+}
43
+
44
+func (a *MatrixStatsAggregation) Format(format string) *MatrixStatsAggregation {
45
+	a.format = format
46
+	return a
47
+}
48
+
49
+func (a *MatrixStatsAggregation) ValueType(valueType interface{}) *MatrixStatsAggregation {
50
+	a.valueType = valueType
51
+	return a
52
+}
53
+
54
+func (a *MatrixStatsAggregation) SubAggregation(name string, subAggregation Aggregation) *MatrixStatsAggregation {
55
+	a.subAggregations[name] = subAggregation
56
+	return a
57
+}
58
+
59
+// Meta sets the meta data to be included in the aggregation response.
60
+func (a *MatrixStatsAggregation) Meta(metaData map[string]interface{}) *MatrixStatsAggregation {
61
+	a.meta = metaData
62
+	return a
63
+}
64
+
65
+// Source returns the JSON to serialize into the request, or an error.
66
+func (a *MatrixStatsAggregation) Source() (interface{}, error) {
67
+	// Example:
68
+	//	{
69
+	//    "aggs" : {
70
+	//      "matrixstats" : {
71
+	//        "matrix_stats" : {
72
+	//          "fields" : ["poverty", "income"],
73
+	//          "missing": {"income": 50000},
74
+	//          "mode": "avg",
75
+	//          ...
76
+	//        }
77
+	//      }
78
+	//    }
79
+	//	}
80
+	// This method returns only the { "matrix_stats" : { ... } } part.
81
+
82
+	source := make(map[string]interface{})
83
+	opts := make(map[string]interface{})
84
+	source["matrix_stats"] = opts
85
+
86
+	// MatrixStatsAggregationBuilder
87
+	opts["fields"] = a.fields
88
+	if a.missing != nil {
89
+		opts["missing"] = a.missing
90
+	}
91
+	if a.format != "" {
92
+		opts["format"] = a.format
93
+	}
94
+	if a.valueType != nil {
95
+		opts["value_type"] = a.valueType
96
+	}
97
+	if a.mode != "" {
98
+		opts["mode"] = a.mode
99
+	}
100
+
101
+	// AggregationBuilder (SubAggregations)
102
+	if len(a.subAggregations) > 0 {
103
+		aggsMap := make(map[string]interface{})
104
+		source["aggregations"] = aggsMap
105
+		for name, aggregate := range a.subAggregations {
106
+			src, err := aggregate.Source()
107
+			if err != nil {
108
+				return nil, err
109
+			}
110
+			aggsMap[name] = src
111
+		}
112
+	}
113
+
114
+	// Add Meta data if available
115
+	if len(a.meta) > 0 {
116
+		source["meta"] = a.meta
117
+	}
118
+
119
+	return source, nil
120
+}

+ 53
- 0
search_aggs_matrix_stats_test.go View File

@@ -0,0 +1,53 @@
1
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
2
+// Use of this source code is governed by a MIT-license.
3
+// See http://olivere.mit-license.org/license.txt for details.
4
+
5
+package elastic
6
+
7
+import (
8
+	"encoding/json"
9
+	"testing"
10
+)
11
+
12
+func TestMatrixStatsAggregation(t *testing.T) {
13
+	agg := NewMatrixStatsAggregation().
14
+		Fields("poverty", "income").
15
+		Missing(map[string]interface{}{
16
+			"income": 50000,
17
+		}).
18
+		Mode("avg").
19
+		Format("0000.0").
20
+		ValueType("double")
21
+	src, err := agg.Source()
22
+	if err != nil {
23
+		t.Fatal(err)
24
+	}
25
+	data, err := json.Marshal(src)
26
+	if err != nil {
27
+		t.Fatalf("marshaling to JSON failed: %v", err)
28
+	}
29
+	got := string(data)
30
+	expected := `{"matrix_stats":{"fields":["poverty","income"],"format":"0000.0","missing":{"income":50000},"mode":"avg","value_type":"double"}}`
31
+	if got != expected {
32
+		t.Errorf("expected\n%s\n,got:\n%s", expected, got)
33
+	}
34
+}
35
+
36
+func TestMatrixStatsAggregationWithMetaData(t *testing.T) {
37
+	agg := NewMatrixStatsAggregation().
38
+		Fields("poverty", "income").
39
+		Meta(map[string]interface{}{"name": "Oliver"})
40
+	src, err := agg.Source()
41
+	if err != nil {
42
+		t.Fatal(err)
43
+	}
44
+	data, err := json.Marshal(src)
45
+	if err != nil {
46
+		t.Fatalf("marshaling to JSON failed: %v", err)
47
+	}
48
+	got := string(data)
49
+	expected := `{"matrix_stats":{"fields":["poverty","income"]},"meta":{"name":"Oliver"}}`
50
+	if got != expected {
51
+		t.Errorf("expected\n%s\n,got:\n%s", expected, got)
52
+	}
53
+}

+ 99
- 0
search_aggs_test.go View File

@@ -1332,6 +1332,105 @@ func TestAggsMetricsExtendedStats(t *testing.T) {
1332 1332
 	}
1333 1333
 }
1334 1334
 
1335
+func TestAggsMatrixStats(t *testing.T) {
1336
+	s := `{
1337
+	"matrixstats": {
1338
+		"fields": [{
1339
+			"name": "income",
1340
+			"count": 50,
1341
+			"mean": 51985.1,
1342
+			"variance": 7.383377037755103E7,
1343
+			"skewness": 0.5595114003506483,
1344
+			"kurtosis": 2.5692365287787124,
1345
+			"covariance": {
1346
+				"income": 7.383377037755103E7,
1347
+				"poverty": -21093.65836734694
1348
+			},
1349
+			"correlation": {
1350
+				"income": 1.0,
1351
+				"poverty": -0.8352655256272504
1352
+			}
1353
+		}, {
1354
+			"name": "poverty",
1355
+			"count": 51,
1356
+			"mean": 12.732000000000001,
1357
+			"variance": 8.637730612244896,
1358
+			"skewness": 0.4516049811903419,
1359
+			"kurtosis": 2.8615929677997767,
1360
+			"covariance": {
1361
+				"income": -21093.65836734694,
1362
+				"poverty": 8.637730612244896
1363
+			},
1364
+			"correlation": {
1365
+				"income": -0.8352655256272504,
1366
+				"poverty": 1.0
1367
+			}
1368
+		}]
1369
+	}
1370
+}`
1371
+
1372
+	aggs := new(Aggregations)
1373
+	err := json.Unmarshal([]byte(s), &aggs)
1374
+	if err != nil {
1375
+		t.Fatalf("expected no error decoding; got: %v", err)
1376
+	}
1377
+
1378
+	agg, found := aggs.MatrixStats("matrixstats")
1379
+	if !found {
1380
+		t.Fatalf("expected aggregation to be found; got: %v", found)
1381
+	}
1382
+	if agg == nil {
1383
+		t.Fatalf("expected aggregation != nil; got: %v", agg)
1384
+	}
1385
+	if want, got := 2, len(agg.Fields); want != got {
1386
+		t.Fatalf("expected aggregaton len(Fields) = %v; got: %v", want, got)
1387
+	}
1388
+	field := agg.Fields[0]
1389
+	if want, got := "income", field.Name; want != got {
1390
+		t.Fatalf("expected aggregation field name == %q; got: %q", want, got)
1391
+	}
1392
+	if want, got := int64(50), field.Count; want != got {
1393
+		t.Fatalf("expected aggregation field count == %v; got: %v", want, got)
1394
+	}
1395
+	if want, got := 51985.1, field.Mean; want != got {
1396
+		t.Fatalf("expected aggregation field mean == %v; got: %v", want, got)
1397
+	}
1398
+	if want, got := 7.383377037755103e7, field.Variance; want != got {
1399
+		t.Fatalf("expected aggregation field variance == %v; got: %v", want, got)
1400
+	}
1401
+	if want, got := 0.5595114003506483, field.Skewness; want != got {
1402
+		t.Fatalf("expected aggregation field skewness == %v; got: %v", want, got)
1403
+	}
1404
+	if want, got := 2.5692365287787124, field.Kurtosis; want != got {
1405
+		t.Fatalf("expected aggregation field kurtosis == %v; got: %v", want, got)
1406
+	}
1407
+	if field.Covariance == nil {
1408
+		t.Fatalf("expected aggregation field covariance != nil; got: %v", nil)
1409
+	}
1410
+	if want, got := 7.383377037755103e7, field.Covariance["income"]; want != got {
1411
+		t.Fatalf("expected aggregation field covariance == %v; got: %v", want, got)
1412
+	}
1413
+	if want, got := -21093.65836734694, field.Covariance["poverty"]; want != got {
1414
+		t.Fatalf("expected aggregation field covariance == %v; got: %v", want, got)
1415
+	}
1416
+	if field.Correlation == nil {
1417
+		t.Fatalf("expected aggregation field correlation != nil; got: %v", nil)
1418
+	}
1419
+	if want, got := 1.0, field.Correlation["income"]; want != got {
1420
+		t.Fatalf("expected aggregation field correlation == %v; got: %v", want, got)
1421
+	}
1422
+	if want, got := -0.8352655256272504, field.Correlation["poverty"]; want != got {
1423
+		t.Fatalf("expected aggregation field correlation == %v; got: %v", want, got)
1424
+	}
1425
+	field = agg.Fields[1]
1426
+	if want, got := "poverty", field.Name; want != got {
1427
+		t.Fatalf("expected aggregation field name == %q; got: %q", want, got)
1428
+	}
1429
+	if want, got := int64(51), field.Count; want != got {
1430
+		t.Fatalf("expected aggregation field count == %v; got: %v", want, got)
1431
+	}
1432
+}
1433
+
1335 1434
 func TestAggsMetricsPercentiles(t *testing.T) {
1336 1435
 	s := `{
1337 1436
   "load_time_outlier": {

Loading…
Cancel
Save