2012-07-23 10 views
7

Je cherche un moyen de générer des statistiques récapitulatives en utilisant Mongo. Supposons que j'ai une collection avec de nombreux enregistrements du formulaireBinning et tabulation (unique/compte) en Mongo

{"name" : "Jeroen", "gender" : "m", "age" :27.53 } 

Maintenant, je veux obtenir les distributions pour le sexe et l'âge. Supposons pour le sexe, il n'y a que les valeurs "m" et "f". Quel est le moyen le plus efficace d'obtenir le nombre total de mâles et de femelles dans ma collection?

Et pour l'âge, existe-t-il un moyen de faire un «binning» et de me donner un résumé de l'histogramme; c'est-à-dire le nombre d'enregistrements où l'âge est dans les intervalles: [0, 2), [2, 4), [4, 6) ... etc?

Répondre

2

La réponse de Konstantin était juste. MapReduce fait le travail. Voici la solution complète au cas où d'autres trouveraient cela intéressant.

Pour comptabiliser les critères de sexe, la touche de fonction de la carte est l'attribut this.gender pour chaque enregistrement. La fonction de réduire ensuite les ajoute tout simplement:

// count genders 
db.persons.mapReduce(
    function(){ 
     emit(this["gender"], {count: 1}) 
    }, function(key, values){ 
     var result = {count: 0}; 
     values.forEach(function(value) { 
      result.count += value.count; 
     }); 
     return result; 
    }, {out: { inline : 1}} 
); 

Pour faire le Binning, nous avons mis la clé dans la fonction carte pour arrondir à la division la plus proche par deux. Par conséquent, par exemple toute valeur entre 10 et 11,9999 obtiendra la même clé "10-12". Et puis on les ajoute tout simplement:

db.responses.mapReduce(
    function(){ 
     var x = Math.floor(this["age"]/2)*2; 
     var key = x + "-" + (x+2); 
     emit(key, {count: 1}) 
    }, function(state, values){ 
     var result = {count: 0}; 
     values.forEach(function(value) { 
      result.count += value.count; 
     }); 
     return result; 
    }, {out: { inline : 1}} 
); 
+0

C'est cool. Où puis-je trouver la documentation pour la fonction mapReduce()? J'ai fait une recherche mais je n'ai pas trouvé de source officielle ... – jimijazz

0

En fonction de la quantité de données le moyen le plus efficace pour trouver la quantité de mâles et de femelles pourrait être soit requête naïve ou carte réduire le travail. Binning est mieux fait par carte réduire:

Dans la phase de carte clé est un bac, et la valeur est 1, et la vous phase de réduire somme juste des valeurs

+0

Pourriez-vous illustrer cela avec un exemple de code? – Jeroen

+0

Vous étiez plus rapide - je suis en vacances et à peu près hors ligne –

1

une façon facile d'obtenir le nombre total de les mâles seraient db.x.find({"gender": "m"}).count()

Si vous voulez les comptes masculins et féminins en une seule requête, alors il n'y a pas de moyen facile. Carte/réduire serait une possibilité. Ou peut-être le nouveau aggregation framework. La même chose est vraie pour votre binning exigence

Mongo n'est pas génial pour l'agrégation, mais c'est fantastique pour beaucoup de petites mises à jour incrémentales. Donc, la meilleure façon de résoudre ce problème avec mongo serait de collecter les données d'agrégation dans une collection séparée.

Donc, si vous gardez une collection de statistiques avec un document comme celui-ci:

stats: [ 
    { 
    "male": 23, 
    "female": 17, 
    "ageDistribution": { 
     "0_2" : 3, 
     "2_4" : 5, 
     "4_6" : 7 
    } 
    } 
] 

... puis à chaque fois que vous ajoutez ou supprimez une personne de l'autre collection, vous comptez les champs respectifs vers le haut ou vers le bas dans la collection de statistiques.

db.stats.update({"$inc": {"male": 1, "ageDistribution.2_4": 1}}) 

