Requêtes depuis R : mongolite
Contents
7.2. Requêtes depuis R : mongolite
¶
Auteurs/trices : Faisal JAYOUZI, Paul LANCELIN, Yolan PERONNET
Le chapitre suivant a pour objectif d’explorer le package mongolite permettant d’effectuer des requêtes MongoDB depuis R. La rédaction du tutoriel suivant trouve sa source dans la documentation rédigée par l’auteur du package mongolite, Jeroen Ooms. Celle-ci est accessible à l’adresse suivante : https://jeroen.github.io/mongolite/
7.2.1. Installation du package mongolite et connexion à un serveur MongoDB¶
7.2.1.1. Installation et chargement du package mongolite¶
Les packages binaires de mongolite peuvent être installés directement depuis le CRAN via la manipulation suivante à partir de la barre d’outils de RStudio :
Tools -> Install Packages -> mongolite -> Install
Ou via la commande suivante à exécuter dans la console ou depuis un script R :
install.packages("mongolite")
Vous pouvez également installer la version de développement, qui contient les dernières fonctionnalités. Pour cela, exécutez la commande suivante :
devtools::install_github("jeroen/mongolite")
Remarque
L’installation et le chargement du package devtools au préalable sera nécessaire pour cette dernière exécution.
Une fois le package installé, il vous suffira de le charger dans votre envionnement de travail R via la commande suivante à exécuter dans la console ou depuis un script R :
library(mongolite)
7.2.1.2. Connexion à une collection d’une base de données présente sur un serveur MongoDB¶
Après avoir chargé les packages nécessaires dans votre environnement R, vous pourrez vous connecter à une collection d’une base de données présente sur un serveur MongoDB à partir d’un lien URI, du nom de la base de données, et du nom de la collection à laquelle vous souhaitez accéder. Pour ce faire, il suffit d’utiliser la fonction mongo()
de la librairie mongolite de la manière suivante :
coll <- mongo(collection="ma_collection", db="ma_BDD",
url="mon_uri",
verbose=TRUE)
La fonction mongo()
prend en entrée les arguments suivants :
collection : nom de la collection à laquelle se connecter. La valeur par défaut est “test”
db : nom de la base de données à laquelle se connecter. La valeur par défaut est “test”.
url : adresse du serveur MongoDB au format URI standard.
verbose : si TRUE, émet une sortie supplémentaire
options : options de connexion supplémentaires telles que les clés et certificats SSL que nous ne developperons pas dans ce tutoriel.
Remarque
La fonction mongo()
prend obligatoirement en entrée le nom d’une collection d’une base de données. Nous comprenons alors que mongolite nous permet seulement d’intéragir avec une collection d’une base données, et non pas avec la base tout entière. Ici nous aurons donc qu’un seul objet pointant sur une collection avec laquelle intéragir. Nous n’aurons donc pas comme sur python et sa librarie pymongo d’objets clients et d’objets base de données.
Astuce
L’adresse URI à spécifier dans le paramètre URL définit l’adresse du serveur et des options de connexion supplémentaires. Parmi ces options, nous pouvons notamment spécifier des mots de passe que nous vous conseillons de lire dans des fichiers externes afin d’en préserver la confidentialité. Afin d’obtenir plus de précisions sur le format exact de l’URI attendu (authentification, tunnel SSH, options SSL et options de réplique), nous vous renvoyons à la documentation.
Pour ce qui est de sa sortie, la fonction mongo()
renvoie un objet propre à sa librairie mère, une Mongo collection, qui, comme nous l’avons vu précédemment, pointe sur une collection d’une base de données. Regardons de plus près à quoi correspond ce type d’objet sur un exemple concret. Ici nous nous connectons à la collection “NYfood” d’une base de données “food” contenant de nombreuses informations sur les restaurants de New-York.
library(mongolite)
coll <- mongo(collection="NYfood", db="food",url = "mongodb://localhost:27017/food", verbose=TRUE)
coll
<Mongo collection> 'NYfood'
$aggregate(pipeline = "{}", options = "{\"allowDiskUse\":true}", handler = NULL, pagesize = 1000, iterate = FALSE)
$count(query = "{}")
$disconnect(gc = TRUE)
$distinct(key, query = "{}")
$drop()
$export(con = stdout(), bson = FALSE, query = "{}", fields = "{}", sort = "{\"_id\":1}")
$find(query = "{}", fields = "{\"_id\":0}", sort = "{}", skip = 0, limit = 0, handler = NULL, pagesize = 1000)
$import(con, bson = FALSE)
$index(add = NULL, remove = NULL)
$info()
$insert(data, pagesize = 1000, stop_on_error = TRUE, ...)
$iterate(query = "{}", fields = "{\"_id\":0}", sort = "{}", skip = 0, limit = 0)
$mapreduce(map, reduce, query = "{}", sort = "{}", limit = 0, out = NULL, scope = NULL)
$remove(query, just_one = FALSE)
$rename(name, db = NULL)
$replace(query, update = "{}", upsert = FALSE)
$run(command = "{\"ping\": 1}", simplify = TRUE)
$update(query, update = "{\"$set\":{}}", filters = NULL, upsert = FALSE, multiple = FALSE)
Nous constatons alors que la Mongo collection est un environnement contenant les informations de la collection “NYfood” avec lequel nous pouvons intéragir via de nombreuses méthodes. Chacune de ces méthodes s’appliquera sur une Mongo collection à l’aide d’un $
et permettra d’effectuer l’équivalent d’une requête MondoDB sur une collection. Par exemple, pour faire une simple requête find en NoSQL récupérant tous les documents d’une collection, il suffira d’écrire :
coll$find()
Ou encore pour affichez la liste des index de la collection NYfood, il suffira d’écrire :
coll$index()
L’objet des prochaines sections de ce chapitre sera alors d’explorer ces différentes méthodes et de voir leurs requêtes équivalentes en NoSQL. Nous reviendrons notamment sur les objets renvoyés par ces différentes méthodes. Toutefois, nous pouvons d’ores et déjà remarquer qu’une méthode find()
renvoie la collection complète dans un dataframe ce qui, dans des cas de grands volumes de données, pourrait entrainer des saturations de mémoire de votre machine. Nous verrons par la suite que pour s’affranchir de ce problème, nous pourrons utiliser une méthode iterate()
, similaire à la méthode find()
, renvoyant non plus un dataframe, mais un Mongo iterator. Un Mongo iterator est un objet propre à mongolite permettant de ne pas stocker explicitement en mémoire le résultat d’une requête mais d’en conserver un itérateur.
7.2.2. Requêtes de données¶
Mongolite utilise une syntaxe JSON dans les arguments de ses méthodes pour interroger des collections. Cette syntaxe JSON devra être spécifiée sous forme de chaîne de caractère dans R. Ces différentes chaînes feront alors l’objet de divers paramètres pour les méthodes présentées dans ce chapitre.
7.2.2.1. Méthode count¶
Tout comme le .count()
en MongoDB (plus de précisions ici), la méthode count()
nous permet de calculer le nombre de résultats d’une collection, ou encore le nombre de résultats d’une certaine requête. Cette méthode n’a qu’un seul argument (query) et sa valeur par défaut est “{}”. Elle renvoie un entier.
Exemples :
Afficher le nombre de résultats de la base NYfood par exemple :
req = "{}"
coll$count(query=req)
Nous pouvons également noter l’équivalent en MongoDB :
req = "{}"
coll$count(query=req)
db.NYfood.count()
Et pour afficher le nombre de restaurants chinois :
req = '{"cuisine":"Chinese"}'
coll$count(query=req)
db.NYfood.count({"cuisine":"Chinese"})
7.2.2.2. Méthode find¶
La méthode find()
permet, à l’instar du .find
en MongoDB (plus de précisions ici), d’interroger une collection en filtrant les documents et les champs. De plus, cette méthode intègre la possibilité de limiter, trier et skiper les documents d’un résultat. Autrement dit, cette méthode comprends en ses arguments le .limit
, le .sort
et le .skip
de MongoDB. Présentons alors les 5 arguments de cette méthode :
query : correspond à la requête de filtrage des documents passée en premier agument d’un
.find
en MongoDB ; ‘{}’ est la valeur par défautfields : correspond à la requête de filtrage des champs passée en second argument d’un
.find
en MongoDBsort : correspond à la requête de tri passée argument d’un .sort en MongoDB
limit : correspond à la requête de limitation du nombre de documents retournées, passée en argument d’un
.limit
en MongoDBskip : correspond à la requête de choix d’une ligne de départ des documents retournées, passée en argument d’un
.skip
en MongoDB
Comme nous avons pu l’énoncer précedémment, cette méthode stocke le résultat dans un dataframe sous R, prenant ainsi de la place en mémoire. Cette prise de place en mémoire peut être problématique, c’est pourquoi nous verrons par la suite l’intérêt de la méthode iterate()
et de l’objet Mongo iterator qu’elle renvoie.
Exemple :
Regardons les 5 premières lignes du dataframe contenant les noms des restaurants chinois à Brooklyn triés par ordre alphabétique inverse (i.e, de Z à A) :
data <- coll$find(query = '{"cuisine":"Chinese", "borough":"Brooklyn"}',
fields = '{"name": true, "_id":false}',
sort = '{"name":-1}',
limit = 5)
data
Found 5 records...
Imported 5 records. Simplifying into dataframe...
name | |
---|---|
<chr> | |
1 | Zhang'S Garden |
2 | Zhang'S Fortune Restaurant |
3 | Zeng'S Restaurant |
4 | Yun Nan Flavour Garden |
5 | Yummy Yummy Kitchen |
L’équivalent en MongoDB est le suivant :
data <- coll$find(query = '{"cuisine":"Chinese", "borough":"Brooklyn"}',
fields = '{"name": true, "_id":false}',
sort = '{"name":-1}',
limit = 5)
data
db.NYfood.find({"cuisine":"Chinese", "borough":"Brooklyn"},
{"name":true, "_id":false}).sort({"name":-1}).limit(5)
Rappelons que le tri par toute variable autre que l’identifiant peut être relativement lent, surtout lorsque la collection est de taille importante car seul l’identifiant est indexé. En ajoutant un index, le champ est pré-trié et son tri est presque immédiat. Pour ajouter un index avec mongolite, il suffit de le déclarer avec la méthode index(add='{"variable":1}')
.
Exemple :
Créer un index sur le champs name :
coll$index(add = '{"name" : 1}')
db.NYfood.createIndex({"name": 1}
Il est aussi possible de faire des requêtes textuelles avec mongolite. Toutefois attention, cela se fait obligatoirement à l’aide de l’opérateur $regex
. En effet, avec mongolite on ne peut pas faire de requêtes textuelles à l’aide d’expressions régulières car le package permettant de convertir une chaîne de caractères en fichier JSON dans R ne connait pas les expressions régulières. Hormis ce détail, la syntaxe entre simples quotes est la même que pour les requêtes textuelles en MongoDB (plus de précisions ici).
Ainsi, pour afficher les 10 premiers restaurants de Manhattan dont le nom commence par la lettre ‘A’ par exemple, la requête adaptée est :
q = '{"borough": "Manhattan", "name": {"$regex": "^A", "$options":"i"}}'
res <- coll$find(query = q, limit=10)
print(res)
Found 10 records...
Imported 10 records. Simplifying into dataframe...
address.building address.loc.type address.loc.coordinates
1 18 Point -73.99698, 40.72589
2 146 Point -73.99730, 40.71887
3 106 Point -74.00033, 40.72749
4 101 Point -73.97831, 40.76323
5 807 Point -73.96643, 40.76417
6 1617 Point -73.94785, 40.77548
7 Point -73.97911, 40.78348
8 1207 Point -73.95926, 40.80886
9 463 Point -73.9897, 40.7517
10 762 Point -73.98820, 40.76414
address.street address.zipcode borough cuisine
1 West Houston Street 10012 Manhattan American
2 Mulberry Street 10013 Manhattan Italian
3 West Houston Street 10012 Manhattan Italian
4 West 55 Street 10019 Manhattan American
5 Lexington Avenue 10065 Manhattan Indian
6 York Avenue 10028 Manhattan Italian
7 W 79 Street 10024 Manhattan American
8 Amsterdam Avenue 10027 Manhattan American
9 7 Avenue 10018 Manhattan American
10 9 Avenue 10019 Manhattan Mexican
grades
1 1396483200, 1365120000, 1332288000, 1303862400, A, A, A, A, 9, 4, 13, 5
2 1398988800, 1363219200, 1348617600, 1329264000, 1316044800, A, A, A, A, A, 11, 13, 9, 13, 11
3 1396224000, 1381190400, 1364515200, 1346803200, 1330905600, A, A, A, A, A, 13, 12, 12, 7, 12
4 1413849600, 1392336000, 1357257600, 1326844800, A, A, A, A, 7, 9, 8, 7
5 1414454400, 1404864000, 1389916800, 1369699200, 1332806400, 1320192000, A, B, A, A, A, C, 10, 24, 11, 9, 13, 28
6 1396396800, 1363132800, 1347408000, 1329782400, 1311120000, A, A, A, B, B, 13, 12, 13, 21, 21
7 1399507200, 1378944000, 1360627200, 1319500800, 1306368000, A, A, A, A, A, 12, 11, 13, 3, 13
8 1409702400, 1397779200, 1379980800, 1362960000, 1324339200, A, C, A, A, A, 12, 0, 10, 11, 12
9 1393545600, 1382313600, 1350518400, 1338854400, 1321315200, 1306454400, A, C, A, A, A, A, 12, 9, 13, 12, 9, 10
10 1419811200, 1385942400, 1353888000, A, A, A, 9, 8, 5
name restaurant_id
1 Angelika Film Center 40362274
2 Angelo Of Mulberry St. 40365293
3 Arturo'S 40365387
4 Astro Restaurant 40373113
5 Agra Restaurant 40375376
6 Arturo'S 40375616
7 American Museum Of Natural History Food Court 40376515
8 Amsterdam Restaurant & Tapas Lounge 40377111
9 Andrews Coffee Shop 40378579
10 Arriba Arriba 40380457
Notons alors l’équivalent possible en MongoDB :
q = '{"borough": "Manhattan", "name": {"$regex": "^A", "$options":"i"}}'
coll$find(query = q, limit=10)
db.NYfood.find({"borough": "Manhattan", "name": /^A/i }).limit(10)
Remarque
Comme en NoSQL, l’accent circonflèxe sert à préciser que seulement les chaînes commençant par la lettre A seront acceptées. L’opérateur "$options":"i"
précise que la casse n’est pas importante (i.e, la chaine “abc” sera jugée acceptable).
Pour ce qui est des requêtes géospatiales, la syntaxe entre simples quotes est la même que pour les requêtes géo-spatiales en MongoDB (plus de précisions ici).
7.2.2.3. Méthode iterate¶
Nous avons vu précedemment que le renvoi d’une requête dans un dataframe R à l’aide de la méthode find()
pouvait être problématique pour de grands volumes de données. La méthode iterate()
permet de s’affranchir de ce problème. Celle-ci fonctionne exactement de la même manière que la méthode find()
mais renvoie le résultat d’une requête non pas dans un dataframe mais dans un objet propre à mongolite, un Mongo iterator. Cet objet va permettre de ne pas stocker toutes les documents de la requête en mémoire mais de les lire un par un. Pour ce faire, il existe 4 méthodes applicables à un Mongo iterator :
batch(n) : permet de parcourir n documents à la fois
json() : retourne les résultats de la requête dans un format JSON
one() : permet de parcourir un document à la fois
page() : retourne les résultats de la requête sous forme de dataframe
Lorsque l’itérateur a épuisé tous les résultats de la collection, il retourne la valeur vide (NULL).
Exemple :
La création d’un itérateur sur l’ensemble de la collection NYfood se fait comme ci-dessous :
it <- coll$iterate()
it
<Mongo iterator>
$batch(size = 1000)
$json(size = 1000)
$one()
$page(size = 1000)
Affichons par exemple les 5 premières lignes :
it$batch(5)
[[1]]
[[1]]$address
[[1]]$address$building
[1] "469"
[[1]]$address$loc
[[1]]$address$loc$type
[1] "Point"
[[1]]$address$loc$coordinates
[[1]]$address$loc$coordinates[[1]]
[1] -73.9617
[[1]]$address$loc$coordinates[[2]]
[1] 40.66294
[[1]]$address$street
[1] "Flatbush Avenue"
[[1]]$address$zipcode
[1] "11225"
[[1]]$borough
[1] "Brooklyn"
[[1]]$cuisine
[1] "Hamburgers"
[[1]]$grades
[[1]]$grades[[1]]
[[1]]$grades[[1]]$date
[1] "2014-12-30 UTC"
[[1]]$grades[[1]]$grade
[1] "A"
[[1]]$grades[[1]]$score
[1] 8
[[1]]$grades[[2]]
[[1]]$grades[[2]]$date
[1] "2014-07-01 UTC"
[[1]]$grades[[2]]$grade
[1] "B"
[[1]]$grades[[2]]$score
[1] 23
[[1]]$grades[[3]]
[[1]]$grades[[3]]$date
[1] "2013-04-30 UTC"
[[1]]$grades[[3]]$grade
[1] "A"
[[1]]$grades[[3]]$score
[1] 12
[[1]]$grades[[4]]
[[1]]$grades[[4]]$date
[1] "2012-05-08 UTC"
[[1]]$grades[[4]]$grade
[1] "A"
[[1]]$grades[[4]]$score
[1] 12
[[1]]$name
[1] "Wendy'S"
[[1]]$restaurant_id
[1] "30112340"
[[2]]
[[2]]$address
[[2]]$address$building
[1] "1007"
[[2]]$address$loc
[[2]]$address$loc$type
[1] "Point"
[[2]]$address$loc$coordinates
[[2]]$address$loc$coordinates[[1]]
[1] -73.85608
[[2]]$address$loc$coordinates[[2]]
[1] 40.84845
[[2]]$address$street
[1] "Morris Park Ave"
[[2]]$address$zipcode
[1] "10462"
[[2]]$borough
[1] "Bronx"
[[2]]$cuisine
[1] "Bakery"
[[2]]$grades
[[2]]$grades[[1]]
[[2]]$grades[[1]]$date
[1] "2014-03-03 UTC"
[[2]]$grades[[1]]$grade
[1] "A"
[[2]]$grades[[1]]$score
[1] 2
[[2]]$grades[[2]]
[[2]]$grades[[2]]$date
[1] "2013-09-11 UTC"
[[2]]$grades[[2]]$grade
[1] "A"
[[2]]$grades[[2]]$score
[1] 6
[[2]]$grades[[3]]
[[2]]$grades[[3]]$date
[1] "2013-01-24 UTC"
[[2]]$grades[[3]]$grade
[1] "A"
[[2]]$grades[[3]]$score
[1] 10
[[2]]$grades[[4]]
[[2]]$grades[[4]]$date
[1] "2011-11-23 UTC"
[[2]]$grades[[4]]$grade
[1] "A"
[[2]]$grades[[4]]$score
[1] 9
[[2]]$grades[[5]]
[[2]]$grades[[5]]$date
[1] "2011-03-10 UTC"
[[2]]$grades[[5]]$grade
[1] "B"
[[2]]$grades[[5]]$score
[1] 14
[[2]]$name
[1] "Morris Park Bake Shop"
[[2]]$restaurant_id
[1] "30075445"
[[3]]
[[3]]$address
[[3]]$address$building
[1] "351"
[[3]]$address$loc
[[3]]$address$loc$type
[1] "Point"
[[3]]$address$loc$coordinates
[[3]]$address$loc$coordinates[[1]]
[1] -73.98514
[[3]]$address$loc$coordinates[[2]]
[1] 40.76769
[[3]]$address$street
[1] "West 57 Street"
[[3]]$address$zipcode
[1] "10019"
[[3]]$borough
[1] "Manhattan"
[[3]]$cuisine
[1] "Irish"
[[3]]$grades
[[3]]$grades[[1]]
[[3]]$grades[[1]]$date
[1] "2014-09-06 UTC"
[[3]]$grades[[1]]$grade
[1] "A"
[[3]]$grades[[1]]$score
[1] 2
[[3]]$grades[[2]]
[[3]]$grades[[2]]$date
[1] "2013-07-22 UTC"
[[3]]$grades[[2]]$grade
[1] "A"
[[3]]$grades[[2]]$score
[1] 11
[[3]]$grades[[3]]
[[3]]$grades[[3]]$date
[1] "2012-07-31 UTC"
[[3]]$grades[[3]]$grade
[1] "A"
[[3]]$grades[[3]]$score
[1] 12
[[3]]$grades[[4]]
[[3]]$grades[[4]]$date
[1] "2011-12-29 UTC"
[[3]]$grades[[4]]$grade
[1] "A"
[[3]]$grades[[4]]$score
[1] 12
[[3]]$name
[1] "Dj Reynolds Pub And Restaurant"
[[3]]$restaurant_id
[1] "30191841"
[[4]]
[[4]]$address
[[4]]$address$building
[1] "2780"
[[4]]$address$loc
[[4]]$address$loc$type
[1] "Point"
[[4]]$address$loc$coordinates
[[4]]$address$loc$coordinates[[1]]
[1] -73.98242
[[4]]$address$loc$coordinates[[2]]
[1] 40.5795
[[4]]$address$street
[1] "Stillwell Avenue"
[[4]]$address$zipcode
[1] "11224"
[[4]]$borough
[1] "Brooklyn"
[[4]]$cuisine
[1] "American "
[[4]]$grades
[[4]]$grades[[1]]
[[4]]$grades[[1]]$date
[1] "2014-06-10 UTC"
[[4]]$grades[[1]]$grade
[1] "A"
[[4]]$grades[[1]]$score
[1] 5
[[4]]$grades[[2]]
[[4]]$grades[[2]]$date
[1] "2013-06-05 UTC"
[[4]]$grades[[2]]$grade
[1] "A"
[[4]]$grades[[2]]$score
[1] 7
[[4]]$grades[[3]]
[[4]]$grades[[3]]$date
[1] "2012-04-13 UTC"
[[4]]$grades[[3]]$grade
[1] "A"
[[4]]$grades[[3]]$score
[1] 12
[[4]]$grades[[4]]
[[4]]$grades[[4]]$date
[1] "2011-10-12 UTC"
[[4]]$grades[[4]]$grade
[1] "A"
[[4]]$grades[[4]]$score
[1] 12
[[4]]$name
[1] "Riviera Caterer"
[[4]]$restaurant_id
[1] "40356018"
[[5]]
[[5]]$address
[[5]]$address$building
[1] "97-22"
[[5]]$address$loc
[[5]]$address$loc$type
[1] "Point"
[[5]]$address$loc$coordinates
[[5]]$address$loc$coordinates[[1]]
[1] -73.86012
[[5]]$address$loc$coordinates[[2]]
[1] 40.73117
[[5]]$address$street
[1] "63 Road"
[[5]]$address$zipcode
[1] "11374"
[[5]]$borough
[1] "Queens"
[[5]]$cuisine
[1] "Jewish/Kosher"
[[5]]$grades
[[5]]$grades[[1]]
[[5]]$grades[[1]]$date
[1] "2014-11-24 UTC"
[[5]]$grades[[1]]$grade
[1] "Z"
[[5]]$grades[[1]]$score
[1] 20
[[5]]$grades[[2]]
[[5]]$grades[[2]]$date
[1] "2013-01-17 UTC"
[[5]]$grades[[2]]$grade
[1] "A"
[[5]]$grades[[2]]$score
[1] 13
[[5]]$grades[[3]]
[[5]]$grades[[3]]$date
[1] "2012-08-02 UTC"
[[5]]$grades[[3]]$grade
[1] "A"
[[5]]$grades[[3]]$score
[1] 13
[[5]]$grades[[4]]
[[5]]$grades[[4]]$date
[1] "2011-12-15 UTC"
[[5]]$grades[[4]]$grade
[1] "B"
[[5]]$grades[[4]]$score
[1] 25
[[5]]$name
[1] "Tov Kosher Kitchen"
[[5]]$restaurant_id
[1] "40356068"
L’affichage avec la méthode batch, qui est essentiellement des listes imbriquées, n’est pas toujours facile à visualiser et nous pouvons décider de stocker le résultat dans un dataframe pour mieux le visualiser ; cela se fait avec la méthode page()
. Par exemple, pour stocker les 5 premières lignes dans un dataframe :
df <- it$page(5)
print(df)
address.building address.loc.type address.loc.coordinates address.street
1 2206 Point -74.13773, 40.61196 Victory Boulevard
2 8825 Point -73.88038, 40.76431 Astoria Boulevard
3 7114 Point -73.90685, 40.61990 Avenue U
4 6409 Point -74.00529, 40.62889 11 Avenue
5 1839 Point -73.94826, 40.64083 Nostrand Avenue
address.zipcode borough cuisine
1 10314 Staten Island Jewish/Kosher
2 11369 Queens American
3 11234 Brooklyn Delicatessen
4 11219 Brooklyn American
5 11226 Brooklyn Ice Cream, Gelato, Yogurt, Ices
grades
1 1412553600, 1400544000, 1365033600, 1327363200, A, A, A, A, 9, 12, 12, 9
2 1416009600, 1398988800, 1362182400, 1328832000, Z, A, A, A, 38, 10, 7, 13
3 1401321600, 1389657600, 1375488000, 1342569600, 1331251200, 1318550400, A, A, A, A, A, A, 10, 10, 8, 10, 13, 9
4 1405641600, 1375142400, 1360713600, 1345075200, 1313539200, A, A, A, A, A, 12, 12, 11, 2, 11
5 1405296000, 1373414400, 1341964800, 1329955200, A, A, A, A, 12, 8, 5, 8
name restaurant_id
1 Kosher Island 40356442
2 Brunos On The Boulevard 40356151
3 Wilken'S Fine Food 40356483
4 Regina Caterers 40356649
5 Taste The Tropics Ice Cream 40356731
7.2.2.4. Méthode distinct¶
Tout comme le .distinct()
en MongoDB (plus de précisions ici), la méthode distinct()
nous renvoie les valeurs distinctes d’un champ.
Exemple :
Affichez la liste des notes existant dans la base :
coll$distinct(key = "grades.grade")
- 'A'
- 'B'
- 'C'
- 'Not Yet Graded'
- 'P'
- 'Z'
Nous pouvons également noter l’équivalent en MongoDB :
coll$distinct(key = "grades.grade")
db.NYfood.distinct("grades.grade")
7.2.2.5. Sélectionner par date¶
Le traitement des dates avec mongolite mérite une attention particulière. En effet, lorsque l’on souhaite intéragir avec une collection sur un champ de type date, la syntaxe sera relativement différente de celle que l’on peut utiliser en MongoDB (plus de précisions ici) ou avec pymongo. En raison de la classe d’une requête en mongolite (chaîne de caractère), on ne pourra pas utiliser d’objet R spécifique aux dates (comme les datetimes en python par exemple). Ainsi, toutes les dates définies dans une requête en mongolite devront être spécifiées dans un format purement JSON intégrant un opérateur $date
et une syntaxe UTC. Concrètement, ce format sera du type :
{ "$date" : "AAAA-MM-JJThh:mm:ssZ" }
Exemple :
Prenons un exemple en affichant la liste des 10 premiers restaurants ayant eu au moins une note postérieure au 20 janvier 2015 :
q <- '{"grades.date": {"$gte": {"$date": "2015-01-20T00:00:00Z"}}}'
data <- coll$find(q, limit=10)
print(data)
Found 10 records...
Imported 10 records. Simplifying into dataframe...
address.building address.loc.type address.loc.coordinates address.street
1 730 Point -73.89955, 40.81644 Kelly Street
2 968 Point -92.72302, 41.74614 6 Avenue
3 1285 Point -73.92275, 40.83945 Shakespeare Ave
4 416 Point -73.95136, 40.77249 East 80 Street
5 725 Point -74.01381, 40.63368 65 Street
6 229-231 Point -74.00155, 40.70780 Front Street
7 263-15 Point -73.70923, 40.74883 Union Turnpike
8 353 Point -73.98533, 40.76794 West 57 Street
9 413 Point -73.86476, 40.90232 East 241 Street
10 590 Point -73.92636, 40.81950 Grand Concourse
address.zipcode borough cuisine
1 10455 Bronx American
2 10018 Manhattan American
3 10452 Bronx Spanish
4 10075 Manhattan American
5 11220 Brooklyn Other
6 10038 Manhattan American
7 11004 Queens American
8 10019 Manhattan American
9 10470 Bronx American
10 10451 Bronx African
grades
1 1421712000, Not Yet Graded, 4
2 1421712000, 1396915200, 1380585600, 1360713600, 1345075200, 1330041600, Not Yet Graded, A, A, B, A, A, 17, 9, 13, 27, 9, 11
3 1421712000, Not Yet Graded, 12
4 1421712000, Not Yet Graded, 9
5 1421712000, Not Yet Graded, NA
6 1421712000, Not Yet Graded, 2
7 1421712000, Not Yet Graded, 7
8 1421712000, Not Yet Graded, 24
9 1421712000, 1383523200, 1367280000, 1318982400, Not Yet Graded, A, A, A, 4, 2, 9, 9
10 1421712000, Not Yet Graded, 21
name restaurant_id
1 Ambassador Diner 40403946
2 Herald Square Market 40984099
3 El Nuevo Encuentro Bar Corp 41185949
4 Caedmon School Winstead Caterers 41243754
5 Swedish Football Club 41278206
6 Onda 41338998
7 Saint Anns 41433190
8 Good Units 41514954
9 Homestyle Food Services (St. Barnabas High School) 41519329
10 Capitol International 41650465
Nous pouvons noter l’équivalent en MongoDB :
q <- '{"grades.date": {"$gte": {"$date": "2015-01-20T00:00:00Z"}}}'
data <- coll$find(q, limit=10)
data
date = new Date("2015-01-20")
db.NYfood.find({"grades.date":{$gte: date}}).limit(10)
7.2.3. Aggrégations¶
Nous nous proposons dans ce paragraphe de traiter de la méthode aggregate()
de mongolite qui permet d’éxécuter, à l’instar de d’un .aggregate
en MongoDB, une pipeline d’aggrégation qui n’est rien d’autre qu’une succession de plusieurs étapes d’aggrégation. La méthode aggregate()
prend comme argument une liste de dictionnaires que l’on met entre simples quotes. La syntaxe a utiliser pour cette liste de dictionnaires est la même que pour le .aggregate
de MongoDB (nous vous renvoyons au chapitre sur les requêtes d’aggrégations pour plus de précisions). Tout comme la méthode find()
, la méthode aggregate renvoie un dataframe.
Exemple :
Prenons un exemple : dans la collection NYfood, le pipeline ci-dessous retourne le nombre de restaurants par arrondissement (borough).
req = '[{"$group":{"_id":"$borough","nb_restos":{"$sum":1}}}]'
df <- coll$aggregate(pipeline=req)
df
Found 6 records...
Imported 6 records. Simplifying into dataframe...
_id | nb_restos | |
---|---|---|
<chr> | <int> | |
1 | Missing | 51 |
2 | Manhattan | 10258 |
3 | Queens | 5656 |
4 | Brooklyn | 6085 |
5 | Bronx | 2338 |
6 | Staten Island | 969 |
Nous pouvons noter l’équivalent en MongoDB :
req = '[{"$group":{"_id":"$borough","nb_restos":{"$sum":1}}}]'
df <- coll$aggregate(pipeline=req)
df
db.NYfood.aggregate([{$group:{"_id":"$borough", "nb_restos":{"$sum":1}}}])
Il se peut que le dataframe à retourner soit de taille importante (ce n’est pas le cas ici). Pour éviter tout problème de saturation de mémoire, il est préférable d’effectuer l’aggrégation avec l’option iterate=TRUE
. Ainsi, la méthode aggregate()
renvoie comme pour la méthode iterate()
un Mongo iterator. Toutes les méthodes applicables à un Mongo iterator présentées précédemment s’appliqueront alors à l’objet renvoyé par la méthode aggregate()
. Pour reprendre l’exemple ci-dessus :
req = '[{"$group":{"_id":"$borough","nb_restos":{"$sum":1}}}]'
it <- coll$aggregate(pipeline=req, iterate=TRUE)
it$page(2)
_id | nb_restos | |
---|---|---|
<chr> | <int> | |
1 | Manhattan | 10258 |
2 | Missing | 51 |
7.2.4. Manipulation de données¶
7.2.4.1. Méthode insert¶
La méthode insert()
permet, à l’instar du .insert
en MongoDB (plus de précisions ici), d’ajouter des données à une collection.
La méthode la plus simple, est d’insérer des données à partir d’un data frame R. Les colonnes du data frame seront automatiquement transformées en clées d’enregistrement JSON.
test <- mongo()
test$drop()
test$insert(iris)
List of 5
$ nInserted : num 150
$ nMatched : num 0
$ nRemoved : num 0
$ nUpserted : num 0
$ writeErrors: list()
Remarque
En pratique, c’est l’inverse de mongo$find()
qui converti la collection en Data Frame.
test$find(limit = 3)
Sepal_Length | Sepal_Width | Petal_Length | Petal_Width | Species | |
---|---|---|---|---|---|
<dbl> | <dbl> | <dbl> | <dbl> | <chr> | |
1 | 5.1 | 3.5 | 1.4 | 0.2 | setosa |
2 | 4.9 | 3.0 | 1.4 | 0.2 | setosa |
3 | 4.7 | 3.2 | 1.3 | 0.2 | setosa |
Il est également possible d’insérer directement des données à partir d’une chaîne de caractère JSON. Cette méthode nécessite un vecteur de caractères où chaque élément est une chaîne JSON valide.
A noter qu’ici la méthode insert()
crée la collection “individus” car cette dernière n’existe pas.
individus <- mongo("individus")
str <- c('{"prenom" : "yolan"}' , '{"prenom": "paul", "age" : 22}', '{"prenom": "faisal"}')
individus$insert(str)
List of 6
$ nInserted : int 3
$ nMatched : int 0
$ nModified : int 0
$ nRemoved : int 0
$ nUpserted : int 0
$ writeErrors: list()
individus$find(query = '{}', fields = '{}')
_id | prenom | age | |
---|---|---|---|
<chr> | <chr> | <int> | |
1 | 62309afeb1ebdb7a4570ec59 | yolan | NA |
2 | 62309afeb1ebdb7a4570ec5a | paul | 22 |
3 | 62309afeb1ebdb7a4570ec5b | faisal | NA |
Nous pouvons également noter l’équivalent en MongoDB :
individus <- mongo("individus")
individus$insert(c('{"prenom" : "yolan"}' , '{"prenom": "paul", "age" : 22}', '{"prenom": "faisal"}'))
db.createCollection("individus")
db.individus.insert([
{"prenom" : "yolan"}',
{"prenom": "paul", "age" : 22},
{"prenom": "faisal"}
])
7.2.4.2. Méthode remove¶
La même syntaxe que nous utilisons dans find()
pour sélectionner les enregistrements à lire, peut également être utilisée pour sélectionner les enregistrements à supprimer :
test$count()
test$remove('{"Species" : "setosa"}')
test$count()
Nous pouvons noter l’équivalent en MongoDB :
test$remove('{"Species" : "setosa"}')
db.test.remove(
{"Species" : "setosa"}
)
Utilisez l’option just_one pour supprimer un seul enregistrement :
test$remove('{"Sepal_Length" : {"$lte" : 5}}', just_one = TRUE)
test$count()
Pour supprimer tous les documents de la collection (mais pas la collection elle-même) :
test$remove('{}')
test$count()
Nous pouvons noter l’équivalent en MongoDB :
test$remove('{}')
db.test.remove({})
La méthode drop()
supprime une collection entière. Cela inclut toutes les documents, ainsi que les métadonnées telles que les index de la collection.
test$drop()
Nous pouvons noter l’équivalent en MongoDB :
test$drop()
db.test.drop()
7.2.4.3. Méthodes update/upsert¶
Pour modifier des enregistrements existants, utilisez l’opérateur update()
:
Modification d’un document :
individus$find()
prenom | age | |
---|---|---|
<chr> | <int> | |
1 | yolan | NA |
2 | paul | 22 |
3 | faisal | NA |
individus$update('{"prenom":"yolan"}', '{"$set":{"age": 22}}')
List of 3
$ modifiedCount: int 1
$ matchedCount : int 1
$ upsertedCount: int 0
individus$find()
prenom | age | |
---|---|---|
<chr> | <int> | |
1 | yolan | 22 |
2 | paul | 22 |
3 | faisal | NA |
Nous pouvons noter l’équivalent en MongoDB :
individus$update('{"prenom":"yolan"}', '{"$set":{"age": 22}}')
db.individus.update(
{"prenom" : "yolan"},
{$set: {"age" : 22}}
)
Mise à jour de plusieurs documents :
Par défaut, la méthode update()
met à jour un seul document. Pour mettre à jour plusieurs documents, utilisez l’option multi
de la méthode update()
.
individus$update('{}', '{"$set":{"booleen_age": false}}', multiple = TRUE)
List of 3
$ modifiedCount: int 3
$ matchedCount : int 3
$ upsertedCount: int 0
individus$update('{"age" : {"$gte" : 0}}', '{"$set":{"booleen_age": true}}', multiple = TRUE)
List of 3
$ modifiedCount: int 2
$ matchedCount : int 2
$ upsertedCount: int 0
individus$find()
prenom | age | booleen_age | |
---|---|---|---|
<chr> | <int> | <lgl> | |
1 | yolan | 22 | TRUE |
2 | paul | 22 | TRUE |
3 | faisal | NA | FALSE |
Nous pouvons également noter l’équivalent en MongoDB :
individus$update('{}', '{"$set":{"booleen_age": false}}', multiple = TRUE)
individus$update('{"age" : {"$gte" : 0}}', '{"$set":{"booleen_age": true}}', multiple = TRUE)
db.individus.update(
{},
{$set: {"booleen_age": false}},
{multi: true}
)
db.individus.update(
{"age" : {"$gte" : 0}},
{$set: {"booleen_age": true}},
{multi: true}
)
Modification avec création d’un document si besoin :
Si aucun document ne correspond à la condition de mise à jour, le comportement par défaut de la méthode de mise à jour est de ne rien faire. En spécifiant l’option upsert
à true
, l’opération de mise à jour met à jour le ou les documents correspondants ou insère un nouveau document si aucun document correspondant n’existe.
individus$update('{"prenom":"malo"}', '{"$set":{"age": 22}}', upsert = TRUE)
List of 4
$ modifiedCount: int 0
$ matchedCount : int 0
$ upsertedCount: int 1
$ upsertedId : chr "62309afee5a11a212c4cf65a"
individus$find()
prenom | age | booleen_age | |
---|---|---|---|
<chr> | <int> | <lgl> | |
1 | yolan | 22 | TRUE |
2 | paul | 22 | TRUE |
3 | faisal | NA | FALSE |
4 | malo | 22 | NA |
Nous pouvons également noter l’équivalent en MongoDB :
individus$update('{"prenom":"malo"}', '{"$set":{"age": 22}}', upsert = TRUE)
db.individus.update(
{"prenom":"malo"},
{$set: {"age": 22}},
{upsert: true}
)
7.2.5. Import et export de données¶
7.2.5.1. La méthode export/import pour JSON¶
Le format par défaut JSON est celui d’une ligne par document.
individus$export(stdout())
{ "_id" : { "$oid" : "62309afeb1ebdb7a4570ec59" }, "prenom" : "yolan", "age" : 22, "booleen_age" : true }
{ "_id" : { "$oid" : "62309afeb1ebdb7a4570ec5a" }, "prenom" : "paul", "age" : 22, "booleen_age" : true }
{ "_id" : { "$oid" : "62309afeb1ebdb7a4570ec5b" }, "prenom" : "faisal", "booleen_age" : false }
{ "_id" : { "$oid" : "62309afee5a11a212c4cf65a" }, "prenom" : "malo", "age" : 22 }
En temps normal on exporte vers un fichier que l’on précise avec file()
:
individus$export(file("individus.json"))
On peut faire l’essai de supprimer totalement la collection et de l’importer ensuite :
individus$drop()
individus$count()
individus$import(file("individus.json"))
individus$count()
7.2.5.2. Autres formats d’export (jsonlite/bjson/…)¶
Pour ces autres formats ou package concernant les méthodes import()
et export()
, nous vous renvoyons à la page de Jeroen Ooms.
7.2.6. Exercices¶
7.2.6.1. Consignes¶
Exercice 1 :
A l’aide d’une requête d’aggrégation, récupérer le nombre de restaurants par quatier dans la collection NYfood.
Réaliser un barplot pour visualiser le résultat.
Exercice 2 :
Cet exercice reprend l’exemple de carthographie avec leaflet de François-Xavier Jollois, disponible en cliquant ici.
A l’aide d’une requête d’aggrégation, récupérer le nom, le quartier, la longitude et la latitude des restaurant new-yorkais de la collection NYfood.
Afficher les différents restaurants sur la carte du monde. Que constatez vous ?
Réaliser une carte des restaurants new-yorkais en ajoutant une couleur en fonction du quartier (coordonnées de New-York : long: -73.9, lat = 40.7).
7.2.6.2. Correction¶
Exercice 1 :
Question 1 :
req = '[{"$group":{"_id":"$borough","nb_restos":{"$sum":1}}}]'
df <- coll$aggregate(pipeline=req)
df
Question 2 :
library(tidyverse)
df %>%
rename(Borough=`_id`,Nombre=nb_restos) %>%
ggplot(aes(x=Borough, y=Nombre)) +
geom_bar(stat="identity",aes(fill=Borough)) +
geom_text(aes(label=Nombre), vjust=1.6, color="black", size=3.5)
Exercice 2 :
Question 1 :
restos.coord = coll$aggregate(
'[
{ "$project": {
"name": 1,
"borough": 1,
"lng": { "$arrayElemAt": ["$address.loc.coordinates", 0]},
"lat": { "$arrayElemAt": ["$address.loc.coordinates", 1]}
}}
]')
head(restos.coord)
Question 2 :
library(leaflet)
leaflet(restos.coord) %>%
addTiles() %>%
addCircles(lng = ~lng, lat = ~lat)
Question 3 :
pal = colorFactor("Accent", restos.coord$borough)
leaflet(restos.coord) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(lng = -73.9,
lat = 40.7,
zoom = 10) %>%
addCircles(lng = ~lng, lat = ~lat, color = ~pal(borough)) %>%
addLegend(pal = pal, values = ~borough, opacity = 1,
title = "Quartier")