cours-2023-2024 | Documents de mes cours pour l'année 2023-2024 | FX Jollois
Il est bien évidemment possible de réaliser des calculs d’agrégats (de type somme, moyenne, minimum et maximum) dans MongoDB,
avec la fonction aggregate()
. Celle-ci permet beaucoup d’autres opérations.
Cette fonction va prendre en paramètre un tableau nommé pipeline
: tableau composé d’une suite d’opérations.
Chaque opération sera faite après la précédente. L’ordre des opérations a donc une importance cruciale. Et le même opérateur peut apparaître plusieurs fois.
Voici quelques unes des opérations possibles :
Fonction | Opération |
---|---|
$limit |
restriction à un petit nombre de documents (très utiles pour tester son calcul) |
$sort |
tri sur les documents |
$match |
restriction sur les documents à utiliser |
$unwind |
séparation d’un document en plusieurs sur la base d’un tableau |
$addFields |
ajout d’un champs dans les documents |
$project |
redéfinition des documents |
$group |
regroupements et calculs d’aggégrats |
$sortByCount |
agrégat + tri |
$lookup |
jointure avec une autre collection |
… |
pipeline
$limit
On indique juste avec un entier le nombre de document que l’on veut afficher.
c = db.restaurants.aggregate([
{ "$limit": 10 }
])
pandas.DataFrame(list(c))
$sort
On indique de façon identique à celle du paramètre sort
de la fonction find()
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$sort": { "name": 1 }}
])
pandas.DataFrame(list(c))
$match
Ici, c’est identique à celle du paramètre query
des autres fonctions
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$sort": { "name": 1 }},
{ "$match": { "borough": "Brooklyn" }}
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$match": { "borough": "Brooklyn" }},
{ "$limit": 10 },
{ "$sort": { "name": 1 }}
])
pandas.DataFrame(list(c))
"$unwind"
Le but de cette opération est d’exploser un tableau dans un document.
Un document avec un tableau à n éléments deviendra n documents avec chacun un des éléments du tableau en lieu et place de celui-ci
Nous devons mettre le nom du tableau servant de base pour le découpage (précédé d’un $
)
grades
)
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$unwind": "$grades" }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$unwind": "$grades" },
{ "$match": { "grades.grade": "B" }}
])
pandas.DataFrame(list(c))
$unwind
et $match
, le résultat est clairement différentc = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$match": { "grades.grade": "B" }},
{ "$unwind": "$grades" }
])
pandas.DataFrame(list(c))
"$addFields"
et "$project"
On souhaite ici rédéfinir les documents en ajoutant des éléments ($addFields
) ou en se restreignant à certains éléments
($project
)
{ "champs" : 1 }
: conservation du champs (0 si suppression - idem que dans find()
, pas de mélange sauf pour _id
)
$project
{ "champs": { "$opérateur" : expression }}
: permet de définir un nouveau champs{ "nouveau_champs": "$ancien_champs" }
: renommage d’un champsQuelques opérateurs utiles pour la projection (plus d’info ici)
$arrayElemAt
: élément d’un tableau$first
et $last
: premier ou dernier élément du tableau$size
: taille d’un tableau$substr
: sous-chaîne de caractères$cond
: permet de faire une condition (genre de if then else)…
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$addFields": { "nb_grades": { "$size": "$grades" } } }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1 } }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "address": 0, "grades": 0 } }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1 , "street": "$address.street"} }
])
pandas.DataFrame(list(c))
grades
)c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } }
])
pandas.DataFrame(list(c))
grades
est préent mais égal à NULL
)grades
c = db.restaurants.aggregate([
{ "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } },
{ "$sort": { "nb_grades": 1 }},
{ "$limit": 10 }
])
pandas.DataFrame(list(c))
grades
(indicé 0)c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1, "grade": { "$arrayElemAt": [ "$grades", 0 ]} } }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": { "nom": { "$toUpper": "$name" }, "borough": 1 } }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$project": {
"nom": { "$toUpper": "$name" },
"quartier": { "$substr": [ "$borough", 0, 3 ] }
} }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$addFields": { "quartier": { "$toUpper": { "$substr": [ "$borough", 0, 3 ] } } }},
{ "$project": {
"nom": { "$toUpper": "$name" },
"quartier": { "$cond": { "if": { "$eq": ["$borough", "Bronx"] }, "then": "BRX", "else": "$quartier" } },
"borough": 1
} }
])
pandas.DataFrame(list(c))
"$group"
Cet opérateur permet le calcul d’agrégats tel qu’on le connaît.
_id
: déclaration du critère de regroupement
$champs
: regroupement selon ce champs{ "a1": "$champs1", ... }
: regroupement multiple (avec modification des valeurs possible)$sum
: somme (soit de valeur fixe - 1 pour faire un décompte donc, soit d’un champs spécifique)$avg, $min, $max
$addToSet
: regroupement des valeurs distinctes d’un champs dans un tableau$push
: aggrégation de champs dans un tableauc = db.restaurants.aggregate([
{ "$group": { "_id": "Total", "NbRestos": { "$sum": 1 }}}
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$group": { "_id": "$borough", "NbRestos": { "$sum": 1 }}}
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$match": { "borough": "Queens" }},
{ "$unwind": "$grades" },
{ "$group": { "_id": "null", "score": { "$avg": "$grades.score" }}}
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$unwind": "$grades" },
{ "$group": { "_id": "$borough", "score": { "$avg": "$grades.score" }}},
{ "$sort": { "score": -1 }}
])
pandas.DataFrame(list(c))
$match
permet de supprimer les restaurants sans évaluations (ce qui engendrerait des moyennes = NA
)c = db.restaurants.aggregate([
{ "$project": {
"borough": 1, "street": "$address.street",
"eval": { "$arrayElemAt": [ "$grades", 0 ]}
} },
{ "$match": { "eval": { "$exists": True } } },
{ "$match": { "eval.score": { "$gte": 0 } } },
{ "$group": {
"_id": { "quartier": "$borough", "rue": "$street" },
"score": { "$avg": "$eval.score" }
}},
{ "$sort": { "score": 1 }},
{ "$limit": 10 }
])
pandas.DataFrame(list(c))
$addToSet
et $push
, on les applique sur les grades obtenus pour les 10 premiers restaurants
$addToSet
: valeurs distinctes$push
: toutes les valeurs présentesc = db.restaurants.aggregate([
{ "$limit": 10 },
{ "$unwind": "$grades" },
{ "$group": {
"_id": "$name",
"avec_addToSet": { "$addToSet": "$grades.grade" },
"avec_push": { "$push": "$grades.grade" }
}}
])
pandas.DataFrame(list(c))
$sortBycount
Cet opérateur réalise un regroupement sur le champs spécifié (précédé d’un $
), compte le nombre de document pour chaque
modalité de ce champs, puis fait un tri décroissant sur le nombre calculé. Il est clairement fait pour réaliser des TOPs donc.
c = db.restaurants.aggregate([
{ "$sortByCount": "$borough" }
])
pandas.DataFrame(list(c))
c = db.restaurants.aggregate([
{ "$group": { "_id": "$borough", "count": { "$sum": 1 } } },
{ "$sort": { "count": -1 }}
])
pandas.DataFrame(list(c))
Une fois importées dans un DataFrame, les champs complexes (comme address
et grades
) sont des variables d’un type un peu particulier.
df = pandas.DataFrame(list(db.restaurants.find(limit = 10)))
df
Le champs address est une liste de dictionnaires, ayant chacun plusieurs champs (ici tous les mêmes).
df.address
Nom du bâtiment et rue concaténés dans une nouvelle variable de df
(utilisation de list comprehension ici)
df.assign(info = [e["building"] + ", " + e["street"] for e in df.address])
Transformation de la liste en un DataFrame
pandas.DataFrame([e for e in df.address])
pandas.concat([df.drop("address", axis = 1), pandas.DataFrame([e for e in df.address])], axis = 1)
Le champs grades
est une liste de tableaux, ayant chacun potentiellement plusieurs valeurs (des dictionnaires de plus)
df.grades
Récupération d’un élément du tableau (premier ou dernier)
df.assign(derniere = [e[0] for e in df.grades], premiere = [e[-1] for e in df.grades]).drop("grades", axis = 1)
Transformation de la liste de tableaux en un seul DataFrame
zip()
permet d’itérer sur plusieurs tableaux en même tempsconcat()
permet de concaténer les tableaux entre euxdfgrades = pandas.concat([pandas.DataFrame(g).assign(_id = i) for (i, g) in zip(df._id, df.grades)])
dfgrades
Jointure entre les deux DataFrames avec merge()
pandas.merge(df.drop("grades", axis = 1), dfgrades.reset_index())
addToSet
)grades
addToSet
, et restriction à ceux pour lequel le tableau créé = [“A”] - par exemple)push
slice
pour prendre une partie d’un tableauNous allons travailler sur des données AirBnB. Celles-ci sont stockées sur le serveur Mongo dans la collection listingsAndReviews
de la base sample_airbnb
.
Une fois créée la connexion à la collection dans Python, répondre aux questions suivantes :
room_type
)amenities
)amenities
)number_of_reviews
et reviews
(tableau des avis) - vérifiez qu’ils soient cohérents_id
)room_type
) le prix (price
)reviews
)