requêtes STATS sera rapide comme l'éclair de cette façon, et vous ne remarquerez aucune surcharge de performance de compter les statistiques de haut en bas.

19

Je viens d'essayer le nouveau cadre d'agrégation qui sera disponible dans la version 2.2 MongoDB (a été publié 2.2.0-RC0), ce qui devrait avoir des performances supérieures à la carte réduire car il ne s'appuie pas sur Javascript.

données d'entrée

:

{ "_id" : 1, "age" : 22.34, "gender" : "f" } 
{ "_id" : 2, "age" : 23.9, "gender" : "f" } 
{ "_id" : 3, "age" : 27.4, "gender" : "f" } 
{ "_id" : 4, "age" : 26.9, "gender" : "m" } 
{ "_id" : 5, "age" : 26, "gender" : "m" } 

commande d'agrégation pour le genre:

db.collection.aggregate(
    {$project: {gender:1}}, 
    {$group: { 
     _id: "$gender", 
     count: {$sum: 1} 
    }}) 

Résultat:

{"result" : 
    [ 
    {"_id" : "m", "count" : 2}, 
    {"_id" : "f", "count" : 3} 
    ], 
    "ok" : 1 
} 

Pour les âges dans les bacs:

db.collection.aggregate(
    {$project: { 
     ageLowerBound: {$subtract:["$age", {$mod:["$age",2]}]}} 
    }, 
    {$group: { 
     _id:"$ageLowerBound", 
     count:{$sum:1} 
    } 
}) 

résultat:

{"result" : 
    [ 
     {"_id" : 26, "count" : 3}, 
     {"_id" : 22, "count" : 2} 
    ], 
    "ok" : 1 
} 
+0

Je devrais également noter pour toute personne intéressée par le framework d'agrégation que l'utilisation de $ match dans la commande aggregate le plus tôt possible est recommandée pour éviter une analyse complète de la table. – Jenna

+2

Pour les groupes arbitraires qui ne sont pas des multiples d'un certain nombre, vous pouvez utiliser [$ cond] (http://docs.mongodb.org/manual/reference/operator/aggregation/cond/#exp._S_cond) bien que la syntaxe soit horrible: $ projet: {âgeBas:{$ cond: "2", "4"]}]}} ... ou quelque chose comme ça. –

+0

@Jenna Que pouvons-nous faire contre les entrées GeoJSON? – Pei

0

Avec Mongo 3.4 vient encore plus facile, grâce au nouveau seau $ et $bucketAuto fonctions d'agrégation. Voici des seaux d'auto-requête en deux groupes:

db.bucket.aggregate([ 
    { 
    $bucketAuto: { 
     groupBy: "$gender", 
     buckets: 2 
    } 
    } 
]) 

Avec les données d'entrée suivantes:

{ "_id" : 1, "age" : 22.34, "gender" : "f" } 
{ "_id" : 2, "age" : 23.9, "gender" : "f" } 
{ "_id" : 3, "age" : 27.4, "gender" : "f" } 
{ "_id" : 4, "age" : 26.9, "gender" : "m" } 
{ "_id" : 5, "age" : 26, "gender" : "m" } 

Il donne le résultat suivant:

{ "_id" : { "min" : "f", "max" : "m" }, "count" : 3 } 
{ "_id" : { "min" : "m", "max" : "m" }, "count" : 2 } 

Note, seau et seau auto sont généralement utilisés pour les variables continues (numérique, date), mais dans ce cas, l'auto-bucket fonctionne très bien.

0

Sur la base de la réponse de @ColinE binning pour histogramme peut être fait par

db.persons.aggregate([ 
 { 
    $bucket: { 
    groupBy: "$j.age", 
    boundaries: [0,2,4,6,8,10,12,14,16,18,20], 
    default: "Other", 
    output: { 
     "count": { $sum: 1 } 
    } 
    } 
], 
{allowDiskUse:true}) 

$bucketAuto ne fonctionne pas pour moi depuis que des seaux semblent être collectées sur une échelle logarithmique. allowDiskUse est seulement nécessaire si vous avez des millions de documents