cours-2020-2021

Documents de cours 2020-2021 - FX Jollois

View the Project on GitHub fxjollois/cours-2020-2021

R et MongoDB - Utilisation avec 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.

Exemples sur 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"
}

Document dans 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

Dénombrement

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"]}
  }'
)
m$count(
  query = '{ 
    "address.street": "Franklin Street"
  }'
)
m$count(
  query = '{ 
    "grades.score": 0
  }'
)
m$count(
  query = '{ 
    "grades.score": { "$lte": 5 }
  }'
)

Valeurs distinctes

On peut aussi voir la liste des valeurs distinctes d’un attribut, avec la fonction distinct().

m$distinct(key = "borough")
m$distinct(
  key = "cuisine",
  query = '{ "borough": "Brooklyn" }'
)
m$distinct(
  key = "grades.grade",
  query = '{ "borough": "Brooklyn" }'
)

Restriction et Projection

La fonction find() de l’objet m permet donc de réaliser les restrictions et projections.

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 }')
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}')

Agrégat

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 }}
]')
m$aggregate(pipeline = '[
    { "$limit": 10 },
    { "$unwind": "$grades" }
]')
m$aggregate(pipeline = '[
    { "$limit": 10 },
    { "$unwind": "$grades" },
    { "$match": { "grades.grade": "B" }}
]')
m$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"} }
]')
m$aggregate(pipeline = '[
    { "$limit": 10 },
    { "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } }
]')
m$aggregate(pipeline = '[
    { "$project": { "name": 1, "borough": 1, "nb_grades": { "$size": "$grades" } } },
    { "$sort": { "nb_grades": 1 }},
    { "$limit": 10 }
]')
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 }}
]')
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 }
]')
m$aggregate(pipeline = '[
    { "$limit": 10 },
    { "$unwind": "$grades" },
    { "$group": { 
        "_id": "$name", 
        "avec_addToSet": { "$addToSet": "$grades.grade" },
        "avec_push": { "$push": "$grades.grade" }
    }}
]')

Itération

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)
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")
df = m$find()
mean(Reduce(rbind, df$grades)$score, na.rm = T)

A faire

Rendu

Envoyez votre fichier (script R ou markdown Rmd - avec votre nom dans le nom du fichier) par mail à francois-xavier.jollois@u-paris.fr.

Restaurants

  1. Lister tous les restaurants de la chaîne “Bareburger” (rue, quartier)
  2. Lister les trois chaînes de restaurant les plus présentes
  3. Donner les 10 styles de cuisine les plus présents dans la collection
  4. Lister les 10 restaurants les moins bien notés (note moyenne la plus haute)
  5. Lister par quartier le nombre de restaurants, le score moyen et le pourcentage moyen d’évaluation A

Questions complémentaires

Nécessitent une recherche sur la toile pour compléter ce qu’on a déjà vu dans ce TP.

  1. Lister les restaurants (nom et rue uniquement) situés sur une rue ayant le terme “Union” dans le nom
  2. Lister les restaurants ayant eu une visite le 1er février 2014
  3. Lister les restaurants situés entre les longitudes -74.2 et -74.1 et les lattitudes 40.1 et 40.2

AirBnB

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.

Aide sur les données

  1. Créer la connexion à la collection dans R
  2. Donner le nombre de logements
  3. Lister les informations du logement “10545725” (cf _id)
  4. Lister les différentes types de logements possibles cf (room_type)
  5. Donner le nombre de logements par type
  6. Représenter graphiquement la distribution des prix
  7. Croiser numériquement et graphiquement le type et le prix (price)
  8. Représenter la distribution du nombre d’avis (cf reviews - à calculer)
  9. Croiser graphiquement le nombre d’avis et le prix, en ajoutant l’information du type de logement