statistica

CTR atteso vs reale: trovare le pagine che rendono meno della loro posizione

Chi passa le giornate dentro Search Console conosce quel piccolo fastidio: una pagina è stabilmente in terza posizione, eppure i clic sono pochi, un CTR che parrebbe da fondo pagina.
La domanda che ci poniamo, di solito, è quella sbagliata: non «quanti clic prende?», ma quella più scomoda — «quanti clic dovrebbe prendere, stando dov’è?». Finché non abbiamo un termine di paragone, un CTR del 3% non dice niente: per la posizione 8 sarebbe ottimo, per la 2 un piccolo disastro.
Quello che ci manca, per giudicare, è un CTR atteso: il valore con cui confrontare quello reale.

Abbiamo avuto modo di vedere, parlando di correlazione, che posizione e CTR si muovono insieme lungo una curva ripida; e che il passo successivo — usare una variabile per prevederne un’altra — è il mestiere della regressione lineare.
Qui i due fili si annodano: trasformiamo quella curva in un CTR atteso e misuriamo, pagina per pagina, di quanto ciascuna se ne discosta. È il modo per smettere di leggere i CTR come numeri assoluti e iniziare a leggerli per quello che sono davvero: scostamenti da una norma.

Di cosa parleremo:


Perché un CTR, da solo, non significa niente

Esistono tabelle di settore che dicono quanto “dovrebbe” valere il CTR di ogni posizione: la prima intorno al 25-30%, la seconda sotto la metà, e così via a scendere. Sono utili come orizzonte generale, ma per giudicare le nostre pagine portano fuori strada: il CTR dipende dal tipo di query (un marchio cliccatissimo o una ricerca informativa fredda), dal settore, da quanto la SERP è affollata di annunci e rich snippet.
Il CTR medio di “posizione 3” su un benchmark americano di e-commerce non ha quasi nulla da dire al nostro blog tecnico in italiano.

La via d’uscita è smettere di confrontarci con una tabella esterna e costruirci la curva di riferimento sui nostri stessi dati.
Prendiamo tutte le pagine, le loro posizioni medie e i loro CTR, e tracciamo la curva che descrive l’andamento tipico del CTR al variare della posizione per come funziona il nostro sito. Quella curva diventa il metro: il CTR atteso di una pagina è il valore che la curva le assegna, data la sua posizione.
Lo scostamento fra il CTR reale e quel valore atteso è l’informazione che cercavamo.

Un CTR ha senso solo accanto alla posizione che l’ha prodotto: è lo scostamento dalla curva, non il numero assoluto, a dirci se una pagina sta lavorando bene o sta lasciando clic sul tavolo.


Modellare la curva del CTR: tre strade

Costruire la curva significa stimare una funzione che, data la posizione, restituisca il CTR atteso. La forma di quella curva la conosciamo già a occhio: parte alta, crolla a precipizio nelle prime posizioni e poi si appiattisce verso lo zero. Una retta non la descrive; serve qualcosa che curvi.

C’è però un dettaglio che cambia tutto il modo di ragionare, ed è il tipo di scostamento che ci interessa.
A noi non importa che una pagina prenda “due punti di CTR in meno” dell’atteso: in cima alla SERP due punti sono briciole, in fondo sono un raddoppio. Ci interessa lo scostamento moltiplicativo — “rende la metà di quanto dovrebbe”, “rende il doppio”.
E lo scostamento moltiplicativo si maneggia bene su scala logaritmica, dove un rapporto diventa una differenza.

La forma più naturale per una curva di questo tipo è la legge di potenza, cioè l’idea che il CTR sia proporzionale alla posizione elevata a un esponente negativo:

\( \text{CTR} = a \cdot posizione^{b} \\ \)

dove \( a \) fissa il livello generale e \( b \) (negativo) governa la rapidità della discesa. Il bello arriva prendendo il logaritmo di entrambi i lati, che trasforma quella curva in una retta:

\( \log(\text{CTR}) = \log(a) + b \cdot \log(posizione) \\ \)

In altri termini: il logaritmo del CTR è una funzione lineare del logaritmo della posizione. E stimare una retta è esattamente ciò che sappiamo fare con la regressione. Da qui, tre strade per costruire la curva.

