programing

MongoDB 집계 프레임워크에서 중위수 계산

lastcode 2023. 6. 26. 21:23
반응형

MongoDB 집계 프레임워크에서 중위수 계산

MongoDB 집계 프레임워크를 사용하여 중위수를 계산하는 방법이 있습니까?

중위수는 전체 데이터 집합을 정렬하거나 데이터 집합 크기에 비례하는 깊이의 재귀를 사용해야 하기 때문에 일반적인 경우 계산하기가 다소 까다롭습니다.이것이 많은 데이터베이스에 즉시 사용할 수 있는 중앙값 연산자가 없는 이유입니다(MySQL에도 중앙값 연산자가 없습니다).

중위수를 계산하는 가장 간단한 방법은 이 두 개의 문을 사용하는 것입니다(중위수를 계산할 속성을 다음과 같이 가정합니다).a그리고 우리는 그것을 수집품에 있는 모든 문서들 위에 원한다,coll):

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

이것은 사람들이 MySQL에 대해 제안하는 것과 같습니다.

Aggregate 프레임워크를 사용하여 한 번에 수행할 수 있습니다.

정렬 => 배열 정렬 값 입력 => 배열 크기 가져오기 => 크기를 2로 나누기 => 분할의 Int 값 가져오기 => 왼쪽(중앙값 왼쪽)에 1 추가 => 왼쪽과 오른쪽에 배열 요소 가져오기 => 두 요소의 평균

다음은 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"]
            }
        }
    }
]

}

시작하는Mongo 4.4,그$groupstage에는 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 }

축전지:

  • 경기장에 축적되는b(accumulateArgs)
  • 빈 배열로 초기화됨(init)
  • 누적된b배열의 항목(accumulate그리고.merge)
  • 마지막으로 중앙값 계산을 수행합니다.b항목(finalize)

맥시플레이의 대답은 정확하지 않지만, 저를 올바른 방향으로 이끌었습니다.주어진 솔루션의 문제는 레코드 수가 짝수일 때만 작동한다는 것입니다.홀수 레코드의 경우 평균을 계산할 필요 없이 중간점에서 값을 취하면 되기 때문입니다.

이것이 제가 그것을 작동하게 한 방법입니다.

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"] }
            } 
        }
    }
}
])

집계 프레임워크는 즉시 사용할 수 있는 중앙값을 지원하지 않습니다.그래서 여러분은 스스로 무언가를 써야 할 것입니다.

저는 당신이 이것을 애플리케이션 수준에서 하는 것을 추천합니다. find를하여 모든 문서를 결과 에서 "find()"를 하여 결과 을 정렬합니다..sort()하는 것 - 의 결정을 한 후 것입니다.size / 2.

데이터베이스 수준에서 실제로 이 작업을 수행하려면 map-reduce를 사용하면 됩니다.지도 함수는 중위수를 구하려는 값인 단일 값을 가진 키와 배열을 내보냅니다.축소 함수는 수신된 결과의 배열을 연결하기만 하면 되므로 각 키는 모든 값을 가진 배열로 끝납니다. 번호 그 다 final-function 배 런 을 정 은 여 배 중 를 수 계 위 산 가 한 옵 니 져 다 다 번 를 요 호 음 소 의 열 당 해 하 렬 열 음 ▁the ▁the ▁would ▁finalize ▁number , ▁then ▁element ▁compute그 옵 - 니 다 function ▁the ▁array ▁of - ▁bysize / 2.

이 문제에 대한 나의 해결책은 Taher의 대답과 상당히 비슷하지만 더 적습니다.$project단상들

// { "value" : 1 }
// { "value" : 2  }
// { "value" : 4 }
// { "value" : 5 }
db.median_values.aggregate([
  // Sort the values
  { $sort: { value: 1 } },
  // Get an array of all the values
  { $group: { _id: null, valuesArray: { $push: "$value" } } },
  // Get if the array has an even or odd number of elements
  {
    $project: {
      _id: 0,
      valuesArray: 1,
      isEven: { $eq: [{ $mod: [{ $size: "$valuesArray" }, 2] }, 0] },
      dividedByTwoIndex: { $divide: [{ $size: "$valuesArray" }, 2] },
    },
  },
  // Get the left value and right value if the array has an even or odd number of elements
  {
    $project: {
      _id: 0,
      left: {
        $cond: {
          if: "$isEven",
          then: {
            $arrayElemAt: [
              "$valuesArray",
              { $subtract: ["$dividedByTwoIndex", 1] },
            ],
          },
          else: {
            $arrayElemAt: ["$valuesArray", { $floor: "$dividedByTwoIndex" }],
          },
        },
      },
      right: {
        $cond: {
          if: "$isEven",
          then: {
            $arrayElemAt: ["$valuesArray", "$dividedByTwoIndex"],
          },
          else: {
            $arrayElemAt: ["$valuesArray", { $floor: "$dividedByTwoIndex" }],
          },
        },
      },
    },
  },

  // Compute the median value
  { $project: { median: { $avg: ["$left", "$right"] } } },
]);

// Output:
// { "median" : 3 }

언급URL : https://stackoverflow.com/questions/20456095/calculate-the-median-in-mongodb-aggregation-framework

반응형