From b577f00c3ef7c58bf31d5c0ad3f08ca8daf4b51e Mon Sep 17 00:00:00 2001 From: Itamar Shefi Date: Wed, 21 Feb 2024 16:35:21 +0200 Subject: [PATCH] Add support to $stdDevPop operator --- mongomock/aggregate.py | 10 ++++++++++ tests/test__collection_api.py | 30 +++++++++++++++++++++++++----- tests/test__mongomock.py | 4 +++- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/mongomock/aggregate.py b/mongomock/aggregate.py index d262cd91d..a6c42e726 100644 --- a/mongomock/aggregate.py +++ b/mongomock/aggregate.py @@ -235,9 +235,19 @@ def _merge_objects_operation(values): return merged_doc +def _std_dev_pop_operation(values): + values_list = list(v for v in values if isinstance(v, numbers.Number)) + if not values_list: + return None + mean = sum(values_list) / float(len(values_list)) + variance = sum((x - mean) ** 2 for x in values_list) / float(len(values_list)) + return math.sqrt(variance) + + _GROUPING_OPERATOR_MAP = { '$sum': _sum_operation, '$avg': _avg_operation, + "$stdDevPop": _std_dev_pop_operation, '$mergeObjects': _merge_objects_operation, '$min': lambda values: _group_operation(values, min), '$max': lambda values: _group_operation(values, max), diff --git a/tests/test__collection_api.py b/tests/test__collection_api.py index 8eb90a970..46617c3db 100644 --- a/tests/test__collection_api.py +++ b/tests/test__collection_api.py @@ -4586,11 +4586,6 @@ def test__aggregate_unrecognized(self): def test__aggregate_not_implemented(self): self.db.collection.insert_one({}) - with self.assertRaises(NotImplementedError): - self.db.collection.aggregate([ - {'$project': {'a': {'$stdDevPop': 'scores'}}}, - ]) - with self.assertRaises(NotImplementedError): self.db.collection.aggregate([ {'$project': {'a': {'$cmp': [1, 2]}}}, @@ -7350,6 +7345,31 @@ def test_aggregate_type(self): }]) self.assertListEqual(expected, list(actual)) + def test_std_dev_pop(self): + collection = self.db.collection + collection.insert_many([ + {'_id': 1, 'grades': [80, 85, 90, 90, 90]}, + {'_id': 2, 'grades': [96, 100, 100, 100, 100]}, + {'_id': 3, 'grades': [90, 90, 90, 90, 90]}, + ]) + + actual = collection.aggregate([ + { + '$project': { + 'stdDevPop': {'$stdDevPop': '$grades'}, + '_id': 0 + } + } + ]) + + expect = [ + {'stdDevPop': 4.0}, + {'stdDevPop': 1.6}, + {'stdDevPop': 0.0}, + ] + + for i, item in enumerate(actual): + self.assertAlmostEqual(expect[i]['stdDevPop'], item['stdDevPop'], places=5) def test_aggregate_project_with_boolean(self): collection = self.db.collection diff --git a/tests/test__mongomock.py b/tests/test__mongomock.py index fc616a5ea..7407e315d 100644 --- a/tests/test__mongomock.py +++ b/tests/test__mongomock.py @@ -2654,7 +2654,8 @@ def test__aggregate20(self): 'pow': {'$pow': [4, 2]}, 'sqrt': {'$sqrt': 100}, 'trunc': {'$trunc': 8.35}, - 'round': {'$round': 9.51} + 'round': {'$round': 9.51}, + 'stdDevPop': {'$stdDevPop': '$a'}, }}] self.cmp.compare.aggregate(pipeline) @@ -3861,6 +3862,7 @@ def test__aggregate_exception(self): self.cmp.compare_exceptions.aggregate([{'$project': {'c': {'$sum': []}}}]) self.cmp.compare_exceptions.aggregate([{'$project': {'c': {'$multiply': []}}}]) self.cmp.compare_exceptions.aggregate([{'$project': {'c': {'$round': "12"}}}]) + self.cmp.compare_exceptions.aggregate([{'$project': {'c': {'$stdDevPop': [12, 2, 3]}}}]) self.cmp.compare_exceptions.aggregate([{'$project': {'n': {'$add': '$a'}}}]) self.cmp.compare_exceptions.aggregate( [{'$project': {'q': {'$multiply': [1, '$non_existent_key']}}}])