La prima, e quella che consiglio come cavallo di battaglia, è una regressione lineare sui logaritmilm(log(ctr) ~ log(posizione)). È in R base, è interpretabile (la pendenza \( b \) è l’elasticità del CTR alla posizione: di quanto cala in percentuale il CTR a ogni punto percentuale di posizione in più), e i suoi residui sono già su scala logaritmica, quindi moltiplicativi, proprio come ci serve.
Si estrapola anche a posizioni poco osservate, e la si può pesare per le impression (weights = impression), così le pagine con due clic in croce non sbilanciano la curva quanto quelle con decine di migliaia di visualizzazioni. È una scelta pragmatica più che il peso teoricamente ottimo (per una proporzione la varianza dipende anche dal CTR stesso), ma nella pratica funziona benissimo.

La seconda è la regressione non lineare con nls, che stima direttamente \( a \) e \( b \) sulla scala naturale del CTR senza passare per i logaritmi. È un raffinamento elegante, ma va innescato con valori di partenza sensati (che si pescano proprio dalla regressione log-log) e su dati sporchi può non convergere. La tengo per quando serve un parametro pulito da mettere in un report, non come punto di partenza.

La terza è il lisciamento locale con loess, che non impone nessuna forma alla curva e la lascia “disegnare ai dati”. È perfetto per vedere l’andamento a colpo d’occhio, ma traballa sulle code (poche pagine in prima posizione) e soprattutto non estrapola: fuori dall’intervallo osservato non sa che dire. È uno strumento esplorativo, non il modello su cui costruire i giudizi.

Dunque: partiamo dalla regressione log-log pesata per le impression come modello di lavoro, la confrontiamo a occhio con un loess per controllare che non stiamo forzando una forma sbagliata, e passiamo a nls solo se ci serve l’esponente esplicito. Vediamola al lavoro.


Un esempio con dati di Search Console

Partiamo da un estratto come quello che chiunque può scaricare da Search Console: una riga per pagina, con le impression, i clic e la posizione media. Nella realtà il CTR è il rapporto fra clic e impression; qui, trattandosi di dati d’esempio, facciamo il percorso inverso — fissiamo un CTR plausibile e ricostruiamo i clic.
Costruisco la tabella in R con dodici pagine di esempio (con dentro, di proposito, un paio di casi anomali):

gsc <- data.frame(
  pagina     = c("/guida-seo-tecnica","/checklist-audit-seo",
                 "/guida-keyword-research","/calcolatore-roi-campagne",
                 "/tutorial-google-analytics","/glossario-statistica",
                 "/recensione-tool-seo","/guida-link-building",
                 "/analisi-dei-competitor","/modello-attribuzione",
                 "/report-posizionamento","/ottimizzazione-meta-tag"),
  impression = c(  9800, 5400, 12500, 2100, 7600, 1500,
                   8300, 4200,  6100,  900, 3300, 1800),
  posizione  = c(  1.3,  2.1,   3.4,  4.0,  4.6,  5.2,
                   2.8,  6.1,   7.0,  8.3,  9.1, 10.2)
)
# CTR osservato (in genere lo si calcola come click / impression)
gsc$ctr   <- c(0.232, 0.150, 0.034, 0.071, 0.066, 0.060,
               0.171, 0.048, 0.041, 0.012, 0.031, 0.028)
gsc$click <- round(gsc$impression * gsc$ctr)

Stimo ora la curva del CTR atteso con la regressione sui logaritmi, pesando ogni pagina per le sue impression:

fit <- lm(log(ctr) ~ log(posizione), data = gsc, weights = impression)
round(coef(fit), 3)
# (Intercept)  log(posizione)
#      -1.240          -1.088

La pendenza è −1,088: un valore vicino a −1 racconta una curva quasi inversamente proporzionale, in cui raddoppiare la posizione (passare, diciamo, dalla 3 alla 6) taglia il CTR all’incirca della metà.
È la stessa caduta ripida che avevamo intravisto misurando la correlazione, ora però scritta in una formula che possiamo interrogare: dato un numero di posizione, ci restituisce il CTR tipico che quella posizione comporta sul nostro sito.


I residui: chi rende meno di quanto dovrebbe

Avere la curva significa poter calcolare, per ogni pagina, il suo CTR atteso e confrontarlo con quello reale. Il confronto, lo abbiamo detto, va fatto in termini di rapporto e non di differenza: rapporto = ctr_reale / ctr_atteso. Un valore intorno a 1 dice che la pagina rende quanto previsto; molto sotto 1 che lascia clic sul tavolo; molto sopra 1 che cattura più della sua quota.
Calcolo il CTR atteso, il rapporto e segnalo i casi che si discostano davvero — ma solo se hanno abbastanza impression da rendere il loro CTR affidabile:

