Python
¶Après avoir vu MongoDB d'un côté et python
de l'autre, nous allons voir dans ce TP comment les deux peuvent communiquer, par le biais de la librairie pymongo
. Il faut bien évidemment installer ce module (éventuellement localement). Pour l'utiliser, on l'importer classiquement comme ci-dessous.
import pymongo
La première opération est de créer une connexion entre python
et MongoDB en utilisant la fonction MongoClient()
. Celle-ci prend en paramètre l'adresse du serveur (IP et port). La commande ci-après permet donc de se connecter au serveur déjà vu en cours précédemment.
con = pymongo.MongoClient("mongodb://193.51.82.104:2343/")
Par le biais de l'objet ainsi créé (con
), on a accès à la liste des bases de données présentes, avec la fonction database_name()
.
con.database_names()
Nous allons utiliser la base gym
, comme dans les deux premiers TP du cours. Nous allons créer un connecteur directement à cette base.
db = con.gym
L'objet db
est doté de différentes fonctions, et permet une utilisation très proche de celle qu'on a dans le shell
de MongoDB. Pour voir la liste des collections présentes, on utilise la fonction collection_names()
.
db.collection_names()
Ensuite, pour accéder aux collections, et aux fonctions à utiliser dans celle-ci, nous utilisont le même formalisme que dans MongoDB.
Nous avons le dénombrement des collections comme suit, avec count()
.
db.Sportifs.count()
db.Gymnases.count()
Pour obtenir le premier document d'une collection, on utilise ici la fonction find_one()
.
db.Sportifs.find_one()
db.Gymnases.find_one()
Et on peut lister les valeurs prises par un champs dans une collection par la fonction ditinct()
.
db.Sportifs.distinct("Sexe")
db.Sportifs.distinct("Sports.Jouer")
db.Gymnases.distinct("Ville")
db.Gymnases.distinct("Surface")
db.Gymnases.distinct("Seances.Libelle")
db.Gymnases.distinct("Seances.Jour")
Nous l'avons vu précédemment, l'affichage du résultat de la fonction find_one()
n'est pas totalement lisible. Il est possible d'utiliser la fonction pprint()
du module pprint
, normalement installé dès l'installation de python
. Celle-ci améliore l'affichage, en ajoutant des indentations.
import pprint
pprint.pprint(db.Sportifs.find_one())
Par contre, pour l'utiliser sur le résultat de la fonction find()
(qui renvoie un curseur, puisqu'il peut y avoir plusieurs documents correspondants), il faut soit faire une boucle sur ce curseur, soit le transformer en list
.
res = db.Sportifs.find({ "Nom": "KERVADEC" })
print(res)
print(res.count())
Voici comment réaliser une boucle sur le résultat pour l'affichage.
for r in res:
pprint.pprint(r)
Et voici un exemple de transformation en list
. Une fois que le curseur est lu, comme ici dans la boucle, il est vide. Il faut donc le recalculer.
res = db.Sportifs.find({ "Nom": "KERVADEC" })
pprint.pprint(list(res))
On créé ici une fonction simple, nommée affichage()
, qui va prendre en paramètre un curseur renvoyé par la fonction find()
et qui affichera proprement le résultat via pprint()
.
def affiche(res):
pprint.pprint(list(res))
Et voici comment utiliser cette fonction.
res = db.Sportifs.find({ "Nom": "KERVADEC" }, { "_id": 0, "Nom": 1 })
affiche(res)
Comme nous allons le voir par la suite, nous allons utiliser exactement le même formalisme que dans MongoDB, avec la fonction find()
. Ceci est possible avec l'utilisation de dictionnaires pour les paramètres de critères de sélection des documents et pour les choix d’items des documents à afficher. La seule obligation est de mettre les noms des champs entre "..."
, alors qu'il était possible de ne pas le faire dans MongoDB.
Ici, nous recherchons les sportifs d'au moins 32 ans, en affichant que leur nom et leur âge. On voit ici un effet de python
sur l'ordre d'affichage des items (ici, dans l'ordre alphabétique - comme toujours dans python
).
res = db.Sportifs.find({ "Age": { "$gte": 32 } }, { "_id": 0, "Nom": 1, "Age": 1 })
affiche(res)
On peut aussi stocker les critères et les choix en sortie dans des variables, pour les utiliser dans find()
. Ici, nous recherchons les sportives jouant au Basket.
criteres = { "Sports.Jouer" : "Basket ball", "Sexe" : "F" }
sortie = { "_id": 0, "Nom": 1, "Sexe": 1, "Sports.Jouer": 1}
res = db.Sportifs.find(criteres, sortie)
affiche(res)
Pour le tri, on utilise la fonction sort()
sur le résultat. Par contre, nous devons ici mettre une liste de critères de tri. Ceux-ci doivent tous être des tuples à deux valeurs : le champ de tri et l'odre choisi (ascendant ou descendant). Pour le spécifier, nous devons utiliser deux valeurs spécifiques du module : DESCENDING
(pour descendant) et ASCENDING
(pour ascendant).
Ici, nous reprenons la recherche précédent des sportifs d'au moins 32 ans. Mais le résultat est trié par ordre décroissant sur l'âge, et par ordre alphabétique pour le nom (pour ceux ayant le même âge donc).
res = db.Sportifs.find({ "Age": { "$gte": 32} }, { "_id": 0, "Nom": 1, "Age": 1 })
res_tri = res.sort([ ("Age", pymongo.DESCENDING), ("Nom", pymongo.ASCENDING) ])
affiche(res_tri)
Nous avons aussi la possibilité de calculer des agrégats, avec la fonction aggregate()
. Celle-ci s'utilise de manière très similaire à celle de MongoDB.
Ci-dessous, nous calculons le nombre de gymnases et la surface moyenne de celle-ci. Pour ne pas mettre de critère d'agrégations, nous mettons une chaîne de caractère comme identifiant.
res = db.Gymnases.aggregate([
{ "$group": { "_id": "Total", "nb": { "$sum": 1 }, "surfmoy": { "$avg": "$Surface" }}}
])
affiche(res)
Pour faire une agrégation sur un critère, on indique le champs toujours avec le symbole "$"
devant. Nous avons ici, pour chaque ville, le nombre de gymnases et des statistiques simples sur la surface de ceux-ci (totale, moyenne, minimum et maximum).
res = db.Gymnases.aggregate([
{ "$group": {
"_id": "$Ville",
"nb": { "$sum": 1 },
"surfaceTotale": { "$sum": "$Surface" },
"surfaceMoyenne": { "$avg": "$Surface" },
"surfaceMinimum": { "$min": "$Surface" },
"surfaceMaximum": { "$max": "$Surface" }
}}
])
affiche(res)
Et comme dans MongoDB, on peut utiliser les commandes unwind
, project
et sort
pour réaliser des calculs d'agrégats complexes. Ici, nous cherchons le nombre total de séances par jour, ceux-ci étant triés dans l'ordre décroissant du nombre de séances.
res = db.Gymnases.aggregate([
{ "$unwind": "$Seances" },
{ "$project": { "Jour": { "$toLower": "$Seances.Jour" } }},
{ "$group": { "_id": "$Jour", "nb": { "$sum": 1 }} },
{ "$sort": { "nb": -1 }}
])
affiche(res)
DataFrame
¶Pour pouvoir utiliser les données recherchées, nous pouvons les transformer en DataFrame
(de type pandas
). Pour cela, nous les transformons en list
, puis en DataFrame
, comme dans l'exemple ci-dessous.
import pandas
res = db.Sportifs.find()
df = pandas.DataFrame(list(res))
Quand on regarde le DataFrame
obtenu, nous remarquons que pour certains colonnes (Sports
ici), le contenu est un objet complexe (un dictionnaire en l'occurence ici).
df.head()
Nous récupérons ici le premier sportif dans l'objet df0
.
df0 = df.loc[0,:]
Avec cet objet, nous accédons aux sports qu'il joue/entraîne/arbitre comme suit.
df0.Sports
Pour obtenir le tableau des sports joués (par exemple), nous le récupérons avec le code suivant.
df0.Sports["Jouer"]
Nous allons utiliser la base de données test
, dans laquelle se trouve la collection restaurants
. Celle-ci est composée de plus de 25000 retaurants new-yorkais, avec des évaluations de ceux-ci.
Z
.DataFrame
contenant les restaurants en ligne et en colonne le score moyen obtenu pour chaque grade possible. Si un restaurant n'a pas eu d'évaluation d'un grade, nous aurons une valeur absente.