Connexion à MongoDB sous Python

Nous allons voir dans ce TP comment nous pouvons récupérer des données stockées dans une base MongoDB, 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.

In [1]:
import pymongo

Utilisation générale

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.

In [2]:
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().

In [3]:
con.database_names()
Out[3]:
['admin',
 'config',
 'gym',
 'horodateurs',
 'local',
 'medicaments',
 'test',
 'trafic',
 'vlsstat',
 'world']

Nous allons utiliser la base gym. Nous allons créer un connecteur directement à cette base.

In [4]:
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().

In [5]:
db.collection_names()
Out[5]:
['Sportifs', 'Gymnases']

Fonctions de base

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().

In [6]:
db.Sportifs.count()
Out[6]:
150
In [7]:
db.Gymnases.count()
Out[7]:
28

Pour obtenir le premier document d'une collection, on utilise ici la fonction find_one().

In [8]:
db.Sportifs.find_one()
Out[8]:
{'Age': 30,
 'IdSportif': 1,
 'Nom': 'BOUTAHAR',
 'Prenom': 'Abderahim',
 'Sexe': 'm',
 'Sports': {'Arbitrer': ['Basket ball', 'Volley ball', 'Hockey'],
  'Entrainer': ['Basket ball',
   'Volley ball',
   'Hand ball',
   'Hockey',
   'Badmington'],
  'Jouer': ['Volley ball', 'Tennis', 'Football']},
 '_id': '566eec5f662b388eba464203'}
In [9]:
db.Gymnases.find_one()
Out[9]:
{'Adresse': '2 rue des pépines',
 'IdGymnase': 1,
 'NomGymnase': 'PAUL ELUARD',
 'Seances': [{'Duree': 60,
   'Horaire': 9.0,
   'IdSportifEntraineur': 149,
   'Jour': 'Samedi',
   'Libelle': 'Basket ball'},
  {'Duree': 60,
   'Horaire': 9.0,
   'IdSportifEntraineur': 1,
   'Jour': 'Lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 60,
   'Horaire': 10.0,
   'IdSportifEntraineur': 1,
   'Jour': 'Lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 60,
   'Horaire': 11.3,
   'IdSportifEntraineur': 1,
   'Jour': 'Lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 90,
   'Horaire': 14.0,
   'IdSportifEntraineur': 1,
   'Jour': 'Lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 17.3,
   'IdSportifEntraineur': 1,
   'Jour': 'lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 19.3,
   'IdSportifEntraineur': 1,
   'Jour': 'Lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 17.3,
   'IdSportifEntraineur': 2,
   'Jour': 'Dimanche',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 19.3,
   'IdSportifEntraineur': 2,
   'Jour': 'Dimanche',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 17.3,
   'IdSportifEntraineur': 2,
   'Jour': 'mardi',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 17.3,
   'IdSportifEntraineur': 2,
   'Jour': 'mercredi',
   'Libelle': 'Hand ball'},
  {'Duree': 60,
   'Horaire': 15.3,
   'IdSportifEntraineur': 2,
   'Jour': 'Samedi',
   'Libelle': 'Hand ball'},
  {'Duree': 60,
   'Horaire': 16.3,
   'IdSportifEntraineur': 2,
   'Jour': 'Samedi',
   'Libelle': 'Hand ball'},
  {'Duree': 120,
   'Horaire': 17.3,
   'IdSportifEntraineur': 2,
   'Jour': 'Samedi',
   'Libelle': 'Hand ball'},
  {'Duree': 30,
   'Horaire': 20.0,
   'IdSportifEntraineur': 3,
   'Jour': 'jeudi',
   'Libelle': 'Hand ball'},
  {'Duree': 60,
   'Horaire': 14.0,
   'IdSportifEntraineur': 3,
   'Jour': 'lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 30,
   'Horaire': 18.0,
   'IdSportifEntraineur': 3,
   'Jour': 'lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 30,
   'Horaire': 19.0,
   'IdSportifEntraineur': 3,
   'Jour': 'lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 30,
   'Horaire': 20.0,
   'IdSportifEntraineur': 3,
   'Jour': 'lundi',
   'Libelle': 'Hand ball'},
  {'Duree': 90,
   'Horaire': 17.0,
   'IdSportifEntraineur': 7,
   'Jour': 'mercredi',
   'Libelle': 'Hockey'}],
 'Surface': 200,
 'Ville': 'STAINS',
 '_id': '566eec69662b388eba464299'}

Et on peut lister les valeurs prises par un champs dans une collection par la fonction ditinct().