gsc$ctr_atteso <- exp(predict(fit))           # torno dalla scala log a quella naturale
gsc$rapporto   <- gsc$ctr / gsc$ctr_atteso

gsc$flag <- ifelse(gsc$rapporto < 0.6 & gsc$impression >= 1000, "SOTTO",
             ifelse(gsc$rapporto > 1.4 & gsc$impression >= 1000, "SOPRA", "ok"))

gsc[order(gsc$rapporto),
    c("pagina","posizione","impression","ctr","ctr_atteso","rapporto","flag")]

n.b. il predict ci dà il logaritmo del CTR atteso, perché è su quella scala che abbiamo stimato il modello: l’exp lo riporta in CTR vero e proprio. A rigore l’exp ci restituisce la mediana del CTR atteso, non la media aritmetica (sotto errori log-normali la media vera è un filo più alta), ma per i rapporti relativi che ci interessano la distinzione è ininfluente.
L’output, ordinato dal rapporto più basso al più alto:

paginaposizioneimpressionctrctr_attesorapportoflag
/modello-attribuzione8,39000,0120,0290,41ok
/guida-keyword-research3,4125000,0340,0760,44SOTTO
/guida-seo-tecnica1,398000,2320,2181,07ok
/calcolatore-roi-campagne4,021000,0710,0641,11ok
/checklist-audit-seo2,154000,1500,1291,16ok
/analisi-dei-competitor7,061000,0410,0351,18ok
/report-posizionamento9,133000,0310,0261,18ok
/guida-link-building6,142000,0480,0401,19ok
/tutorial-google-analytics4,676000,0660,0551,20ok
/ottimizzazione-meta-tag10,218000,0280,0231,21ok
/glossario-statistica5,215000,0600,0481,25ok
/recensione-tool-seo2,883000,1710,0941,81SOPRA

Il caso che salta all’occhio è /guida-keyword-research: sta in terza posizione, dove la curva si aspetterebbe un CTR del 7,6%, e invece raccoglie un misero 3,4% — meno della metà del dovuto, su dodicimila e cinquecento impression che rendono il dato solidissimo.
È un’ipotesi forte e immediatamente azionabile: con ogni probabilità il title e la meta description non stanno facendo il loro lavoro, e una riscrittura potrebbe sbloccare clic che la posizione avrebbe già meritato.

All’estremo opposto c’è /recensione-tool-seo, che in seconda-terza posizione rende quasi il doppio dell’atteso. Non è un problema, è una lezione: qualcosa in quello snippet funziona benissimo — un titolo magnetico, una rich card, una corrispondenza perfetta con l’intento — e vale la pena capire cosa, per provare a replicarlo altrove. I residui non servono solo a trovare i malati: le sovra-performance sono i casi di studio da cui imparare cosa, sul nostro sito, fa cliccare.


Leggere gli scostamenti senza farsi ingannare

C’è un dettaglio nella tabella che è il cuore di tutto il discorso, e che è facile mancare. /modello-attribuzione ha un rapporto di 0,41 — più basso ancora di /guida-keyword-research — eppure non l’abbiamo segnalato. Il motivo è nella colonna delle impression: novecento. Un CTR calcolato su così pochi dati è quasi solo rumore, e il mese prossimo rientrerà verso la sua media a prescindere da qualunque cosa facciamo.
Segnalarlo come “pagina da ottimizzare” ci farebbe inseguire un fantasma. Due pagine con lo stesso scostamento, due verdetti opposti, e a fare la differenza è solo la quantità di dati che li sostiene.

Un avvertimento: il CTR atteso è un valore tipico condizionato — la mediana che la curva associa a una posizione — non una legge di natura. Una pagina può “sotto-performare” per ragioni che con il title non c’entrano nulla: una query di marca che gonfia il CTR dei concorrenti, un featured snippet o un blocco di annunci che si mangia i clic prima del primo risultato organico, un intento puramente informativo che si soddisfa già leggendo lo snippet. E un CTR costruito su poche impression non misura quasi niente: regredirà verso la sua media da solo, come abbiamo visto parlando di regressione verso la media. Un residuo negativo è un’ipotesi da verificare — “forse qui il title rende poco” — non un verdetto da eseguire.

