Is there a way to calculate the median using the MongoDB aggregation framework?
Is there a way to calculate the median using the MongoDB aggregation framework?
Starting Mongo 4.4, the $group stage has a new aggregation operator $accumulator allowing custom accumulations of documents as they get grouped, via javascript user defined functions.
Thus, in order to find the median:
// { "a" : 25, "b" : 12 }
// { "a" : 89, "b" : 7 }
// { "a" : 25, "b" : 17 }
// { "a" : 25, "b" : 24 }
// { "a" : 89, "b" : 15 }
db.collection.aggregate([
{ $group: {
_id: "$a",
median: {
$accumulator: {
accumulateArgs: ["$b"],
init: function() { return []; },
accumulate: function(bs, b) { return bs.concat(b); },
merge: function(bs1, bs2) { return bs1.concat(bs2); },
finalize: function(bs) {
bs.sort(function(a, b) { return a - b });
var mid = bs.length / 2;
return mid % 1 ? bs[mid - 0.5] : (bs[mid - 1] + bs[mid]) / 2;
},
lang: "js"
}
}
}}
])
// { "_id" : 25, "median" : 17 }
// { "_id" : 89, "median" : 11 }
The accumulator:
b (accumulateArgs)init)b items in an array (accumulate and merge)b items (finalize)While maxiplay's answer is not accurate, it did lead me in the correct direction. The problem with the given solution is that it only works when the number of records is even. Because for odd number of records, one simply needs to take the value at midpoint without having to calculate an average.
This is how I got it to work.
db.collection.aggregate([
{ "$match": { "processingStatus": "Completed" } },
{ "$sort": { "value": 1 } },
{
"$group": {
"_id": "$userId",
"valueArray": {
"$push": "$value"
}
}
},
{
"$project": {
"_id": 0,
"userId": "$_id",
"valueArray": 1,
"size": { "$size": ["$valueArray"] }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": { "$eq": [{ "$mod": ["$size", 2] }, 0 ] },
"middlePoint": { "$trunc": { "$divide": ["$size", 2] } }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": 1,
"middlePoint": 1,
"beginMiddle": { "$subtract": [ "$middlePoint", 1] },
"endMiddle": "$middlePoint"
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": { "$arrayElemAt": ["$valueArray", "$beginMiddle"] },
"endValue": { "$arrayElemAt": ["$valueArray", "$endMiddle"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": 1,
"endValue": 1,
"middleSum": { "$add": ["$beginValue", "$endValue"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"median": {
"$cond": {
if: "$isEvenLength",
then: { "$divide": ["$middleSum", 2] },
else: { "$arrayElemAt": ["$valueArray", "$middlePoint"] }
}
}
}
}
])