web-dev-qa-db-ja.com

MongoDB集約フレームワークで中央値を計算します

MongoDB集約フレームワークを使用して中央値を計算する方法はありますか?

23
user3080286

中央値は、データセット全体を並べ替えたり、データセットのサイズにも比例する深​​さの再帰を使用したりするため、一般的なケースでは計算がやや​​難しいです。これが、多くのデータベースにすぐに使用できる中央値演算子がない理由かもしれません(MySQLにも中央値演算子がありません)。

中央値を計算する最も簡単な方法は、これら2つのステートメントを使用することです(中央値を計算する属性がaと呼ばれ、コレクション内のすべてのドキュメントにcollが必要であると仮定します。 )::

count = db.coll.count();
db.coll.find().sort( {"a":1} ).skip(count / 2 - 1).limit(1);

これは、人々が MySQLに提案 と同等です。

30
drmirror

アグリゲートフレームワークを使用すると、ワンショットでそれを行うことができます。

並べ替え=>配列に入力並べ替え値=>配列のサイズを取得=>サイズを2で除算=>除算のInt値を取得(中央値の左側)=>左側(右側)に1を追加=>配列要素を取得左側と右側=> 2つの要素の平均

これはSpring Java mongoTemplate:

モデルは、著者(「所有者」)のログインを含む本のリストです。目的は、ユーザーが本の中央値を取得することです。

        GroupOperation countByBookOwner = group("owner").count().as("nbBooks");

    SortOperation sortByCount = sort(Direction.ASC, "nbBooks");

    GroupOperation putInArray = group().Push("nbBooks").as("nbBooksArray");

    ProjectionOperation getSizeOfArray = project("nbBooksArray").and("nbBooksArray").size().as("size");

    ProjectionOperation divideSizeByTwo = project("nbBooksArray").and("size").divide(2).as("middleFloat");

    ProjectionOperation getIntValueOfDivisionForBornLeft = project("middleFloat", "nbBooksArray").and("middleFloat")
            .project("trunc").as("beginMiddle");

    ProjectionOperation add1ToBornLeftToGetBornRight = project("beginMiddle", "middleFloat", "nbBooksArray")
            .and("beginMiddle").project("add", 1).as("endMiddle");

    ProjectionOperation arrayElementAt = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray")
            .and("nbBooksArray").project("arrayElemAt", "$beginMiddle").as("beginValue").and("nbBooksArray")
            .project("arrayElemAt", "$endMiddle").as("endValue");

    ProjectionOperation averageForMedian = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray",
            "beginValue", "endValue").and("beginValue").project("avg", "$endValue").as("median");

    Aggregation aggregation = newAggregation(countByBookOwner, sortByCount, putInArray, getSizeOfArray,
            divideSizeByTwo, getIntValueOfDivisionForBornLeft, add1ToBornLeftToGetBornRight, arrayElementAt,
            averageForMedian);

    long time = System.currentTimeMillis();
    AggregationResults<MedianContainer> groupResults = mongoTemplate.aggregate(aggregation, "book",
            MedianContainer.class);

そしてここで結果の集計:

{
"aggregate": "book" ,
"pipeline": [
    {
        "$group": {
            "_id": "$owner" ,
            "nbBooks": {
                "$sum": 1
            }
        }
    } , {
        "$sort": {
            "nbBooks": 1
        }
    } , {
        "$group": {
            "_id": null  ,
            "nbBooksArray": {
                "$Push": "$nbBooks"
            }
        }
    } , {
        "$project": {
            "nbBooksArray": 1 ,
            "size": {
                "$size": ["$nbBooksArray"]
            }
        }
    } , {
        "$project": {
            "nbBooksArray": 1 ,
            "middleFloat": {
                "$divide": ["$size" , 2]
            }
        }
    } , {
        "$project": {
            "middleFloat": 1 ,
            "nbBooksArray": 1 ,
            "beginMiddle": {
                "$trunc": ["$middleFloat"]
            }
        }
    } , {
        "$project": {
            "beginMiddle": 1 ,
            "middleFloat": 1 ,
            "nbBooksArray": 1 ,
            "endMiddle": {
                "$add": ["$beginMiddle" , 1]
            }
        }
    } , {
        "$project": {
            "beginMiddle": 1 ,
            "endMiddle": 1 ,
            "middleFloat": 1 ,
            "nbBooksArray": 1 ,
            "beginValue": {
                "$arrayElemAt": ["$nbBooksArray" , "$beginMiddle"]
            } ,
            "endValue": {
                "$arrayElemAt": ["$nbBooksArray" , "$endMiddle"]
            }
        }
    } , {
        "$project": {
            "beginMiddle": 1 ,
            "endMiddle": 1 ,
            "middleFloat": 1 ,
            "nbBooksArray": 1 ,
            "beginValue": 1 ,
            "endValue": 1 ,
            "median": {
                "$avg": ["$beginValue" , "$endValue"]
            }
        }
    }
]

}

8
maxiplay

maxiplayの答え は正確ではありませんが、それは私を正しい方向に導きました。与えられたソリューションの問題は、レコード数が偶数の場合にのみ機能することです。レコード数が奇数であるため、平均を計算することなく、中間点で値を取得する必要があります。

これが私がそれを機能させる方法です。

db.collection.aggregate([
{ "$match": { "processingStatus": "Completed" } },
{ 
    "$group": {
        "_id": "$userId",
        "valueArray": {
            "$Push": "$value"
        }
    } 
},
{ "$sort": { "value": 1 } },
{
    "$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": ["$stepsArray", "$beginMiddle"] },
        "endValue": { "$arrayElemAt": ["$stepsArray", "$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": ["$stepsArray", "$middlePoint"] }
            } 
        }
    }
}
])
2
Taher

集約フレームワークは、すぐに使用できる中央値をサポートしていません。だからあなたは自分で何かを書かなければならないでしょう。

これはアプリケーションレベルで行うことをお勧めします。通常のfind()を使用してすべてのドキュメントを取得し、結果セットを並べ替え(カーソルの.sort()関数を使用するか、アプリケーションで並べ替えてデータベースで並べ替えます-決定)、要素size / 2を取得します。

本当にデータベースレベルでそれを実行したい場合は、map-reduceを使用して実行できます。 map関数は、キーと単一の値(中央値を取得する値)を持つ配列を出力します。 reduce関数は、受け取った結果の配列を連結するだけなので、各キーはすべての値を持つ配列になります。次に、finalize関数は、配列を並べ替えて要素番号size / 2を取得することにより、その配列の中央値を計算します。

2
Philipp

開始Mongo 4.4$groupステージに新しい集計演算子があります $accumulator javascriptユーザー定義関数を介して、グループ化されたドキュメントのカスタム累積を許可します。

したがって、中央値を見つけるために:

// { "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 }

アキュムレータ:

  • フィールドに蓄積するbaccumulateArgs
  • 空の配列に初期化されます(init
  • bアイテムを配列に蓄積します(accumulateおよびmerge
  • 最後に、bアイテムの中央値計算を実行します(finalize
0
Xavier Guihot