In [10]:
db.Sportifs.distinct("Sexe")
Out[10]:
['m', 'M', 'F']
In [11]:
db.Sportifs.distinct("Sports.Jouer")
Out[11]:
['Football',
 'Tennis',
 'Volley ball',
 'Basket ball',
 'Ping pong',
 'Badmington',
 'Hand ball']
In [12]:
db.Gymnases.distinct("Ville")
Out[12]:
['STAINS',
 'MONTMORENCY',
 'PIERREFITTE',
 'SARCELLES',
 'SAINT DENIS',
 'VILLETANEUSE',
 'GARGES']
In [13]:
db.Gymnases.distinct("Surface")
Out[13]:
[200, 450, 400, 500, 620, 360, 420, 300, 480, 600, 520, 350]
In [14]:
db.Gymnases.distinct("Seances.Libelle")
Out[14]:
['Basket ball', 'Hand ball', 'Hockey', 'Volley ball']
In [15]:
db.Gymnases.distinct("Seances.Jour")
Out[15]:
['Dimanche',
 'Lundi',
 'Samedi',
 'jeudi',
 'lundi',
 'mardi',
 'mercredi',
 'dimanche',
 'Mercredi',
 'Vendredi',
 'vendredi',
 'samedi',
 'Mardi',
 'Jeudi']

Affichage des résultats

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.

In [16]:
import pprint
pprint.pprint(db.Sportifs.find_one())
{'Age': 30,
 'IdSportif': 1,
 'Nom': 'BOUTAHAR',
 'Prenom': 'Abderahim',
 'Sexe': 'm',
 'Sports': {'Arbitrer': ['Basket ball', 'Volley ball', 'Hockey'],
            'Entrainer': ['Basket ball',
                          'Volley ball',
                          'Hand ball',
                          'Hockey',
                          'Badmington'],
            'Jouer': ['Volley ball', 'Tennis', 'Football']},
 '_id': '566eec5f662b388eba464203'}

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.

In [17]:
res = db.Sportifs.find({ "Nom": "KERVADEC" })
print(res)
<pymongo.cursor.Cursor object at 0x7fec0003f240>
In [18]:
print(res.count())
1

Voici comment réaliser une boucle sur le résultat pour l'affichage.

In [19]:
for r in res:
    pprint.pprint(r)
{'Age': 28,
 'IdSportif': 2,
 'IdSportifConseiller': 1,
 'Nom': 'KERVADEC',
 'Prenom': 'Yann',
 'Sexe': 'M',
 'Sports': {'Arbitrer': ['Hockey', 'Football'],
            'Entrainer': ['Basket ball',
                          'Volley ball',
                          'Hand ball',
                          'Tennis',
                          'Hockey',
                          'Badmington',
                          'Ping pong',
                          'Boxe'],
            'Jouer': ['Basket ball', 'Volley ball', 'Ping pong', 'Football']},
 '_id': '566eec5f662b388eba464204'}

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.

In [20]:
res = db.Sportifs.find({ "Nom": "KERVADEC" })
pprint.pprint(list(res))
[{'Age': 28,
  'IdSportif': 2,
  'IdSportifConseiller': 1,
  'Nom': 'KERVADEC',
  'Prenom': 'Yann',
  'Sexe': 'M',
  'Sports': {'Arbitrer': ['Hockey', 'Football'],
             'Entrainer': ['Basket ball',
                           'Volley ball',
                           'Hand ball',
                           'Tennis',
                           'Hockey',
                           'Badmington',
                           'Ping pong',
                           'Boxe'],
             'Jouer': ['Basket ball', 'Volley ball', 'Ping pong', 'Football']},
  '_id': '566eec5f662b388eba464204'}]

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().

In [21]:
def affiche(res):
    pprint.pprint(list(res))

Et voici comment utiliser cette fonction.

In [22]:
res = db.Sportifs.find({ "Nom": "KERVADEC" }, { "_id": 0, "Nom": 1 })
affiche(res)
[{'Nom': 'KERVADEC'}]

Recherche d'informations

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).

In [23]:
res = db.Sportifs.find({ "Age": { "$gte": 32 } }, { "_id": 0, "Nom": 1, "Age": 1 })
affiche(res)
[{'Age': 32, 'Nom': 'DORLEANS'},
 {'Age': 40, 'Nom': 'RABAHI'},
 {'Age': 32, 'Nom': 'TIZEGHAT'},
 {'Age': 32, 'Nom': 'BAZOUD'},
 {'Age': 39, 'Nom': 'SARRAZIN'},
 {'Age': 40, 'Nom': 'HOUEL'},
 {'Age': 36, 'Nom': 'LEROUX'},
 {'Age': 32, 'Nom': 'BONE'}]

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.

