statistica

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

Chi passa le giornate dentro Search Console conosce bene una situazione di questo tipo: 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.


Le altre due strade: loess e nls al lavoro

Abbiamo scelto la regressione log-log come modello di lavoro, ma le tre strade le avevamo promesse tutte. Vale la pena vederle davvero all’opera sugli stessi dodici dati, per capire cosa aggiungono le altre due — e soprattutto dove inciampano.

La regressione non lineare stima \( a \) e \( b \) direttamente sulla scala del CTR, senza passare per i logaritmi. Va innescata con valori di partenza sensati, che peschiamo proprio dalla log-log appena stimata: l’intercetta riportata in scala naturale con l’exp è la nostra \( a \), la pendenza è la nostra \( b \). La faccio partire in R:

start <- list(a = exp(coef(fit)[1]), b = coef(fit)[2])  # innesco dalla log-log
fit_nls <- nls(ctr ~ a * posizione^b, data = gsc,
               weights = impression, start = start)
round(coef(fit_nls), 3)
#      a       b
#  0.315  -1.073

Converge, e restituisce un esponente −1,073, praticamente lo stesso della log-log, ma con un regalo in più: ora \( a \) e \( b \) sono numeri leggibili sulla scala vera del CTR. Un \( a \) di 0,315 dice, in soldoni, che in cima alla SERP il CTR tipico è attorno al 31% — esattamente il genere di parametro pulito da mettere in un report.
Il prezzo lo paghiamo in fragilità: senza quei valori di partenza, o su dati più rumorosi, nls può non convergere affatto e restituirci soltanto un errore.

Il lisciamento locale con loess fa l’opposto: non impone nessuna forma, lascia disegnare la curva ai dati. Lo stimo e gli chiedo il CTR atteso a qualche posizione, inclusa la prima:

fit_lo <- loess(ctr ~ posizione, data = gsc, span = 0.9)
round(predict(fit_lo, data.frame(posizione = c(1, 2, 3, 5))), 3)
# [1]    NA 0.173 0.111 0.029

Ed ecco il limite in un solo output: alla posizione 1, loess restituisce NA. Il minimo che i nostri dati osservano è 1,3, e fuori da quell’intervallo loess si rifiuta di pronunciarsi — non estrapola.
Per una curva di CTR è un difetto serio: la prima posizione, quella che ci interessa di più, è spesso la meno popolata, ed è proprio lì che il metodo locale ci lascia a bocca asciutta.

Messe sullo stesso grafico, le tre strade raccontano tutto in un colpo d’occhio:

Le tre stime della curva del CTR atteso sui dodici dati d’esempio. Al centro, dove le pagine abbondano, quasi coincidono; sulle code divergono. Il loess (viola) si ferma alla posizione 1,3 — non estrapola — mentre lm (blu) e nls (arancio) proseguono anche sotto la prima posizione. La curva nls sta un filo sopra la log-log perché ne stima il livello direttamente sulla scala del CTR.

Al centro, dove le pagine abbondano, le tre curve quasi coincidono: qualunque metodo va bene quando i dati parlano chiaro. È sulle code che si separano — loess ondeggia dietro le poche pagine che trova e si ferma di netto al bordo dei dati, mentre lm e nls proseguono lisce anche dove le osservazioni scarseggiano.

Dunque: la log-log pesata resta il cavallo di battaglia — interpretabile, estrapolabile, con i residui già in scala moltiplicativa. nls la raffina quando serve un esponente pulito da mettere nero su bianco; loess è l’occhio critico che, prima di fidarci, ci dice se stiamo forzando la forma sbagliata. Tre strumenti diversi per un solo mestiere: trasformare la posizione in un CTR atteso.


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.

Le dodici pagine d’esempio: posizione media in SERP contro CTR, con l’area di ogni punto proporzionale alle impression. La curva blu è il CTR atteso stimato con la log-log pesata; il segmento verticale sotto ogni punto è il suo residuo, cioè la distanza dalla curva. Spiccano le due pagine dell’esempio: /guida-keyword-research a 0,44× l’atteso (rosso, sotto-performa) e /recensione-tool-seo a 1,81× (verde, sovra-performa).

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. Togli il peso delle impression — lm(log(ctr) ~ log(posizione)) senza weights — e confronta esponente e verdetti con la versione pesata: quanto sposta le conclusioni l’aver dato più voce alle pagine con più dati? È il modo per sentire quanto conta la pesatura, invece di prenderla sulla fiducia.
  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 spesso che si esporti l'elenco delle keyword da Search Console o da un tool,…

1 mese 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…

1 mese 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…

1 mese 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…

1 mese 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…

1 mese 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 mese ago