Documents de cours 2020-2021 - FX Jollois
R
Dans ce document, nous utilisons le package mongolite
, permettant la connection à une base de données MongoDB. Il fait suite à l’introduction à MongoDB
Le but de ce document est donc de montré les exemples d’utilisation des différentes commandes permettant la récupération de données sur le serveur.
restaurants
Ici, nous allons nous connecter sur un serveur distant, et travailler sur une base des restaurants New-Yorkais.
library(mongolite)
USER = "user"
PASS = "user"
HOST = "cluster0.ougec.mongodb.net"
URI = sprintf("mongodb+srv://%s:%s@%s/", USER, PASS, HOST)
m = mongo(
collection = "restaurants",
db = "sample_restaurants",
url = URI)
Le premier document est présenté ci-dessous. La base contient les informations de plus de 25000 restaurants new-yorkais (base de test fournie par Mongo).
{
"_id" : ObjectId("58ac16d1a251358ee4ee87de"),
"address" : {
"building" : "469",
"coord" : [
-73.961704,
40.662942
],
"street" : "Flatbush Avenue",
"zipcode" : "11225"
},
"borough" : "Brooklyn",
"cuisine" : "Hamburgers",
"grades" : [
{
"date" : ISODate("2014-12-30T00:00:00Z"),
"grade" : "A",
"score" : 8
},
{
"date" : ISODate("2014-07-01T00:00:00Z"),
"grade" : "B",
"score" : 23
},
{
"date" : ISODate("2013-04-30T00:00:00Z"),
"grade" : "A",
"score" : 12
},
{
"date" : ISODate("2012-05-08T00:00:00Z"),
"grade" : "A",
"score" : 12
}
],
"name" : "Wendy'S",
"restaurant_id" : "30112340"
}
R
R
ne gérant pas nativement les données JSON
, les documents sont traduits, pour la librairie mongolite
, en data.frame
. Pour récupérer le premier document, nous utilisons la fonction find()
de l’objet créé m
.
d = m$find(limit = 1)
d
class(d)
Les objets address
et grades
sont particuliers, comme on peut le voir dans le JSON
. Le premier est une liste, et le deuxième est un tableau. Voila leur classe en R
.
class(d$address)
d$address
class(d$grades)
d$grades
Comme indiqué, on utilise la fonction count()
pour dénombrer les documents :
m$count()
m$count(query = '{ "borough": "Brooklyn" }')
m$count(query = '{ "borough": "Brooklyn", "cuisine": "French" }')
m$count(query = '{ "borough": "Brooklyn", "cuisine": { "$in": ["French", "Italian"]} }')
m$count(
query = '{
"borough": "Brooklyn",
"cuisine": { "$in": ["French", "Italian"]}
}'
)
street
du champs address
m$count(
query = '{
"address.street": "Franklin Street"
}'
)
m$count(
query = '{
"grades.score": 0
}'
)
m$count(
query = '{
"grades.score": { "$lte": 5 }
}'
)
On peut aussi voir la liste des valeurs distinctes d’un attribut, avec la fonction distinct()
.
borough
), pour tous les restaurantsm$distinct(key = "borough")
m$distinct(
key = "cuisine",
query = '{ "borough": "Brooklyn" }'
)
m$distinct(
key = "grades.grade",
query = '{ "borough": "Brooklyn" }'
)
La fonction find()
de l’objet m
permet donc de réaliser les restrictions et projections.
"street"
et "borough"
)m$find(query = '{ "name": "Shake Shack" }',
fields = '{ "address.street": 1, "borough": 1 }')
m$find(query = '{ "name": "Shake Shack" }',
fields = '{ "_id": 0, "address.street": 1, "borough": 1 }')
R
.m$find(query = '{"borough": "Queens", "grades.score": { "$gte": 50}}',
fields = '{"_id": 0, "name": 1, "grades.score": 1, "address.street": 1}',
limit = 10)
m$find(query = '{"name": "Shake Shack", "borough": {"$in": ["Queens", "Brooklyn"]}}',
fields = '{"_id": 0, "address.street": 1, "borough": 1}')
m$find(query = '{"borough": "Queens", "grades.score": { "$gt": 50}}',
fields = '{"_id": 0, "name": 1, "address.street": 1}',
sort = '{"address.street": -1, "name": 1}')
Ils sont réalisés avec la fonction aggregate()
, qui permet de faire beaucoup plus.
m$aggregate(pipeline = '[
{"$limit": 10 }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$sort": { "name": 1 }}
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$sort": { "name": 1 }},
{ "$match": { "borough": "Brooklyn" }}
]')
m$aggregate(pipeline = '[
{ "$match": { "borough": "Brooklyn" }},
{ "$limit": 10 },
{ "$sort": { "name": 1 }}
]')
grades
)
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$unwind": "$grades" }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$unwind": "$grades" },
{ "$match": { "grades.grade": "B" }}
]')
$unwind
et $match
, le résultat est clairement différentm$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$match": { "grades.grade": "B" }},
{ "$unwind": "$grades" }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1 } }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "address": 0, "grades": 0 } }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1 , "street": "$address.street"} }
]')
grades
)m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } }
]')
grades
est préent mais égal à NULL
)grades
m$aggregate(pipeline = '[
{ "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } },
{ "$sort": { "nb_grades": 1 }},
{ "$limit": 10 }
]')
grades
(indicé 0)m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "name": 1, "borough": 1, "grade": { "$arrayElemAt": [ "$grades", 0 ]} } }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": { "nom": { "$toUpper": "$name" }, "borough": 1 } }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$addFields": { "nb_grades": { "$size": "$grades" } } }
]')
m$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$project": {
"nom": { "$toUpper": "$name" },
"quartier": { "$substr": [ "$borough", 0, 3 ] }
} }
]')
m$aggregate(pipeline = '[
{ "$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
} }
]')
m$aggregate(pipeline = '[
{"$group": {"_id": "Total", "NbRestos": {"$sum": 1}}}
]')
m$aggregate(pipeline = '[
{"$group": {"_id": "$borough", "NbRestos": {"$sum": 1}}}
]')
m$aggregate('[
{ "$match": { "borough": "Queens" }},
{ "$unwind": "$grades" },
{ "$group": { "_id": "null", "score": { "$avg": "$grades.score" }}}
]')
m$aggregate('[
{ "$unwind": "$grades" },
{ "$group": { "_id": "$borough", "score": { "$avg": "$grades.score" }}},
{ "$sort": { "score": -1 }}
]')
$match
permet de supprimer les restaurants sans évaluations (ce qui engendrerait des moyennes = NA
)m$aggregate(pipeline = '[
{ "$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 }
]')
$addToSet
et $push
, on les applique sur les grades obtenus pour les 10 premiers restaurants
$addToSet
: valeurs distinctes$push
: toutes les valeurs présentesm$aggregate(pipeline = '[
{ "$limit": 10 },
{ "$unwind": "$grades" },
{ "$group": {
"_id": "$name",
"avec_addToSet": { "$addToSet": "$grades.grade" },
"avec_push": { "$push": "$grades.grade" }
}}
]')
Il est possible de définir un curseur (de même type que PL/SQL par exemple), qui va itérer sur la liste de résultats (celle-ci sera stocké sur le serveur). Cela permet de récupérer les documents un par un, ce qui est judicieux en cas de gros volume. De plus, ceux-ci sont récupérés au format list
pure, ce qui peut simplifier la manipulation en cas de données fortement imbriquées.
cursor = m$iterate(
query = '{"borough": "Queens", "grades.score": { "$gte": 50}}',
fields = '{"_id": 0, "name": 1, "address.street": 1}',
sort = '{"address.street": -1, "name": 1}',
limit = 10)
while(!is.null(doc <- cursor$one())){
cat(sprintf("%s (%s)\n", doc$name, doc$`address`$`street`))
}
Plutôt que d’avoir les documents un par un, il est ausi possible de les avoir par paquets avec la fonction batch(n)
(ainsi que page(n)
et json(n)
qui diffèrent sur le type de ce qui est retourné) sur le curseur (n
étant donc le nombre de documents renvoyés)
cursor = m$iterate(limit = 15)
cursor$batch(5)
cursor$page(5)
cursor$json(5)
batch
cursor = m$iterate()
fin = FALSE
n_batch = 5
somme = 0
nombre = 0
liste = NULL
while (!fin) {
#cat("\n\n--------------\n\n")
doc = cursor$batch(n_batch)
if (length(doc) > 0 ) {
scores = unlist(lapply(doc, function (d) {
res = lapply(d$grades, function (g) {
return (g$score)
})
return (res)
}))
#print(scores)
nombre = nombre + length(scores)
somme = somme + sum(scores)
#liste = c(liste, scores)
}
if (length(doc) < n_batch) {
fin = TRUE
}
}
cat("Résultat du calcul :", round(somme/nombre, 5), "\n")
R
en important les données
df = m$find()
mean(Reduce(rbind, df$grades)$score, na.rm = T)
Envoyez votre fichier (script R
ou markdown Rmd
- avec votre nom dans le nom du fichier) par mail à francois-xavier.jollois@u-paris.fr.
Nécessitent une recherche sur la toile pour compléter ce qu’on a déjà vu dans ce TP.
Nous 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
.