2012-07-23 8 views
7

Estoy buscando una forma de generar algunas estadísticas de resumen usando Mongo. Supongamos que tengo una colección con muchos registros del formularioBinning y tabulate (unique/count) en Mongo

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

Ahora quiero obtener las distribuciones por sexo y edad. Supongamos por género, solo hay valores "m" y "f". ¿Cuál es la forma más eficiente de obtener el recuento total de hombres y mujeres en mi colección?

Y para la edad, ¿hay alguna manera de hacer un 'binning' y me da un resumen como un histograma; es decir, el número de registros donde la edad está en los intervalos: [0, 2), [2, 4), [4, 6) ..., etc.

Respuesta

2

La respuesta de Konstantin era correcta. MapReduce hace el trabajo. Aquí está la solución completa en caso de que otros encuentren esto interesante.

Para contar los géneros, la tecla de función del mapa es el atributo this.gender para cada registro. La función de reducir simplemente los suma:

// 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}} 
); 

para hacer el binning, fijamos la llave en la función de mapa para redondear hacia abajo a la división más cercana por dos. Por lo tanto, p. cualquier valor entre 10 y 11.9999 obtendrá la misma clave "10-12". Y luego otra vez simplemente se añade a ellos:

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

Esto es genial. ¿Dónde puedo encontrar documentación para la función mapReduce()? Hice una búsqueda, pero no pareció encontrar una fuente oficial ... – jimijazz

0

Dependiendo de la cantidad de datos, la manera más efectiva de encontrar la cantidad de hombres y mujeres podría ser consulta ingenua o mapa reducir trabajo. Binning se realiza mejor a través de MapReduce:

En la fase mapa su clave es un cubo, y el valor es 1, y en la fase reducir acaba de resumir los valores

+0

¿Podría ilustrar esto con algún código de ejemplo? – Jeroen

+0

Fuiste más rápido, estoy de vacaciones y prácticamente fuera de línea –

1

una manera fácil de obtener el recuento total de los varones serían db.x.find({"gender": "m"}).count()

Si desea conteos masculinos y femeninos en una sola consulta, entonces no hay una manera fácil. Mapa/reducir sería una posibilidad. O tal vez el nuevo aggregation framework. Lo mismo es cierto para su binning requisito

Mongo no es ideal para la agregación, pero es fantástico para muchas pequeñas actualizaciones incrementales. Entonces, la mejor manera de resolver este problema con mongo sería recolectar los datos de agregación en una colección separada.

Por lo tanto, si se mantiene una colección de estadísticas con un documento como este:

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

... entonces cada vez que añadir o quitar a una persona de la otra colección, se cuentan los campos respectivos arriba o hacia abajo en la colección de estadísticas

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

Consultas a las estadísticas será la velocidad del rayo de esta manera, y casi no notará ninguna sobrecarga de rendimiento de contar las estadísticas de arriba abajo.

19

yo sólo probamos el nuevo marco de agregación, que estará disponible en la versión 2.2 MongoDB (2.2.0-rc0 ha sido liberado), que debe tener un mayor rendimiento que el mapa reducir ya que no depende de Javascript.datos

de entrada:

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

comando de la agregación de género:

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

resultado:

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

Para obtener las edades en los contenedores:

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

resultado:

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

También debo señalar a cualquier persona interesada en el marco de agregación que se use $ match en el comando agregado tan pronto como sea posible para evitar un análisis completo de la tabla. – Jenna

+2

Para contenedores arbitrarios que no son múltiplos de un número, puede usar [$ cond] (http://docs.mongodb.org/manual/reference/operator/aggregation/cond/#exp._S_cond) aunque la sintaxis es horrible: $ project: {ageLowerBound: {$ cond: [{$ lt: [$ age, 2]}, "0", {$ cond: [{$ lt: [$ age, 4]}, "2", "4"]}]}} ... o algo por el estilo. –

+0

@Jenna ¿Qué podemos hacer contra las entradas de GeoJSON? – Pei

0

Con Mongo 3.4 es ahora aún más fácil, gracias a la nueva $ cubo y $bucketAuto funciones de agregación. La siguiente consulta auto-cubos en dos grupos:

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

Con los siguientes datos de entrada:

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

Se da el siguiente resultado:

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

Nota, cubo y auto-cubo típicamente se usan para variables continuas (numéricas, de fecha), pero en este caso el auto-bucket funciona muy bien.

0

Sobre la base de la respuesta de @ColinE de agrupación de histograma se puede hacer por

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 no funcionó para mí desde cubos parecen estar recogido en una escala logarítmica. allowDiskUse solo es necesario si tiene millones de documentos