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.
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).
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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *