Capita con ogni progetto un po’ serio: si esporta l’elenco delle keyword da Search Console o da un tool, e ci si ritrova davanti a migliaia di righe. Tremila, diecimila query. Leggerle una per una è impensabile, e raggrupparle a mano “a sentimento” è un lavoro lento, soggettivo e impossibile da rifare.
Eppure quel raggruppamento ci serve: vogliamo capire quali grandi famiglie di ricerche esistono nel nostro mercato, per decidere dove creare contenuti, quali pagine costruire, su cosa puntare.
La domanda è: possiamo lasciare che siano i dati a rivelarci i gruppi, invece di imporli noi? Trasformare quella montagna di query in pochi insiemi omogenei è il lavoro del clustering delle keyword.
Abbiamo già affrontato un problema vicino, classificare l’intento di una query con il Naive Bayes — ma lì avevamo un ingrediente che oggi ci manca: un insieme di esempi già etichettati da cui imparare. Qui le etichette non ce le ha date nessuno. È il territorio del clustering, uno degli strumenti più usati del machine learning, e in questo articolo lo costruiamo in R con i suoi due algoritmi classici: il K-means e il clustering gerarchico.
Di cosa parleremo:
La differenza con il Naive Bayes è di principio, non di dettaglio. Lì facevamo apprendimento supervisionato: avevamo query già marcate come informazionali, navigazionali o transazionali, e insegnavamo all’algoritmo a riconoscere le nuove. Qui facciamo apprendimento non supervisionato: nessuno ci ha detto quali e quanti gruppi esistano. Il clustering non verifica un’etichetta che già conosciamo: cerca una struttura che non sapevamo ci fosse.
Per raggruppare servono due cose. La prima è descrivere ogni keyword con dei numeri: nel nostro esempio useremo il volume di ricerca, il costo per clic (cpc), la posizione media e il numero di parole.
La seconda è una nozione di distanza: due keyword sono “vicine” se i loro numeri si somigliano. La distanza più comune è quella euclidea, la stessa che useremmo su una mappa, solo calcolata in uno spazio a quattro dimensioni (una per ogni metrica).
C’è però una trappola da disinnescare subito. Il volume si misura in decine di migliaia, il cpc in centesimi di euro: lasciate così, le distanze sarebbero dominate dal volume, e il cpc non conterebbe quasi nulla.
Prima di calcolare qualsiasi distanza dobbiamo mettere tutte le variabili sulla stessa scala, standardizzandole — in R con la funzione scale(), che a ogni colonna sottrae la media e la divide per la deviazione standard. Solo a quel punto un euro di differenza nel cpc e diecimila ricerche di differenza nel volume “pesano” allo stesso modo.
L’idea del K-means è quasi ingenua nella sua semplicità. Decidiamo in quanti gruppi (k) vogliamo dividere i dati; l’algoritmo piazza k punti-rappresentanti, i centroidi, e poi ripete due passi finché le cose si stabilizzano: assegna ogni keyword al centroide più vicino, poi sposta ogni centroide nel centro delle keyword che gli sono state assegnate. Ad ogni giro i gruppi diventano un po’ più coesi, finché non si muovono più.
Ciò che l’algoritmo cerca di minimizzare, a parole, è la dispersione interna dei gruppi: la somma delle distanze (al quadrato) di ogni punto dal centroide del proprio cluster. In formula:
\( \text{WCSS} = \sum_{k=1}^{K} \sum_{x \in C_k} \lVert x – \mu_k \rVert^2 \\ \)dove \( C_k \) è il k-esimo cluster, \( \mu_k \) il suo centroide e la doppia sommatoria scorre tutti i punti di tutti i gruppi. Più la WCSS (within-cluster sum of squares) è bassa, più i gruppi sono compatti.
Resta il punto dolente: k lo dobbiamo decidere noi, prima di iniziare. Un aiuto viene dal metodo del gomito: proviamo diversi valori di k e guardiamo come cala la WCSS. All’inizio aggiungere un cluster aiuta molto, poi i miglioramenti si fanno marginali; il “gomito” della curva — il punto dove la discesa si appiattisce — suggerisce un k ragionevole. Costruisco la tabella di keyword e calcolo la WCSS da 1 a 6 gruppi in R:
kw <- data.frame(
keyword = c("scarpe running","scarpe running uomo","nike pegasus","nike pegasus 40",
"migliori scarpe trail 2026","come scegliere scarpe running",
"scarpe running offerta","comprare scarpe trail online",
"differenza scarpe trail e strada","scarpe running pronazione",
"asics gel nimbus","saucony endorphin","recensione scarpe trail",
"scarpe running scontate","negozio scarpe running milano"),
volume = c(40000,18000,12000,8000,2400,1900,3200,880,1300,2100,9000,4000,1500,2600,720),
cpc = c(0.45,0.55,0.30,0.35,0.40,0.10,0.95,1.10,0.08,0.30,0.28,0.33,0.15,0.90,0.85),
posizione= c(3.1,4.2,2.0,5.5,8.1,11.2,6.0,9.4,14.0,7.3,2.5,6.8,12.1,5.9,4.7),
n_parole = c(2,3,2,3,5,5,3,4,6,3,3,2,3,3,4)
)
# standardizzo le quattro metriche (scale diverse -> stesso peso)
X <- scale(kw[, c("volume","cpc","posizione","n_parole")])
# metodo del gomito: WCSS per k da 1 a 6
set.seed(1)
wss <- sapply(1:6, function(k) kmeans(X, centers = k, nstart = 10)$tot.withinss)
round(wss, 1)
# [1] 56.0 34.4 20.7 12.4 8.7 6.9 Il calo è ripido fino a tre gruppi (56 → 34 → 21) e poi rallenta nettamente (21 → 12 → 9 → 7). Il gomito non è mai una linea netta — è una lettura, non un teorema — ma qui indica con ragionevolezza k = 3. Lancio allora il K-means con tre centroidi:
set.seed(1)
km <- kmeans(X, centers = 3, nstart = 25)
kw$cluster <- km$cluster
table(km$cluster)
# 1 2 3
# 4 7 4 n.b. l’argomento nstart = 25 fa ripartire l’algoritmo 25 volte da centroidi iniziali diversi, tenendo la soluzione migliore: il K-means può infatti incagliarsi in un minimo locale a seconda di dove parte, e ripartire più volte è la difesa standard. Il set.seed(1) serve solo a rendere l’esempio riproducibile (compresa la numerazione dei cluster, che di per sé è arbitraria).
Avere tre gruppi non serve a niente finché non capiamo cosa rappresentano. Il modo più diretto è guardare le metriche medie di ciascun cluster.
Le calcolo sulla scala originale (non quella standardizzata, che è illeggibile):
aggregate(kw[, c("volume","cpc","posizione","n_parole")],
by = list(cluster = kw$cluster), FUN = mean)
# cluster volume cpc posizione n_parole
# 1 1 1775 0.18 11.35 4.75
# 2 2 13300 0.37 4.49 2.57
# 3 3 1850 0.95 6.50 3.50 Adesso i gruppi parlano.
Il cluster 2 raccoglie le query a volume altissimo (in media 13.300 ricerche), corte (due-tre parole), ben posizionate e con cpc modesto: sono le teste generiche e di marca — “scarpe running”, “nike pegasus”, “asics gel nimbus”. Il cluster 1 ha volumi bassi, query lunghe (quasi cinque parole), posizioni arretrate e cpc minimo: è il long-tail informazionale — “come scegliere scarpe running”, “differenza scarpe trail e strada”, “recensione scarpe trail”. Il cluster 3 si distingue per un cpc altissimo (0,95 €) e contiene le query a chiaro taglio commerciale — “scarpe running offerta”, “comprare scarpe trail online”, “scarpe running scontate”, “negozio scarpe running milano”.
Vale la pena fermarsi un istante su cosa è appena successo. Senza dare all’algoritmo alcuna etichetta, i tre gruppi che emergono ricalcano da vicino una distinzione per intento — ricerca informativa, teste generiche e di marca, query commerciali — vicina a quella che con il Naive Bayes avevamo invece dovuto insegnargli a colpi di esempi.
L’allineamento, attenzione, non è magia: emerge perché le metriche che abbiamo scelto (cpc, lunghezza, posizione) tracciano indirettamente l’intento, non perché il clustering lo conosca — di significato delle query, qui, non ne ha vista neppure una parola. La lettura resta comunque immediatamente operativa: il cluster informativo chiede articoli e guide, quello commerciale pagine prodotto e di offerta, quello ad alto volume pagine pilastro robuste.
Il K-means ci ha costretti a scegliere k in anticipo. Il clustering gerarchico ribalta l’approccio: non decide nulla a priori, e costruisce invece un albero completo di raggruppamenti. Parte da ogni keyword come gruppo a sé, poi fonde via via le due più vicine, poi le due più vicine fra quelle rimaste, e così via fino a un unico grande gruppo. Il risultato è un dendrogramma: un albero che mostra a quale “altezza” (cioè a quale distanza) ogni fusione avviene.
Lo costruisco in R calcolando prima la matrice delle distanze, poi l’albero:
d <- dist(X) # distanze euclidee fra le keyword standardizzate
hc <- hclust(d, method = "ward.D2") # albero gerarchico (criterio di Ward)
plot(hc, labels = kw$keyword) # il dendrogramma Il bello del dendrogramma è che la decisione su quanti gruppi tenere la prendiamo dopo averlo visto, semplicemente “tagliandolo” a un’altezza scelta: un taglio basso lascia molti gruppetti, un taglio alto pochi gruppi grandi. Taglio a tre gruppi e confronto con il K-means:
kw$cluster_hc <- cutree(hc, k = 3)
table(kw$cluster_hc)
# 1 2 3
# 8 3 4 La partizione non è identica a quella del K-means (qui i gruppi sono da 8, 3 e 4 keyword), ed è normale: i due metodi ottimizzano criteri diversi e su pochi dati le differenze si notano.
Ma la sostanza dei raggruppamenti — il blocco transazionale ad alto cpc, le teste ad alto volume, la coda informazionale — resta riconoscibile in entrambi. Quando due metodi diversi convergono sulla stessa storia, possiamo fidarci un po’ di più di quella storia.
La scelta fra i due, nella pratica, segue poche regole semplici. Il K-means è veloce ed efficiente anche su decine di migliaia di keyword, ma vuole che gli si dica k e tende a costruire gruppi di forma “sferica” e di dimensioni simili. Il gerarchico non chiede k in anticipo e regala il dendrogramma — prezioso per vedere come i gruppi si annidano l’uno nell’altro — ma diventa pesante quando le keyword sono troppe. La pratica più comune: esplorare in gerarchico su un campione per farsi un’idea del numero di gruppi, poi applicare il K-means all’intero set con il k così individuato.
Un avvertimento, il più importante di tutti: il clustering trova sempre dei gruppi, anche quando non ce ne sono. Diamogli del rumore puro e ce lo dividerà diligentemente in k cluster ordinati. Il numero di gruppi, le metriche scelte per descrivere le keyword, la standardizzazione: sono tutte decisioni nostre, e ognuna cambia il risultato. Un raggruppamento non è una verità scoperta nei dati, è un’ipotesi di lavoro che ha senso solo se regge alla prova del buon senso di business. Se un cluster non riusciamo a spiegarlo a parole, probabilmente non esiste davvero.
C’è poi una questione di dimensioni. Qui abbiamo usato quattro metriche, ma nella realtà operativa le variabili descrittive di una keyword possono essere molte di più, e con tante dimensioni le distanze perdono di significato (tutto tende a sembrare ugualmente lontano).
È esattamente il problema che l’analisi delle componenti principali sa alleggerire, comprimendo molte metriche in poche componenti prima di passare il testimone al clustering.
Il modo migliore per capire il clustering è vederlo cambiare risposta al variare delle scelte. Riprendendo il codice qui sopra:
kw[, c("volume","cpc","posizione","n_parole")] senza scale(). I gruppi crollano tutti sul volume? È la dimostrazione pratica di perché si standardizza.method = "complete" o "average" al posto di "ward.D2". Il dendrogramma cambia forma? E i gruppi tagliati a k=3?Suggerimento: tieni sempre d’occhio le medie per cluster con aggregate(). È lì, e non nel codice, che si decide se un raggruppamento è utile o è solo una partizione elegante di niente.
Abbiamo raggruppato le keyword in base a come si comportano — volume, costo, posizione.
Ma resta fuori la cosa che per un SEO conta di più: il loro significato. Due query possono avere metriche diverse e voler dire la stessa cosa, oppure metriche simili e intenti opposti. Raggruppare per senso, e non solo per numeri, significa trasformare il testo stesso delle query in qualcosa di misurabile: è il mestiere del text mining, dove le parole diventano vettori e la similarità si calcola sul linguaggio. Ed è da lì che ripartiremo.
Se vuoi approfondire il clustering — K-means, metodi gerarchici, la scelta del numero di gruppi e le insidie dell’interpretazione — An Introduction to Statistical Learning di James, Witten, Hastie e Tibshirani dedica all’apprendimento non supervisionato un capitolo limpido, con i laboratori in R che ricalcano esattamente i passi che abbiamo visto qui. È il riferimento che consiglio a chi vuole passare dall’esempio giocattolo al clustering su dati veri.
Chi passa le giornate dentro Search Console conosce quel piccolo fastidio: una pagina è stabilmente…
Nell'articolo sul multi-armed bandit abbiamo usato Bayes per decidere fra varianti: spostare il traffico verso…
Nell'articolo sull'A/B test bayesiano abbiamo confrontato due varianti a campione fisso: si raccolgono i dati…
Abbiamo avuto modo di esaminare, nell'articolo sull'A/B testing classico, come confrontare due varianti con il…
Abbiamo avuto modo di esaminare, nell'articolo sulle fondamenta della statistica bayesiana, come l'aggiornamento bayesiano funzioni…
Il 21 gennaio 2015 Optimizely — una delle piattaforme di A/B testing più usate al…