In [24]:
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)
[{'Nom': 'COMES',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball',
                       'Volley ball',
                       'Badmington',
                       'Ping pong']}},
 {'Nom': 'RETALDI',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball',
                       'Volley ball',
                       'Hand ball',
                       'Ping pong']}},
 {'Nom': 'CAILLIOT',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Volley ball', 'Ping pong']}},
 {'Nom': 'LEJEUNE',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball',
                       'Volley ball',
                       'Badmington',
                       'Ping pong']}},
 {'Nom': 'HEDDI',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Badmington', 'Ping pong']}},
 {'Nom': 'JOUVE',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Ping pong']}},
 {'Nom': 'GUERRAOUI',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Ping pong']}},
 {'Nom': 'CLERICE',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Badmington', 'Ping pong']}},
 {'Nom': 'MARIE',
  'Sexe': 'F',
  'Sports': {'Jouer': ['Basket ball', 'Hand ball', 'Ping pong']}},
 {'Nom': 'BELZ', 'Sexe': 'F', 'Sports': {'Jouer': 'Basket ball'}}]

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).

In [25]:
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)
[{'Age': 40, 'Nom': 'HOUEL'},
 {'Age': 40, 'Nom': 'RABAHI'},
 {'Age': 39, 'Nom': 'SARRAZIN'},
 {'Age': 36, 'Nom': 'LEROUX'},
 {'Age': 32, 'Nom': 'BAZOUD'},
 {'Age': 32, 'Nom': 'BONE'},
 {'Age': 32, 'Nom': 'DORLEANS'},
 {'Age': 32, 'Nom': 'TIZEGHAT'}]

Agrégats

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.

In [26]:
res = db.Gymnases.aggregate([ 
    { "$group": { "_id": "Total", "nb": { "$sum": 1 }, "surfmoy": { "$avg": "$Surface" }}}
])
affiche(res)
[{'_id': 'Total', 'nb': 28, 'surfmoy': 444.2857142857143}]

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).

In [27]:
res = db.Gymnases.aggregate([ 
    { "$group": { 
        "_id": "$Ville", 
        "nb": { "$sum": 1 }, 
        "surfaceTotale": { "$sum": "$Surface" },
        "surfaceMoyenne": { "$avg": "$Surface" },
        "surfaceMinimum": { "$min": "$Surface" },
        "surfaceMaximum": { "$max": "$Surface" }
    }}
])
affiche(res)
[{'_id': 'SAINT DENIS',
  'nb': 3,
  'surfaceMaximum': 520,
  'surfaceMinimum': 450,
  'surfaceMoyenne': 490.0,
  'surfaceTotale': 1470},
 {'_id': 'STAINS',
  'nb': 6,
  'surfaceMaximum': 400,
  'surfaceMinimum': 200,
  'surfaceMoyenne': 333.3333333333333,
  'surfaceTotale': 2000},
 {'_id': 'MONTMORENCY',
  'nb': 5,
  'surfaceMaximum': 500,
  'surfaceMinimum': 420,
  'surfaceMoyenne': 470.0,
  'surfaceTotale': 2350},
 {'_id': 'GARGES',
  'nb': 1,
  'surfaceMaximum': 400,
  'surfaceMinimum': 400,
  'surfaceMoyenne': 400.0,
  'surfaceTotale': 400},
 {'_id': 'VILLETANEUSE',
  'nb': 3,
  'surfaceMaximum': 620,
  'surfaceMinimum': 350,
  'surfaceMoyenne': 523.3333333333334,
  'surfaceTotale': 1570},
 {'_id': 'PIERREFITTE',
  'nb': 5,
  'surfaceMaximum': 450,
  'surfaceMinimum': 300,
  'surfaceMoyenne': 382.0,
  'surfaceTotale': 1910},
 {'_id': 'SARCELLES',
  'nb': 5,
  'surfaceMaximum': 620,
  'surfaceMinimum': 400,
  'surfaceMoyenne': 548.0,
  'surfaceTotale': 2740}]

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.