Vale anche la pena ricordare che la curva la abbiamo stimata sui nostri dati, e quei dati la influenzano: una manciata di pagine molto anomale può inclinarla quel tanto che basta a spostare i giudizi sulle altre.
Lo si vede già nella nostra tabella: dieci pagine su dodici hanno un rapporto sopra l’1, ma non è che il sito “sovra-performi” quasi ovunque. È che l’unico grosso scostamento negativo, /guida-keyword-research, pesa moltissimo (dodicimila e cinquecento impression) e tira la curva verso il basso, alzando di riflesso il rapporto di tutte le altre. Il “centro” della nuvola, insomma, non è esattamente 1, e conviene leggere i rapporti in termini relativi — chi sta molto sotto e chi molto sopra il grosso del gruppo — più che rispetto alla soglia secca dell’uno.
Per questo il peso delle impression è prezioso, e per questo conviene rifare la curva ogni volta che il quadro cambia, invece di trattarla come una costante incisa nella pietra.


Prova tu

Il modo migliore per fissare il meccanismo è metterci le mani. Riprendendo il codice qui sopra, ci sono tre direzioni interessanti da esplorare:

  1. Aggrega per query invece che per pagina: la stessa pagina può comparire su decine di ricerche con posizioni e CTR diversi, e spesso è lì — sulla singola query — che si nasconde l’occasione persa. La curva e i residui si costruiscono allo stesso identico modo.
  2. Sostituisci la regressione log-log con un loess(ctr ~ posizione) e confronta i due CTR attesi: dove combaciano e dove divergono? Sulle code (prime posizioni, poche pagine) chi dei due ti sembra più prudente?
  3. Cambia la soglia di impression sotto cui non ti fidi del CTR: passando da 1000 a 3000, quali pagine escono dal radar? Il numero giusto non esiste in astratto, dipende da quanto traffico muove il tuo sito.

Suggerimento: la struttura non cambia mai — si stima la curva, si predice l’atteso, si guarda il rapporto. È giocando con la soglia e con il livello di aggregazione che si capisce quanto di ciò che chiamiamo “pagina sotto-performante” sia segnale e quanto, semplicemente, rumore.


Individuare una pagina che rende meno del previsto per la sua posizione è cugino stretto di un altro problema che ogni analista conosce: individuare un giorno che rende meno del previsto nel tempo, un crollo o un picco nel traffico che non torna con l’andamento abituale. È lo stesso ragionamento sui residui — valore osservato contro valore atteso — spostato dallo spazio delle posizioni all’asse del tempo, dove l’atteso lo fornisce l’andamento storico della serie. È esattamente il mestiere dell’anomaly detection: distinguere il segnale dal rumore quando i numeri si muovono nel tempo — il passo naturale da cui proseguire.


Per approfondire

Se vuoi approfondire la regressione, le trasformazioni logaritmiche e la lettura dei residui — l’ossatura esatta del modello che abbiamo costruito qui — Introduzione all’econometria di Stock e Watson è il riferimento che consiglio: spiega con cura sia il “perché” dei logaritmi sia il “come” si interpretano i coefficienti, partendo sempre da problemi applicati. A chi volesse spingersi oltre la legge di potenza, verso i metodi locali come il loess, An Introduction to Statistical Learning di James, Witten, Hastie e Tibshirani offre i capitoli giusti, con i laboratori in R sotto mano.

Paolo Gironi

Recent Posts

Clustering delle keyword: raggruppare migliaia di query con K-means e clustering gerarchico

Capita con ogni progetto un po' serio: si esporta l'elenco delle keyword da Search Console…

3 ore ago

Naive Bayes: classificare l’intento delle query con il teorema di Bayes

Nell'articolo sul multi-armed bandit abbiamo usato Bayes per decidere fra varianti: spostare il traffico verso…

2 giorni ago

Multi-armed bandit: ottimizzare le varianti mentre il test è ancora in corso

Nell'articolo sull'A/B test bayesiano abbiamo confrontato due varianti a campione fisso: si raccolgono i dati…

3 giorni ago

A/B test bayesiano: non solo “se” B è meglio di A, ma “di quanto”

Abbiamo avuto modo di esaminare, nell'articolo sull'A/B testing classico, come confrontare due varianti con il…

4 giorni ago

Stima bayesiana di un conversion rate: quanto possiamo fidarci dei pochi dati che abbiamo

Abbiamo avuto modo di esaminare, nell'articolo sulle fondamenta della statistica bayesiana, come l'aggiornamento bayesiano funzioni…

5 giorni ago

Il peeking problem: perché sbirciare l’A/B test gonfia i falsi positivi

Il 21 gennaio 2015 Optimizely — una delle piattaforme di A/B testing più usate al…

1 settimana ago