Data Mining - Parte I: Analisi Materiale Originale A/A 2014/2015 Davide Mottin Aggiornato per A/A 2016/2017 Matteo Lissandrini Ultimo Aggiornamento: 30.05.2017 Questa guida non sostituisce il materiale del corso, ma ne integra il contenuto cercando di aggiungere dettagli e chiarimenti laddove più necessario Data Mining - Parte I: Analisi Frequent itemset mining Soluzione näive Algoritmo A-priori Trovare oggetti simili Min-hashing Algoritmo min-hash Locality Sensitive Hashing Clustering Clustering agglomerativo Distanza/unione di cluster Condizioni di stop Point assignment clustering k-means Algoritmo BFR OLAP Data cube Schema a stella Slicing e dicing: affetta e scubotta Data cube formale Molte volte si hanno a disposizione dati non strutturati o per i quali si hanno poche informazioni preliminari (prior), ma si vogliono fare delle analisi per scoprire importanti relazioni o inferire nuove informazioni. Il data-mining si occupa di questo. In questa sezione approfondiremo Come individuare patterns tra gli acquisti (o le interazioni) tra utenti e il sistem Le misure la similartà tra oggetti (prodotti, utenti, acquisit) Come individuare gruppi di oggetti simili tra loro L'esecuzioni di complesse analisi di grandi basi di dati Frequent itemset mining Problema: Abbiamo un insieme di oggetti venduti da un negozio e una serie di transazioni, ossia oggetti comperati da utenti in un singolo acquisto (anhe chiamato carrello). È possibile trovare quali oggetti vengono comperati insieme più frequentemente? Questo problema è detto fequent itemsets, e il problema di predire cosa verrà comprato sapendo cosa è già nel carrello è detto association rule mining. Assumiamo di avere un insieme di oggetti e uno B di transazioni. Vogliamo trovare tutte le regole del tipo I ⇒ i , dove i ∈ e I ⊆ . Definiamo una nozione di "frequenza" basata sul supporto, che misura quante volte un sottoinsieme di oggetti compare nelle transazioni degli utenti. Diciamo che un insieme di oggetti è frequente se ha un supporto maggiore di un s fissato. Definiamo inoltre la confidenza di una regola I contengono I . ⇒ i come la probabilità di trovare i negli insiemi che Problema: come trovare efficientemente tutte le regole che abbiano un minimo supporto s e una minima confidenza c ? Basket ID milk bread butter beer diapers B1 1 1 0 0 0 B2 0 0 1 0 0 B3 0 0 0 1 1 B4 1 1 1 0 0 B5 0 1 0 0 0 B6 0 1 0 0 0 B7 0 1 1 1 1 Es. milk ha supporto 2, bread ha supporto 5, milk, bread ha supporto 2, la confidenza di milk ⇒ bread è 2/2 , se fosse bread ⇒ milk avremo confidenza solo 2/5 Esercizio: calcola confidenza e supporto per : ⇒ butter bread, butter ⇒ beer milk, bread SOLUZIONE NÄIVE Soluzione: La parte difficile è trovare quali insiemi di itemset sono effettivamente frequenti. Semplicemente conta tutte le coppie di elementi e quante volte appaiono, poi tieni solo le coppie frequenti. -- Immagina una relazione Baskets(basketID, itemID) SELECT I.itemID, J.itemID, COUNT(I.basketID) FROM Baskets I, Baskets J WHERE I.basketID = J.basketID AND I.itemID < J.itemID GROUP BY I.itemID, J.itemID HAVING COUNT(I.basketID) >= s; Chiaramente richiede troppe comparazioni e potrebbe generare regole poco interessanti. Nota: Quando possiamo stabilire che un insieme è frequente? Con una soglia troppo bassa, qualsiasi insieme potrebbe essere frequente. Solitamente si stabilisce una soglia di frequenza intorno al 1% dei basket. Inoltre Consideriamo solo itemset di almeno 2 elementi, non ci interessano insiemi di un solo elemento. ALGORITMO A-PRIORI Sfrutta il risultato teorico che la frequenza è antimonotona, ovvero: i sottoinsiemi di un insieme frequente sono insiemi frequenti. Ecco lo pseudo-pseudo codice (nelle slide ne è presente uno più formale): 1. Sia C1 l'insieme degli itemset frequenti contenenti un solo item. 2. i=1 3. Per ogni (a, b) 1. 4. ∈ Ci Ci+1 contiene tutti gli a ∪ b frequenti, i = i + 1 e riparti da 3. finché Ci+1 ≠ ∅ Nota: A livello di implementazione l'algoritmo può essere ottimizzato con strutture dati intelligenti. Attenzione: L'algoritmo genera itemset frequenti, non regole frequenti. Per generare regole frequenti bisogna aggiungere anche una confidenza minima e considerare le sole regole che hanno una confidenza minima. L'algoritmo si può ottimizzare utilizzando hashing: una funzione di hash viene sfruttata per produrre dei bucket tra coppie di oggetti negli item frequenti. Si mantiene un unico contatore di frequenza nel bucket. Se il contatore è sotto una soglia allora gli item non sono frequenti e possiamo eliminarli, altrimenti non possiamo concludere nulla. Per trovare gli itemset frequenti possiamo eseguire A-priori sugli insiemi di item già ridotti dopo hashing. Trovare oggetti simili Una semplice metrica per definire la similarità tra insiemi di oggetti è Jaccard, che rappresenta la percentuale di oggetti condivisi. Siano dati due insiemi S, T : Jaccard(S, T) = |S ∩ T| |S ∪ T| La misura Jaccard per quanto semplice è utile per: 1. Suggerire oggetti da acquistare ad utenti trovando oggetti, o utenti simili (recommending systems). 2. Se rappresentati come insiemi di parole o insiemi di n-grammi, trovare documenti simili. 3. Trovare la distanza semantica tra due ricerche su un motore di ricerca confrontando i risultati. Problema: computare Jaccard per insiemi grandi è computazionalmente costoso. Soluzione: approssimare Jaccard con un hash "furbo": min-hashing o locality sensitive hashing. MIN-HASHING Sia dato un insieme di n elementi e si prenda una permutazione casuale ma fissa dei suoi elementi. Il minhash di un insieme S è definito come il primo elemento degli elementi permutati che è membro di S . Per esempio, sia {a, b, c, d, e} l'insieme di elementi e (b, e, a, d, c) una sua permutazione fissa. Il min-hash di {b, d, e} è b (primo elemento nella permutazione presente nell'insieme) Il min-hash di {a, c, d} è a (primo elemento nella permutazione presente nell'insieme) Il min-hash di {e, b, c, a, d} è b (primo elemento nella permutazione presente nell'insieme) Nota: di per sé è una soluzione debole, ma se si effettuano m permutazioni si possono prendere i primi m numeri in ogni permutazione come firma dell'insieme, aumentandone quindi il potere espressivo. Perché funziona? Se la permutazione è casuale calcolare il min-hash è come selezionare un sottoinsieme a caso di due insiemi e confrontarlo. Il sotto-insieme nel min-hash è generato però in entrambi gli insiemi dallo stesso insieme di permutazione. Queste tecniche randomizzate infatti funzionano perché il valore atteso si avvicina al valore statistico da misurare (ossia Jaccard in questo caso). Se prendiamo una permutazione casuale, la probabilità che produca lo stesso minhash per due insiemi diversi è uguale a Jaccard. Quindi possiamo approssimare Jaccard con min-hash! Problema: anche calcolare una permutazione è dispendioso. Soluzione: utilizzare un insieme di funzioni di hash h con B bucket per calcolare un'approssimazione delle permutazioni e risolvere le collisioni (una collisione è una coppia di elementi x ≠ x′ tali che h(x) = h(x′ ) in qualche modo). ALGORITMO MIN-HASH Sia dato un insieme S di n elementi, una funzione di hash h e un numero MAX_VALUE > n a cui inizializzare la varabile. L'idea è di iterare per tutti gli elementi e trovare l'elemento S con hash minimo V . V := MAX_VALUE; for i := 1 to n do if h(ai) < V then V := h(ai); Nota: si può ripetere la stessa idea per m diverse funzioni di hash. Nota: L'hash approssima una permutazione. LOCALITY SENSITIVE HASHING Si sfrutta un'hashing function tale per cui coppie di documenti simili finiscano nello stesso bucket con alta probabilità. Una volta computati i min-hash di ogni insieme (tupla) si possono dividere in b bande di uguale dimensione, ognuna contenente r valori. Sia s la distanza di Jaccard tra due insiemi, con un'analisi teorica si può calcolare che la probabilità che i documenti concordino su tutte le righe di almeno una banda è 1 − (1 − s r )b . Clustering Metodi per trovare e raggruppare oggetti simili. Sono basati su nozioni di distanza in spazi n dimensionali, ogni punto dello spazio rappresenta un oggetto[cluster]. Due approcci principali al clustering: Agglomerativo o gerarchico: dato un insieme di cluster si tenta di unire (merge) cluster di oggetti simili Point assignemnt: dopo aver assegnato i punti ad un insieme di cluster si tenta di assegnarli al cluster più vicino in modo tale da aumentare la qualità dei raggruppamenti. Gli algoritmi di clustering dipendono dalla misura di distanza. Dati due oggetti qualsiasi x e y una funzione di distanza d(x, y) deve soddisfare: d(x, y) ≥ 0 : non negatività d(x, y) = 0 se e soltanto se x = y: assioma di coincidenza d(x, y) = d(y, x) : simmetria d(x, y) ≤ d(x, z) + d(z, y) per ogni x, y, z : disuguaglianza triangolare Un'importante misura di distanza è la Lr -norm, definita come d(x, y) = ∣ xi − yi∣ (∑ ) i=1 n 1/r r da cui derivano la Manhattan distance (L1-norm) e la euclidian distance (L2-norm). Un'altra importante misura è la distanza di Jaccard (è facile provare che è una metrica): d(x, y) = 1 − J(x, y) Nell'ambito dell'information retrieval una misura particolarmente interessante è la cosine distance che misura l'angolo tra due vettori (è massima quando l'angolo tra di loro è di 90º). La distanza coseno è definita come: d(x, y) = 1 − x⋅y ‖x‖‖y‖ dove x ⋅ y è il prodotto scalare tra x e y e ‖x‖ indica la norma del vettore x . Altre misure: Edit distance (stringhe), Hamming distance (booleani). Attenzione: gli oggetti sono tipicamente rappresentati da vettori in uno spazio multidimensionale. Se il numero di dimensioni è elevato (e tende ad infinito) allora la media delle distanze tra due oggetti a caso tende a 1. Questo fenomeno è conosciuto col nome di "curse of dimensionality". CLUSTERING AGGLOMERATIVO Famiglia di algoritmi molto semplici: 1. Si parte da un iniziale assegnamento dei punti a cluster 2. Finché non si raggiunge la condizione di stop 1. Unire i due cluster più vicini Nota: il risultato del clustering agglomerativo, molte volte è un albero ottenuto dall'unione di cluster. DISTANZA/UNIONE DI CLUSTER Si può definire in molti modi minimo tra le distanze dei punti tra due cluster distanza media tra i punti dei cluster distanza tra i centroidi, dove il centroide1 di n punti è il punto ottenuto dalla media delle coordinate di ogni punto. Sia per esempio A = (x1 , y1 ) e B = (x2 , y2 ), allora il centroide sarà C = ((x1 + x2 )/2, (y1 + y2 )/2) definito il raggio di un Cluster come la distanza del punto più distante dal centroide, si uniscono i due cluster che risultano nel raggio minimo definito diametro come la distanza massima tra due punti di un cluster, si uniscono i due cluster che risultano nel diametro minimo CONDIZIONI DI STOP In base alla distanza si possono definire molte condizioni di stop. Dato in input k , ci si ferma quando si ottengono k cluster. Si continua finché non si ottiene un singolo cluster La coesione (distanza media tra i punti) del cluster non è maggiore di una certa soglia. Il diametro del cluster ottenuto dall'unione supera una determinata soglia. Fermarsi quando la densità del cluster unione (il migliore) è sotto una certa soglia. La densità può essere definita come il numero di punti diviso una certa potenza del raggio. Problema: Cosa succede se lo spazio non è euclideo e quindi non possiamo trovare un centroide per il cluster? Soluzione: Possiamo prendere il punto del cluster che minimizzi la somma/massimo/somma quadratica delle distanze da sé stesso. Questo punto si chiama clustroide. POINT ASSIGNMENT CLUSTERING K-MEANS k -means è il più famoso algoritmo per clustering a punti, dove k è il numero di clustering che deve essere prodotto (è un parametro in input). 1. Scegli k punti iniziali come cluster 2. Assegna i centroidi ai punti scelti in 1. 3. Finche i centroidi cambiano * per ogni altro punto p * trova il centroide c del cluster più vicino * aggiungi p a c * ricalcola il centroide del cluster Attenzione: la qualità dei cluster in alcuni casi dipende dalla scelta dei punti d'inizio. Inizializzare i cluster Un modo per inizializzare i cluster è di scegliere punti che sono il più distante possibile tra loro. Ecco un possibile algoritmo per la scelta dei punti 1. Scegli il primo punto a random 2. Finché non sono stati scelti k punti * Scegli il punto il quale ha distanza minima con i punti già scelti massima. Alternativamente si può fare clustering di un campione di punti tale per cui ci siano k cluster e prendere i punti tra di loro più vicini al centroide del cluster. Problema: k deve essere conosciuto in anticipo. Soluzione: Adottare una misura per la qualità dei cluster (per esempio il diametro medio) e fare ripetute prove con k = 1, 2, 4, … finché non si trova il miglior k ALGORITMO BFR Problema: I dati non riescono ad essere caricati tutti in memoria Soluzione: partizionare e caricare i dati in memoria e mantenere statistiche dei cluster e degli altri dati. Nota: si assumono dati distribuiti normalmente (e quindi si deve conoscere media e varianza) 1. Seleziona k punti iniziali 2. Leggi i dati in memoria in chunk (partizioni) 3. Mantieni statistiche dei dati e dei cluster 4. I punti sono divisi in: Discard set: punti già assegnati ad un cluster che non richiedono di essere in memoria Compressed set: punti vicini ad altri che si credono appartenere allo stesso cluster, ma non vicini abbastanza al centroide (rappresentati come statistiche) Retained set: non sono vicini a nessun altro punto (stanno in memoria) Dato che bisogna calcolare media e varianza, per ogni set bisogna mantenere: 1. Il numero di punti 2. La somma per componente (ricordati che siamo in uno spazio multidimensionale) di ogni punto. Per esempio se si hanno due punti (x1, y1) e (x2, y2) si memorizza (x1 + x2, y1 + y2) 3. La somma per componente dei quadrati (per il calcolo della varianza). Nota: per calcolare i centroidi basta usare la statistica al punto 2. e dividere per il numero di punti! OLAP OLAP significa (On-Line Analytic Processing) e si intende un insieme di tecniche per esaminare dati alla ricerca di pattern o trend. In contrasto OLTP (On-Line Transaction Processing) riguarda le operazioni fondamentali (transazioni), ovvero registrare una vendita avvenuta, verificare che un cliente possa prelevare la cifra richiesta, etc. I processi OLTP sono solitamente operazioni semplici che arrivano in enorme quantità durante il funzionamento normale di una azienda. OLAP richiede query molto complesse (esempio, join multipli) e aggregati (avg, max, min) per poter calcolare statistiche. Ecco perché si mantengono copie dei dati nelle data warehouse che vengono aggiornate tipicamente una volta al giorno (di notte). Considera la seguente query su tre tabelle diverse: se i dati sono tanti potrebbe impiegare molto tempo in quanto analizza la gran parte del database e tutto per ritornare un risultato molto piccolo. SELECT state, AVG(price) FROM Sales,Dealers WHERE Sales.dealer = Dealers.name AND date >= "2006-01-04" GROUP BY state; DATA CUBE La soluzione, per ottimizzare l'analisi dei dati è di copiare i dati in una data warehouse e di precomputare alcuni risultati aggregati. Immaginiamo di avere i dati organizzati in uno spazio multi-dimensionale (si immagina un cubo per semplicità quando abbiamo 3 dimensioni). Ogni punto rappresenta una tupla ottenuta attraverso tutte le join delle tabelle di un database. La tabella che contiene i punti si chiama fact table. Esempio: Immagina una vendita nell'esempio precedente rappresentata dalla tripla (macchina, dealer, data). I punti di questo cubo sono tutte le possisbili combinazioni di codice macchina , identificativo dealer , data di vendita In questo caso sono esattamente 3 dimensioni, poichè la stessa macchina può essere venduta da dealer diversi in date diverse, o lo stesso dealer può vendere macchine diverse in date diverse. Se numeriamo ogni dimensione la tupla (1,2,3) rapprenseta la macchina 1 venduta dal dealer 2 nella data 3 , così come coordinate nello spazio (x,y,z) : ecco la metafora del cubo Ovviamente, con maggiori attributi, es., il codice cliente di vendita, la tupla avrà 4 dimensioni codice macchina , identificativo dealer , data di vendita , codice cliente , e quindi dovremo immaginare un cubo in 4 dimensioni (x,y,z,u) . Due tipi di data cube: raw data cube: un modo per visualizzare i dati originali in modo diverso, nessun dato o ottimizzazione viene memorizzata formal data cube: i punti potrebbero già rappresentare un'aggregazione di dati (vendite totali o modello, oppure il mese di vendita se le singole date non sono interessanti) e vengono materializzati in un cubo vero e proprio. SCHEMA A STELLA Al centro della stella c'è la fact table collegata a delle dimension tables che rappresentano una un attributo o un aggregato. Le dimensioni possono essere il numero di macchine vendute, il metodo di pagamento e così via. Ci possono essere alcuni attributi dipendenti che rappresentano dei valori vincolati alla scelta delle dimensioni (per esempio il costo, perchè dipende solo dal modello venduto e non cambia da vendita a vendita). Il concetto importante della Fact table rappresenta solo ciò che si vuole misurare o registrare, es. una vendita, mentre tutti i dettagli devono essere rappresentati nelle dimensioni esterne. Le dimension table possono quindi essere usare per fare filtri, raggruppamenti o dare nomi a certi valori: esempio filtrare per modello, raggruppare per mese e quindi tornare come risultato la vendita media giornaliere per ogni mese. Per esempio Fact table: Sales(serialNo, date, dealer, price) Dimensioni: serialNo , date , dealer (sono chiavi esportate per una dimension table) Attributo dipendente: price , dipende dal serialNo ovvero dal modello, ma potrebbe dipendere anche dal dealer , quindi non è registrato in nessuna dimension table. Dimesion tables: Autos(serialNo, model, color) , Dealers(name, city, state, phone) e date? Per date si immagina una tabella virtuale Date(day, weerk, month, year) Esempio: bevitori di birra: Fact: Sales(bar, beer, drinker, day, time, price) Dimension: Bars(bar, addr, license) Beers(beer, manf) Drinkers(drinker, addr, phone) SLICING E DICING: AFFETTA E SCUBOTTA SELECT e GROUP BY si focalizzano su parti del cubo (dice:dadi) attraverso la proiezioni di dati nelle diverse dimensioni (per esempio il modello di macchina, il colore). Ogni selezione/proiezione di dimensione diventa una fetta slice Nota: Si può dividere il cubo i cubi più piccoli e calcolare degli aggregati. Slicing si riferisce alla clausola WHERE che restringe e partiziona i dati. La forma generale di query "slicing and dicing" è la seguente: SELECT <grouping attributes and aggregations> FROM <fact table joined with some dimension tables> WHERE <certain attributes are constant> GROUP BY <grouping attributes > ; Un esempio: SELECT color, month, SUM(price) FROM (Sales NATURAL JOIN Autos) JOIN Days ON date = day WHERE model = "Gobi" GROUP BY color, month; Qui abbiamo uno slice su modello "Gobi", color e month sono i nostri sotto-cubi (dice), quindi la vendita di "Gobi" rosse a dicembre e gennaio, e la vendita delle gobi blu. All'intersezione di ognuno il valore SUM . Queste query permettono analisi dei dati in profondità, come chiedersi se alcuni modelli di macchine particolari non sono state vendute e perché. DATA CUBE FORMALE Si memorizzano dati riguardanti gli aggregati di determinate dimensioni o valori. L'operazione di creazione di un data-cubo quindi "aumenta" le informazioni della fact table. Non è richiesta genericamente troppa memoria fisica perchè si tratta di aggregati. Se la fact table ha 3 dimensioni, il data-cube rappresenta il risultato di slice e dice su una o più specifiche dimensioni. Il sotto-cubo più scuro nell'immagine è l'aggregato su tutte e tre le dimensioni. Ogni slice (fetta) ha delle aggregazioni parziali. La fact table originale è quella non colorata nell'immagine. L'operatore CUBE aggiunge infati un guscio esterno (colorato nell'immagine). Quindi aumenta lo spazio consumato su disco. Il primo passo, per rendere utile l'utilizzo di un data-cube, è decidere come ridefinire la fact table: Prendiamo per esempio Sales(serialNo, date, dealer, price) , si può decidere di esportare un cubo diverso, sui modelli di macchine ottenendo una nuova fact table con il numero di vendite totali per modello. SalesModel(model, color, date, dealer, value, count) Nota: con value e count possiamo calcolare la media dei valori! dove value è la somma di tutti i prezzi per un dato modello, colore, data e dealer; count è il numero di auto vendute. Oppure utilizzare un cubo con dettagli sui dealer SalesDealer(serialNo, date, city, state, price) Attenzione: Cambiando la Fact table cambia il valore dipendente! Prima price riferiva ad una specifica combinazione serialNo, date, dealer , ora invece è un aggregato Attenzione: Se il nuovo valore dipendente è un aggregato (somma, minimo, massimo, media) dobbiamo pensare a come aggiornarlo! Somma, minimo e massimo sono facili, ma la media? Dobbiamo aggiungere 2 nuovi valori dipendenti: somma e numero istanze, poichè la media è definita dalle due. P.s. La media delle medie, non è la vera media! CUBE(F) : è un operatore sulla fact table F che aggiunge un valore alternativo col simbolo "*" a ogni dimensione. Il simbolo "*" si legge "any" e significa infatti "qualsiasi/tutti". Vuol dire proiettare i valori del data cube su una particolare "faccia" o spigolo per poter costruire fisicamente il cubo. Ad esempio se l'attributo colore era uno tra red, blue, green, gold , aggiungendo CUBE(SalesModel) esiste una nuova entry nella fact table, con parametro * per colore. Esplosione di tutte le possibili aggregazioni CUBE(SalesModel) contiene esattamente tutte le tuple che hanno lo schema della fact table SalesModel . Quindi esisteranno tuple del tipo SalesModel(Gobi, red, 2001-05-21, Friendly Fred, 45000, 2) sia nella fact table Sales e nel CUBE di Sales, mentre solo nel CUBE esistono tuple del tipo CUBE(SalesModel): ('Gobi', *, 2001-05-21, Friendly Fred, 152000, 7) ('Gobi', *, *, *, 1339800000, 58000) (*, *, *, *, 3521727000, 198000) La tupla (Gobi, *, 2001-05-21, Friendly Fred, 152000, 7) rappresenta la somma e il valore delle macchine vendute di tipo "Gobi", in data 2001-05-21 , dal venditore "Friendly Fred" ⇒ un'aggregazione in Sales è divenuta una semplice SELECT su CUBE(SalesModel) perchè quei valori sono già calcolati (notare che è ora molto semplice da scrivere). In SQL attraverso la sintassi GROUP BY ... WITH CUBE noi scriviamo la seguente query CREATE MATERIALIZED VIEW SalesCube AS SELECT model, color, date, dealer, SUM(value), SUM(count) FROM SalesModel GROUP BY model, color, date, dealer WITH CUBE; E otteniamo il risultato di una MATERIALIZED VIEW in cui ci sono tutte le aggegazioni per le possibili combinazioni di model, color, date, dealer con "*". Questo significa che otteniamo fact table addizionali come dscritto sopra CUBE(SalesModel): ('Gobi', *, 2001-05-21, Friendly Fred, 152000, 7) ('Gobi', *, *, *, 1339800000, 58000) (*, *, *, *, 3521727000, 198000) Nota però che in SQL il simbolo "*" non è accettato quindi la fact table conterrà NULL al posto di "*" (con i relativi problemi di ambiguità se non si fa attenzione). Ad ogni modo, otteniamo così di poter lancare una query su SalesCube senza però leggere l'intera tabella SalesModel ma leggendo invece le aggregazioni su CUBE(SalesModel) . Quindi la query SELECT color, AVG(price) FROM SalesModel WHERE model = 'Gobi' GROUP BY color; Non fa un aggregazione su SalesModel , ma fa una query su SalesCube alla ricerca di tuple del tipo ('Gobi', [color], NULL, NULL, [value], [number]) SELECT color, SUM(value)/SUM(number) FROM SalesCube WHERE model = 'Gobi' AND date is NULL AND dealer is NULL GROUP BY color; 1. Il centroide di un cluster è un punto ottenuto sommando le coordinate diviso per il numero di punti (baricentro). ↩