In [28]:
res = db.Gymnases.aggregate([
    { "$unwind": "$Seances" }, 
    { "$project": { "Jour": { "$toLower": "$Seances.Jour" } }},
    { "$group": { "_id": "$Jour", "nb": { "$sum": 1 }} },
    { "$sort": { "nb": -1 }}
])
affiche(res)
[{'_id': 'lundi', 'nb': 23},
 {'_id': 'dimanche', 'nb': 17},
 {'_id': 'mercredi', 'nb': 13},
 {'_id': 'samedi', 'nb': 9},
 {'_id': 'mardi', 'nb': 8},
 {'_id': 'vendredi', 'nb': 6},
 {'_id': 'jeudi', 'nb': 6}]

Importation dans un 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.

In [29]:
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).

In [30]:
df.head()
Out[30]:
Age IdSportif IdSportifConseiller Nom Prenom Sexe Sports _id
0 30 1 NaN BOUTAHAR Abderahim m {'Entrainer': ['Basket ball', 'Volley ball', '... 566eec5f662b388eba464203
1 28 2 1.0 KERVADEC Yann M {'Entrainer': ['Basket ball', 'Volley ball', '... 566eec5f662b388eba464204
2 25 3 1.0 HUE Pascale F {'Entrainer': ['Basket ball', 'Volley ball', '... 566eec5f662b388eba464205
3 32 4 1.0 DORLEANS Jean-michel M {'Entrainer': ['Basket ball', 'Ping pong', 'Bo... 566eec5f662b388eba464206
4 22 5 1.0 COMES Sylvie F {'Arbitrer': 'Ping pong', 'Jouer': ['Basket ba... 566eec5f662b388eba464207

Nous récupérons ici le premier sportif dans l'objet df0.

In [31]:
df0 = df.loc[0,:]

Avec cet objet, nous accédons aux sports qu'il joue/entraîne/arbitre comme suit.

In [32]:
df0.Sports
Out[32]:
{'Arbitrer': ['Basket ball', 'Volley ball', 'Hockey'],
 'Entrainer': ['Basket ball',
  'Volley ball',
  'Hand ball',
  'Hockey',
  'Badmington'],
 'Jouer': ['Volley ball', 'Tennis', 'Football']}

Pour obtenir le tableau des sports joués (par exemple), nous le récupérons avec le code suivant.

In [33]:
df0.Sports["Jouer"]
Out[33]:
['Volley ball', 'Tennis', 'Football']

A faire

Répondre aux questions suivantes

  1. Quels sont les sportifs (identifiant, nom et prénom) qui ont entre 20 et 30 ans ?
  2. Quels sont les gymnases de "Villetaneuse" ou de "Sarcelles" qui ont une surface de plus de 400 m2 ?
  3. Quels sont les sportifs (identifiant et nom) qui pratiquent du hand ball ?
  4. Dans quels gymnases et quels jours y a t-il des séances de hand ball ?
  5. Dans quels gymnases peut-on jouer au hockey le mercredi apres 15H ?
  6. Quels sportifs (identifiant et nom) ne pratiquent aucun sport ?
  7. Quels gymnases n'ont pas de séances le dimanche ?
  8. Quels gymnases ne proposent que des séances de basket ball ou de volley ball ?
  9. Quels sont les entraîneurs qui sont aussi joueurs ?
  10. Quels sont les sportifs qui sont des conseillers ?
  11. Pour le sportif "Kervadec" quel est le nom de son conseiller ?
  12. Quels entraîneurs entraînent du hand ball et du basket ball ?
  13. Quels sont les couples de sportifs (identifiant et nom et prénom de chaque) de même age ?
  14. Quelle est la moyenne d'âge des sportives qui pratiquent du basket ball ?
  15. Quels sont les sportifs les plus jeunes ?
  16. Quels sont les gymnases de "Stains" ou de "Montmorency" qui ont la plus grande surface ?
  17. Quels entraîneurs n'entraînent que du hand ball ou du basket ball ?
  18. Quels sont les couples de sportifs (identifiant et nom et prénom de chaque) de même âge avec le même conseiller ?
  19. Quels sportifs n'ont pas de conseillers ?
  20. Pour chaque sportif donner le nombre de sports qu'il arbitre
  21. Pour chaque gymnase de Stains donner par jour d'ouverture les horaires des premières et dernières séances
  22. Pour chaque entraîneurs de hand ball quel est le nombre de séances journalières qu'il assure ?
  23. Quels sont les gymnases ayant plus de 15 séances le mercredi ?
  24. Pour chaque gymnase de Montmorency : quel est le nombre de séances journalières de chaque sport propose ?
  25. Dans quels gymnases et quels jours y a t-il au moins 4 séances de volley ball dans la journée ?