1
Fork 0
mirror of https://github.com/Steffo99/unimore-bda-1.git synced 2024-11-21 14:04:18 +00:00

Interrogazioni di esempio sul dataset Amazon

This commit is contained in:
Steffo 2022-11-08 21:55:52 +01:00
parent d4bbe97a07
commit 7bac1efc37
Signed by: steffo
GPG key ID: 6965406171929D01
5 changed files with 202 additions and 70 deletions

0
.gitmodules vendored
View file

233
README.md
View file

@ -13,21 +13,7 @@
## Premessa
L'attività è stata svolta su MongoDB 6.0.2, attraverso il progetto [Docker Compose](https://docs.docker.com/compose/) allegato.
Per ricreare lo stesso ambiente di lavoro utilizzato, sarà necessario:
1. Inserire il file `metaexport.json` all'interno della cartella `seed`, non allegato per motivi di dimensioni.
2. Con un daemon Docker in esecuzione, e Docker Compose installato sulla macchina locale, "accendere" il progetto:
```console
# docker compose up -d
```
3. Con la [MongoDB Shell](https://www.mongodb.com/try/download/shell) installata sulla macchina locale, è possibile interfacciarsi al database con:
```console
$ mongosh --username=unimore --password=unimore --authenticationDatabase=admin mongodb://127.0.0.1:27017/amazon
```
L'attività è stata svolta su MongoDB 6.0.2, attraverso la macchina virtuale predisposta su [Azure Labs](https://labs.azure.com/virtualmachines), `LabBDA2`.
## Introduzione
@ -36,10 +22,14 @@ Sviluppando una applicazione o effettuando analisi su dati, è possibile aver bi
Per comodità, MongoDB include funzionalità basilari per la ricerca in linguaggio naturale, in modo che i suoi utilizzatori possano usufruirne senza dover configurare un motore di ricerca esterno come [ElasticSearch](https://www.elastic.co/).
Sono disponibili funzionalità di ricerca più avanzate, ma sono limitate al piano managed del database, [MongoDB Atlas](https://www.mongodb.com/pricing): esse non vengono prese in considerazione da questa relazione.
### Il problema degli indici
Una richiesta molto comune che riguarda il testo naturale è trovare i documenti contenenti parole specifiche in uno o più campi: ad esempio, in una collezione di film, si potrebbe voler trovare quello con un determinato titolo.
Quando si effettua una ricerca in linguaggio naturale su una collezione di documenti, solitamente si desidera trovare uno o più documenti dal contenuto più simile possibile al contenuto della richiesta effettuata.
Ad esempio, in un database di film, si potrebbe voler trovare il film con uno specifico titolo, o con un termine specifico nella descrizione.
Non effettuando confronti diretti (`===`) tra i contenuti dei campi interrogati e i termini della query, non è possibile fare uso dei comuni indici usati per la ricerca binaria; è invece necessario un indice apposito, detto *[Text Index](https://www.mongodb.com/docs/manual/core/index-text/)*, in grado di processare correttamente le stringhe dei documenti nella collezione.
@ -131,12 +121,16 @@ db.EXAMPLE.createIndex(
Per operare efficacemente con il linguaggio naturale, è necessario effettuare alcune operazioni di preprocessing sulle stringhe in questione, trasformandole in insiemi di token.
> Ho scelto di considerare il contenuto e l'effetto di queste operazioni oltre lo scopo di questo testo, in quanto è stato ampiamente trattato in Gestione dell'Informazione alla triennale.
Queste operazioni, su MongoDB, sono:
1. tokenizzazione: le parole vengono separate, ripulite da punteggiatura e diacritici, e trasformate in _token_
2. eliminazione delle stopwords: i token più comuni e insignificanti ai fini della ricerca, come le congiunzioni, vengono eliminati
3. _opzionalmente_ stemming: i token vengono ridotti al loro prefisso, in modo che non ci sia disambiguazione tra singolari, plurali, maschili o femminili
Esse sono effettuate a runtime, nello specifico:
- quando si crea un nuovo Text Index, sono preprocessate le stringhe di tutti i documenti già esistenti;
- quando si inserisce un nuovo documento o se ne modifica uno già esistente, sono preprocessate le sue stringhe;
- quando si effettua un'interrogazione basata sul Text Index, sono preprocessati i termini di essa.
MongoDB effettua queste operazioni a runtime, nello specifico:
- quando si crea un nuovo Text Index, sono processate le stringhe di tutti i documenti già esistenti;
- quando si inserisce un nuovo documento o se ne modifica uno già esistente, sono processate le sue stringhe;
- quando si effettua un'interrogazione basata sul Text Index, sono processati i termini di essa.
##### Lingua
@ -180,6 +174,7 @@ db.EXAMPLE.createIndex(
})
```
### Interrogazione sull'indice
Una volta creato l'indice, è possibile usarlo per effettuare interrogazioni di linguaggio naturale sulla collezione utilizzando i metodi della collezione `.find()` e le chiavi speciali `$text` e `$search`:
@ -193,6 +188,7 @@ db.EXAMPLE.find({
})
```
#### Query speciali
Circondando di virgolette doppie `"` un termine della ricerca, è possibile richiedere la presenza esatta di uno dato termine o frase all'interno del documento:
@ -215,7 +211,7 @@ db.EXAMPLE.find({
})
```
Prefissando un trattino `-` ad un termine è possibile filtrare i documenti che lo contengono:
Prefissando un trattino `-` ad un termine è possibile nascondere i documenti che lo contengono:
```javascript
// Cerca documenti che contengono le parole della stringa "La Mia Query" nei campi indicizzati, e che hanno SICURAMENTE la parola "Mia" da qualche parte
@ -226,6 +222,7 @@ db.EXAMPLE.find({
})
```
#### Query miste
È possibile effettuare query `$text` assieme a query "normali", effettuando l'intersezione dei risultati:
@ -260,13 +257,14 @@ db.EXAMPLE.find(
)
```
## Interrogazioni di esempio sul dataset Amazon
### 0 - Creazione dell'indice sulla collezione Meta
### 0 - Creazione del Text Index sulla collezione `meta`
La collezione `meta` ha un solo campo in cui sono utilizzate stringhe con testo in linguaggio naturale: `description`.
Pertanto, si crea un indice contenente solo quel campo specifico.
Pertanto, si è creato un indice contenente solo quel campo specifico.
```javascript
db.meta.createIndex(
@ -280,10 +278,35 @@ db.meta.createIndex(
)
```
### 0 - Creazione del Text Index sulla collezione `reviews`
La collezione `reviews` ha due campi di testo in linguaggio naturale: `summary`, il titolo della recensione, e `reviewText`, il contenuto completo della recensione.
Quindi, si è eliminato l'indice pre-esistente `summary_text`, e si è creato un indice contenente quei due campi, dando peso maggiore al campo `summary`, più rilevante.
```javascript
db.reviews.dropIndex("summary_text")
db.reviews.createIndex(
{
summary: "text",
reviewText: "text",
},
{
name: "reviews_text",
default_language: "english",
weights: {
summary: 2,
reviewText: 1,
}
}
)
```
### 1 - Ricerca semplice
Si desidera cercare all'interno della collezione il videogioco _[Terraria](https://store.steampowered.com/app/105600/Terraria/)_.
Si desidera cercare all'interno della collezione tutti i dati relativi al videogioco _[Terraria](https://store.steampowered.com/app/105600/Terraria/)_.
Si effettua una query dove il termine "Terraria" viene cercato all'interno del Text Index.
@ -295,8 +318,7 @@ db.meta.findOne(
)
```
La query ha avuto successo ed ha restituito il prodotto che aspettato:
La query ha successo e restituisce un documento con le caratteristiche richieste, ma il dataset pare essere sporco: immagine e prezzo del prodotto si riferiscono a una copia del videogioco _[Minecraft](https://www.minecraft.net/en-us)_.
<details>
<summary>Risultato ottenuto</summary>
@ -338,14 +360,163 @@ La query ha avuto successo ed ha restituito il prodotto che aspettato:
</details>
### 2 -
### 2 - Ricerca mista
Si desidera trovare tutte le recensioni negative (2 stelle o inferiore) relative a qualche tipo di gioco (contenenti la parola "game" in qualche campo).
Si effettua la seguente query:
```javascript
db.reviews.find(
{
$text: {$search: "game"},
overall: {$lte: 2},
}
)
```
La query ha successo, e restituisce 192139 documenti.
### 3 -
### 3 - Aggregazione con ricerca speciale
Si desidera trovare la posizione di ricerca media di prodotti riguardanti "Dungeons & Dragons" (e non con le parole Dungeons e Dragons nella descrizione).
Si effettua la seguente aggregazione:
```javascript
db.meta.aggregate([
// Trova i documenti che contengono "Dungeons & Dragons"
{$match: {
$text: {$search: `"Dungeons & Dragons"`}
}},
// Trasforma il sottodocumento salesRank in un array
{$project: {
salesRank: {$objectToArray: "$salesRank"},
}},
// Effettua l'unwind dell'array
{$unwind: {
path: "$salesRank",
}},
// Raggruppa per categoria calcolando la media
{$group: {
_id: "$salesRank.k",
avg: {$avg: "$salesRank.v"},
}}
])
```
L'aggregazione ha successo, e restituisce il seguente risultato:
```javascript
[
{ _id: 'Video Games', avg: 30079.060606060608 },
{ _id: 'Books', avg: 257210 },
{ _id: 'Software', avg: 26086 }
]
```
### 4 - Aggregazione con ricerca speciale e mista
Si desidera trovare la posizione di ricerca media, minima e massima dei videogiochi contenenti il termine "Dante".
Si effettua la seguente aggregazione:
```javascript
db.meta.aggregate([
// Trova i videogiochi che contengono il termine "Dante"
{$match: {
$text: {$search: "Dante"},
// Le categorie sono tuple di termini dal più generale al più specifico; per risolvere questa query è sufficiente che un termine qualsiasi sia "Video Games"
categories: {$elemMatch: {$elemMatch: {$eq: "Video Games"}}},
}},
// Trasforma il sottodocumento salesRank in un array
{$project: {
salesRank: {$objectToArray: "$salesRank"},
}},
// Effettua l'unwind dell'array salesRank
{$unwind: {
path: "$salesRank",
}},
// Raggruppa per categoria calcolando posizione minima, media e massima
{$group: {
_id: "$salesRank.k",
best: {$min: "$salesRank.v"},
avg: {$avg: "$salesRank.v"},
worst: {$max: "$salesRank.v"}
}}
])
```
L'aggregazione ha successo, e restituisce il seguente risultato:
```javascript
[
{
_id: 'Video Games',
best: 2112,
avg: 17816.904761904763,
worst: 74608
}
]
```
### 4 -
### 5 - Aggregazione con ricerca e lookup
Si desidera trovare una bizzarra irregolarità statistica: trovare i dati dei 10 prodotti con le recensioni più alte, contando solo le recensioni in cui compare la parola "cringe".
### 5 -
Si effettua la seguente query:
```javascript
db.reviews.aggregate([
// Trova le recensioni che contengono il termine "cringe"
{$match:
{$text: {$search: "cringe"}},
},
// Raggruppa le recensioni per codice prodotto, conteggiandole e calcolandone la valutazione media
{$group: {
_id: "$asin",
avgRating: {$avg: "$overall"},
reviewCount: {$sum: 1},
}},
// Scarta i prodotti che non hanno almeno 3 recensioni "cringe": avrebbero facilmente degli outliers
{$match: {
reviewCount: {$gte: 3},
}},
// Effettua il lookup dei dati completi dei prodotti attraverso l'asin
{$lookup: {
from: "meta",
localField: "_id",
foreignField: "asin",
as: "product",
}},
// Dato che nella collezione "meta" ci potrebbero essere più prodotti con lo stesso asin, effettua l'unwind dell'array dei prodotti trovati
{$unwind: {
path: "$product",
}},
// Mantieni solo i prodotti per cui è stato trovato un corrispondente nella tabella meta; ci sono tantissime non-corrispondenze
{$match: {
product: {$exists: true},
}},
// Ordina i prodotti per valutazione media decrescente
{$sort: {
avgRating: -1,
}},
// Mostra solo i 10 prodotti con valutazioni migliori
{$limit: 10},
])
```
L'aggregazione ha successo, restituendo i seguenti prodotti come risultato:
1. `0061461369` (5.0* da 4 recensioni)
2. `0099496941` (5.0* da 4 recensioni)
3. `0062108026` (5.0* da 4 recensioni)
4. `0007423632` (5.0* da 3 recensioni)
5. `0060392991` (5.0* da 8 recensioni)
6. `0062115359` (5.0* da 3 recensioni)
7. `0060890096` (5.0* da 4 recensioni)
8. `0061713244` (5.0* da 3 recensioni)
9. `0007386648` (4.9* da 11 recensioni)
10. `0061448761` (4.8* da 4 recensioni)

View file

@ -1,30 +0,0 @@
version: "3.9"
services:
mongo:
image: "mongo:6.0.2-focal"
ports:
- "127.0.0.1:27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: "unimore"
MONGO_INITDB_ROOT_PASSWORD: "unimore"
MONGO_INITDB_DATABASE: "amazon"
mongoimport:
image: "mongo:6.0.2-focal"
volumes:
- "./seed:/seed"
entrypoint: >-
mongoimport
--host mongo
--db amazon
--authenticationDatabase admin
--username unimore
--password unimore
--collection meta
--type json
--file /seed/metaexport.json
depends_on:
mongo:
condition: service_started

4
seed/.gitignore vendored
View file

@ -1,4 +0,0 @@
# Data files should be provided externally
# I don't think I'm allowed to redistribute them
*.json

View file

@ -1,5 +0,0 @@
# Data files
This directory should contain an externally provided copy of:
- `metaexport.json` (SHA256: `917fc2a44d03a68747fbe23389db9b178401d6cd1a869b63436bfa0eee526139`)