<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>paologironi blog</title>
	<atom:link href="https://www.gironi.it/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.gironi.it/blog</link>
	<description>Appunti sparsi di (retro) informatica, analisi dei dati, statistica, seo, e cose che cambiano</description>
	<lastBuildDate>Thu, 15 Oct 2026 14:15:00 +0000</lastBuildDate>
	<language>it-IT</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Survivorship bias: perché i case study di successo ci ingannano</title>
		<link>https://www.gironi.it/blog/survivorship-bias/</link>
					<comments>https://www.gironi.it/blog/survivorship-bias/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 14:13:58 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=4552</guid>

					<description><![CDATA[Lo abbiamo letto tutti, magari su LinkedIn con tanto di grafico in salita: «Abbiamo riscritto i title di 300 pagine e in tre mesi il traffico è cresciuto del 38%». Il case study è onesto, i numeri sono veri, e la tentazione di copiare la ricetta è fortissima. Ma c&#8217;è una domanda che il post &#8230; <a href="https://www.gironi.it/blog/survivorship-bias/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Survivorship bias: perché i case study di successo ci ingannano"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Lo abbiamo letto tutti, magari su LinkedIn con tanto di grafico in salita: «Abbiamo riscritto i title di 300 pagine e in tre mesi il traffico è cresciuto del 38%». <br>Il case study è onesto, i numeri sono veri, e la tentazione di copiare la ricetta è fortissima. Ma c&#8217;è una domanda che il post non si pone, e che conviene porsi al posto suo: <strong>quante altre aziende hanno fatto la stessa cosa, e com&#8217;è andata a loro?</strong> <br>Chi ha riscritto i title e ha perso il 10% non scrive un case study. Non lo scrive nemmeno chi è rimasto fermo. Il loro silenzio non è un dettaglio: è metà dei dati.</p>



<p class="wp-block-paragraph">Nell&#8217;<a href="https://www.gironi.it/blog/p-value/">articolo sul p-value</a> abbiamo visto che anche i risultati «significativi» possono ingannarci. Qui l&#8217;inganno è ancora più semplice, e per questo più insidioso: guardiamo solo chi ce l&#8217;ha fatta. Si chiama <strong>survivorship bias</strong>, il bias del sopravvissuto.</p>



<span id="more-4552"></span>



<p class="wp-block-paragraph"><strong>In sintesi:</strong> il <strong>survivorship bias</strong> (in italiano <em>bias del sopravvissuto</em> o <em>di sopravvivenza</em>) è l&#8217;errore che commettiamo quando traiamo conclusioni guardando solo i casi che hanno superato una selezione, ignorando quelli che sono stati scartati lungo la strada. Se la selezione dipende proprio dal risultato che stiamo misurando (si pubblica il case study solo se è andata bene, resta online solo l&#8217;articolo che porta traffico) la media calcolata sui sopravvissuti è distorta, a volte di molto. Lo si combatte chiedendosi sempre <strong>chi manca dal dataset e perché</strong>, definendo il gruppo da studiare <em>prima</em> di conoscere l&#8217;esito e conservando i dati dei casi falliti.</p>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#cose">Cos&#8217;è il survivorship bias?</a></li><li><a href="#case-study">Un esempio: 1.000 siti e 55 case study</a></li><li><a href="#cimitero">Chi non torna non racconta</a></li><li><a href="#potatura">Un secondo esempio: la potatura dei contenuti</a></li><li><a href="#dove">Dove si nasconde il survivorship bias nella SEO</a></li><li><a href="#difendersi">Come difendersi?</a></li><li><a href="#domande-frequenti">Domande frequenti</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="cose">Cos&#8217;è il survivorship bias?</h2>



<p class="wp-block-paragraph">La storia più famosa arriva dalla Seconda guerra mondiale. A New York, alla Columbia University, lavorava lo <em>Statistical Research Group</em>, un gruppo di matematici al servizio delle forze armate americane; fra loro c&#8217;era <strong>Abraham Wald</strong>. <br>Il problema, nella versione che si racconta di solito, era questo: gli aerei che tornavano dalle missioni erano crivellati di colpi, ma non in modo uniforme. La fusoliera e le ali ne avevano molti, i motori pochissimi. Dove conveniva aggiungere la corazza, sapendo che non la si poteva mettere dappertutto senza appesantire troppo l&#8217;aereo?</p>



<p class="wp-block-paragraph">La risposta istintiva è «dove ci sono più fori». Wald propose l&#8217;esatto contrario: <strong>la corazza va dove i fori mancano</strong>. <br>Gli aerei osservati erano quelli <em>tornati</em>. Se sui motori dei sopravvissuti i colpi erano rari, non era perché i motori venissero colpiti poco: era perché gli aerei colpiti ai motori non tornavano. I fori mancanti erano sugli aerei abbattuti, che nessuno poteva contare.</p>



<p class="wp-block-paragraph">(La versione aneddotica semplifica parecchio: il lavoro di Wald fu un vero metodo matematico per stimare la vulnerabilità degli aerei a partire dai danni dei sopravvissuti. Ma il cuore del ragionamento è proprio quello.)</p>



<p class="wp-block-paragraph">Dunque: <strong>il survivorship bias è l&#8217;errore di trarre conclusioni solo dai casi che hanno superato una selezione, come se rappresentassero tutti.</strong> <br>Non è un errore di calcolo: le medie sui sopravvissuti sono calcolate benissimo. È un errore su <em>chi</em> entra nel calcolo. E diventa grave quando la selezione dipende dall&#8217;esito che ci interessa: gli aerei tornano <em>se</em> non sono stati colpiti nei punti vitali, il case study si pubblica <em>se</em> la strategia ha funzionato.</p>



<h2 class="wp-block-heading" id="case-study">Un esempio: 1.000 siti e 55 case study</h2>



<p class="wp-block-paragraph">Costruiamo l&#8217;esempio per simulazione, così conosciamo la verità. Mille siti fanno la stessa modifica, per esempio riscrivono i title delle pagine principali, e <strong>la modifica non serve a niente</strong>: la variazione di traffico nei tre mesi successivi è puro rumore, la normale oscillazione di un sito da un trimestre all&#8217;altro (la simuliamo con una deviazione standard del 15% circa). <br>Poi facciamo quello che succede nella realtà: scrive il case study solo chi ha visto almeno un +25%.</p>



<p class="wp-block-paragraph">Genero i dati in R:</p>



<pre class="wp-block-code"><code>set.seed(42)
n_siti &lt;- 1000
var_trim1 &lt;- exp(rnorm(n_siti, 0, 0.15)) - 1   # variazione del traffico in tre mesi
pubblica  &lt;- var_trim1 &gt;= 0.25                  # chi racconta il successo</code></pre>



<p class="wp-block-paragraph">(Il rumore è generato sulla scala logaritmica e poi riportato in percentuale: così un sito può perdere al massimo il 100%, ma guadagnare anche più del 100%, come accade al traffico vero.)</p>



<p class="wp-block-paragraph">Calcoliamo prima la verità, cioè la media su tutti i mille siti, e la quota di quelli che sono migliorati:</p>



<pre class="wp-block-code"><code>round(100 * mean(var_trim1), 1)       # variazione media, in %
# [1] 0.7
round(100 * mean(var_trim1 &gt; 0), 1)   # % di siti migliorati
# [1] 48.5</code></pre>



<p class="wp-block-paragraph">Come ci si aspetta da una modifica inutile, la media è praticamente zero (+0,7%) e <strong>più della metà dei siti è peggiorata</strong>. Ora guardiamo il mondo come lo vediamo noi, cioè attraverso i case study pubblicati:</p>



<pre class="wp-block-code"><code>sum(pubblica)                              # quanti case study
# [1] 55
round(100 * mean(var_trim1[pubblica]), 1)  # variazione media raccontata
# [1] 34.9
round(100 * range(var_trim1[pubblica]), 1)
# [1] 25.1 68.9</code></pre>



<p class="wp-block-paragraph">Escono 55 case study, con una crescita media del <strong>+34,9%</strong> e punte fino al +68,9%. Il 100% dei case study racconta un successo, mentre fra tutti i siti i successi sono stati meno della metà. <br>Il lettore che li raccoglie tutti e ne fa la media, scrupolosamente, conclude che riscrivere i title vale un +35% di traffico. <strong>Ogni singolo numero è vero, e la conclusione è completamente falsa.</strong></p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/survivorship-case-study.png" alt="La variazione di traffico dei 1.000 siti che hanno fatto la stessa modifica inutile. La distribuzione è centrata sullo zero; i 55 siti oltre il +25% (evidenziati) sono gli unici che pubblicano un case study, e la media che leggiamo è la loro."/><figcaption class="wp-element-caption">La variazione di traffico dei 1.000 siti che hanno fatto la stessa modifica inutile. La distribuzione è centrata sullo zero; i 55 siti oltre il +25% (evidenziati) sono gli unici che pubblicano un case study, e la media che leggiamo è la loro.</figcaption></figure>



<p class="wp-block-paragraph">C&#8217;è un modo semplice per smascherare l&#8217;inganno: chiedere ai 55 vincitori come è andato il trimestre successivo. Se la ricetta funzionasse, dovrebbero continuare a crescere. Simulo un altro trimestre di rumore:</p>



<pre class="wp-block-code"><code>var_trim2 &lt;- exp(rnorm(n_siti, 0, 0.15)) - 1   # il trimestre dopo
round(100 * mean(var_trim2[pubblica]), 1)      # i 55 vincitori, tre mesi dopo
# [1] 2.2
sum(var_trim2[pubblica] &gt;= 0.25)               # quanti si ripetono
# [1] 4</code></pre>



<p class="wp-block-paragraph">I vincitori tornano sulla media di tutti (+2,2%), e solo 4 su 55 replicano il successo, più o meno quanti ci si aspetterebbe dal caso. È la <a href="https://www.gironi.it/blog/regressione-verso-la-media/">regressione verso la media</a>, che abbiamo già incontrato: chi è stato selezionato per un risultato estremo, alla misura successiva, tende a tornare nella norma. <strong>Survivorship bias e regressione verso la media sono due facce dello stesso problema: abbiamo scelto i casi guardando l&#8217;esito.</strong></p>



<h2 class="wp-block-heading" id="cimitero">Chi non torna non racconta</h2>



<p class="wp-block-paragraph">L&#8217;idea è antichissima. Cicerone racconta che al filosofo Diagora di Melo, a Samotracia, mostrarono le tavolette votive dipinte da chi era sopravvissuto a un naufragio dopo aver pregato gli dèi: non era forse la prova che le preghiere funzionano? Diagora rispose chiedendo dove fossero i ritratti di quelli che avevano pregato ed erano annegati. <br>Nassim Taleb chiama questo insieme di dati invisibili il <em>cimitero silenzioso</em>: i casi che non ce l&#8217;hanno fatta non scrivono, non dipingono tavolette, non tornano alla base. E proprio perché sono silenziosi, ci dimentichiamo che esistono.</p>



<p class="wp-block-paragraph">Il punto delicato è che il cimitero silenzioso non si vede <em>per costruzione</em>. Non è un dato mancante per distrazione, che con un po&#8217; di cura si può recuperare: è il meccanismo stesso che produce i dati a escluderlo. Nessun case study ci dirà mai quanti altri hanno provato la stessa cosa.</p>



<h2 class="wp-block-heading" id="potatura">Un secondo esempio: la potatura dei contenuti</h2>



<p class="wp-block-paragraph">Il survivorship bias non ha bisogno di LinkedIn: può nascere dentro i nostri stessi dati. Prendiamo un blog che pubblica 100 articoli all&#8217;anno dal 2020 al 2025. Ogni anno fa un audit dei contenuti, quella che in gergo si chiama <em>content pruning</em>: gli articoli che hanno almeno un anno e portano meno di 30 visite al mese vengono rimossi (o messi in noindex) con probabilità 1/2, perché qualcuno li giudica recuperabili e qualcun altro no. <br>Nella simulazione, e questo è il punto, <strong>il traffico di un articolo non dipende in alcun modo dall&#8217;anno in cui è stato scritto</strong>.</p>



<p class="wp-block-paragraph">Costruisco il blog in R:</p>



<pre class="wp-block-code"><code>set.seed(2025)
anni &lt;- 2020:2025
post &lt;- data.frame(anno = rep(anni, each = 100))
post$visite &lt;- round(rlnorm(nrow(post), log(40), 1.1))   # visite al mese
post$online &lt;- TRUE
for (audit in 2021:2025) {
  candidati &lt;- post$online &amp; post$anno &lt; audit &amp; post$visite &lt; 30
  post$online[candidati &amp; runif(nrow(post)) &lt; 0.5] &lt;- FALSE
}</code></pre>



<p class="wp-block-paragraph">Il traffico segue una distribuzione log-normale, come accade di solito: molti articoli con poche visite, pochi articoli con moltissime. Proprio per questo come misura riassuntiva usiamo la mediana e non la media, che i pochi articoli di grande successo trascinerebbero verso l&#8217;alto.</p>



<p class="wp-block-paragraph">Un anno dopo l&#8217;ultimo audit, qualcuno si chiede se convenga puntare sui contenuti <em>evergreen</em>. Estrae dal CMS gli articoli online e calcola le visite mediane per anno di pubblicazione:</p>



<pre class="wp-block-code"><code>online &lt;- post[post$online, ]
tapply(online$visite, online$anno, median)
#  2020 2021 2022 2023 2024 2025
#  78.0 78.5 73.0 54.0 54.0 41.5</code></pre>



<p class="wp-block-paragraph">Il risultato sembra netto: gli articoli del 2020 portano quasi il doppio delle visite di quelli del 2025. «I contenuti invecchiano bene, il traffico si accumula col tempo». Ora guardiamo la stessa tabella su <strong>tutti</strong> gli articoli mai pubblicati, compresi quelli rimossi:</p>



<pre class="wp-block-code"><code>tapply(post$visite, post$anno, median)
#  2020 2021 2022 2023 2024 2025
#  38.0 38.0 43.0 38.0 40.5 41.5</code></pre>



<p class="wp-block-paragraph">La crescita sparisce: l&#8217;anno di pubblicazione non conta niente, come avevamo stabilito. L&#8217;effetto «età» era interamente prodotto dagli audit. Gli articoli del 2020 hanno subìto cinque potature e ne è rimasto il 62%, i più forti; quelli del 2025 non ne hanno subita nessuna e sono tutti lì, deboli compresi. <strong>Confrontando gruppi che hanno subìto selezioni diverse, abbiamo misurato la selezione, non i contenuti.</strong></p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/survivorship-potatura.png" alt="Visite mensili mediane per anno di pubblicazione. Su tutti gli articoli (linea tratteggiata) il traffico non dipende dall'anno; sugli articoli ancora online (linea continua) sembra crescere con l'età, perché i vecchi articoli deboli sono stati rimossi e i nuovi no. In basso, la quota di articoli sopravvissuti agli audit."/><figcaption class="wp-element-caption">Visite mensili mediane per anno di pubblicazione. Su tutti gli articoli (linea tratteggiata) il traffico non dipende dall&#8217;anno; sugli articoli ancora online (linea continua) sembra crescere con l&#8217;età, perché i vecchi articoli deboli sono stati rimossi e i nuovi no. In basso, la quota di articoli sopravvissuti agli audit.</figcaption></figure>



<p class="wp-block-paragraph">C&#8217;è un&#8217;ironia sottile: proprio chi lavora bene, e fa pulizia regolarmente, si costruisce un archivio che mente. E la menzogna è comoda, perché conferma che i contenuti vecchi sono preziosi e che la potatura ha funzionato.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: <strong>prima di guardare un risultato, chiediamoci chi è uscito dal dataset, e perché.</strong> <br>Se l&#8217;uscita dipende dall&#8217;esito che stiamo misurando (il traffico, le conversioni, il successo di una strategia), qualunque media calcolata su chi è rimasto è distorta, e nessun test statistico, per quanto raffinato, se ne accorgerà: il test lavora sui dati che gli diamo, non su quelli che mancano.</p>
</div></div>



<h2 class="wp-block-heading" id="dove">Dove si nasconde il survivorship bias nella SEO</h2>



<p class="wp-block-paragraph">Una volta imparato a riconoscerlo, lo si trova dappertutto. Qualche esempio dalla pratica quotidiana:</p>



<ul class="wp-block-list"><li><strong>Gli studi sui fattori di ranking.</strong> Periodicamente esce lo studio che misura le pagine in prima posizione: tante parole in media, tante immagini, tanti backlink. Ma lo studio guarda solo le prime dieci posizioni: quante pagine con le stesse caratteristiche stanno in quarta pagina? Senza di loro non sappiamo se quelle caratteristiche distinguono i vincitori o sono semplicemente comuni a tutti. (È anche un problema di <a href="https://www.gironi.it/blog/correlazione/">correlazione scambiata per causazione</a>, ma il primo vizio è nel campione.)</li><li><strong>«Cosa hanno in comune i siti che hanno superato il core update».</strong> Senza guardare i siti con le stesse caratteristiche che invece sono crollati, l&#8217;elenco delle virtù dei sopravvissuti non dimostra niente.</li><li><strong>Search Console.</strong> Mostra le pagine e le query che hanno ottenuto impressioni: le pagine che Google non mostra mai non compaiono, e le query anonimizzate per privacy nemmeno. Le tabelle dell&#8217;interfaccia, inoltre, si fermano alle prime mille righe. Analizzare «le pagine del sito» partendo da lì significa analizzare le pagine che già funzionano.</li><li><strong>Le best practice degli A/B test.</strong> Le raccolte di «test vincenti» raccontano le varianti che hanno vinto, non le dieci identiche che hanno perso altrove. E i vincitori di un <a href="https://www.gironi.it/blog/ab-testing/">A/B test</a> fermato al momento giusto, come abbiamo visto parlando di <a href="https://www.gironi.it/blog/peeking-problem/">peeking</a>, sono sopravvissuti doppiamente selezionati.</li><li><strong>I portfolio e le testimonianze.</strong> Un&#8217;agenzia mostra i clienti cresciuti, non quelli persi; un corso mostra gli studenti che hanno trovato lavoro. Non è disonestà: è il modo in cui funziona la vetrina. Ma la vetrina non è il magazzino.</li><li><strong>I dataset dei tool.</strong> I domini scaduti, i siti chiusi, le pagine eliminate escono silenziosamente dagli indici di backlink e di visibilità. Un trend storico calcolato sui siti attivi oggi è un trend dei sopravvissuti.</li></ul>



<h2 class="wp-block-heading" id="difendersi">Come difendersi?</h2>



<p class="wp-block-paragraph">Non esiste una formula che corregga il survivorship bias dopo il fatto: se i casi scartati sono persi, nessun calcolo li restituisce. Esistono però abitudini che lo prevengono.</p>



<p class="wp-block-paragraph"><strong>Definire il gruppo prima di conoscere l&#8217;esito.</strong> È la lezione del secondo esempio. Se vogliamo sapere come rendono gli articoli del 2020, il gruppo giusto è <em>tutti gli articoli pubblicati nel 2020</em>, non quelli ancora online. Gli epidemiologi lo chiamano studio di coorte: si segue tutto il gruppo dall&#8217;inizio, morti compresi. Nella simulazione, è esattamente la differenza fra la linea tratteggiata e quella continua.</p>



<p class="wp-block-paragraph"><strong>Chiedere il denominatore.</strong> Davanti a un successo, la domanda è sempre «su quanti tentativi?». Cinquantacinque case study entusiasti su mille tentativi raccontano una storia molto diversa da cinquantacinque su sessanta. Se il denominatore non è noto, la conclusione onesta è che non sappiamo.</p>



<p class="wp-block-paragraph"><strong>Conservare i morti.</strong> Nel nostro lavoro il cimitero silenzioso si può, in parte, conservare: le vecchie sitemap, l&#8217;elenco degli URL rimossi o reindirizzati con la data, gli export completi di Search Console scaricati regolarmente, i risultati di <em>tutti</em> gli A/B test, non solo dei vincenti. Costa poco, e trasforma un archivio che mente in uno che si può interrogare.</p>



<p class="wp-block-paragraph"><strong>Confrontare con chi non ha fatto niente.</strong> Il modo migliore per sapere se una modifica funziona è confrontare le pagine modificate con pagine simili lasciate com&#8217;erano, scelte prima di guardare i risultati: è l&#8217;idea dello <a href="https://www.gironi.it/blog/split-test-seo/">split test SEO</a>. Il gruppo di controllo è, in un certo senso, il cimitero che ci costruiamo apposta.</p>



<p class="wp-block-paragraph"><strong>Diffidare degli effetti grandi selezionati per successo.</strong> Quando un risultato ci arriva perché è stato eccezionale, aspettiamoci che alla prova successiva sia meno eccezionale. Non è pessimismo: è quello che abbiamo visto accadere ai 55 vincitori.</p>



<h2 class="wp-block-heading" id="domande-frequenti">Domande frequenti</h2>



<h3 class="wp-block-heading">Qual è l&#8217;esempio più famoso di survivorship bias?</h3>



<p class="wp-block-paragraph">Quello degli aerei della Seconda guerra mondiale analizzati dal matematico Abraham Wald. Gli aerei che tornavano avevano pochi colpi sui motori, e la tentazione era corazzare le parti più colpite. Wald capì che i colpi sui motori mancavano perché gli aerei colpiti lì non tornavano: la corazza andava messa proprio dove, sui sopravvissuti, i fori non c&#8217;erano.</p>



<h3 class="wp-block-heading">Survivorship bias e bias di selezione sono la stessa cosa?</h3>



<p class="wp-block-paragraph">Il survivorship bias è un caso particolare del bias di selezione. Il bias di selezione si ha ogni volta che il campione analizzato non rappresenta la popolazione di interesse; nel survivorship bias il campione è formato da chi ha superato una selezione, e la selezione dipende di solito dall&#8217;esito stesso, come il successo o la permanenza nel tempo.</p>



<h3 class="wp-block-heading">Come si dice survivorship bias in italiano?</h3>



<p class="wp-block-paragraph">Si usano <em>bias del sopravvissuto</em>, <em>bias di sopravvivenza</em> o <em>distorsione da sopravvivenza</em>. Il termine inglese resta il più diffuso, anche nei testi italiani.</p>



<h3 class="wp-block-heading">Perché i case study di successo sono poco affidabili?</h3>



<p class="wp-block-paragraph">Non perché siano falsi, ma perché sono selezionati: si pubblicano quando la strategia ha funzionato, e restano nel cassetto quando non ha funzionato. Una raccolta di case study mostra quindi solo la coda fortunata dei risultati, e la media dei successi raccontati può essere molto lontana dall&#8217;effetto reale, che può anche essere nullo.</p>



<h3 class="wp-block-heading">Si può correggere il survivorship bias dopo aver raccolto i dati?</h3>



<p class="wp-block-paragraph">Solo se si recuperano le informazioni sui casi esclusi, o se si conosce con precisione il meccanismo che li ha esclusi e lo si può modellare. Se gli esclusi sono semplicemente spariti, nessuna tecnica statistica li restituisce: per questo conviene prevenirlo, definendo il gruppo da studiare prima di conoscere l&#8217;esito e conservando i dati dei casi falliti.</p>



<script type="application/ld+json">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "Qual è l'esempio più famoso di survivorship bias?", "acceptedAnswer": {"@type": "Answer", "text": "Quello degli aerei della Seconda guerra mondiale analizzati dal matematico Abraham Wald. Gli aerei che tornavano avevano pochi colpi sui motori, e la tentazione era corazzare le parti più colpite. Wald capì che i colpi sui motori mancavano perché gli aerei colpiti lì non tornavano: la corazza andava messa proprio dove, sui sopravvissuti, i fori non c'erano."}}, {"@type": "Question", "name": "Survivorship bias e bias di selezione sono la stessa cosa?", "acceptedAnswer": {"@type": "Answer", "text": "Il survivorship bias è un caso particolare del bias di selezione. Il bias di selezione si ha ogni volta che il campione analizzato non rappresenta la popolazione di interesse; nel survivorship bias il campione è formato da chi ha superato una selezione, e la selezione dipende di solito dall'esito stesso, come il successo o la permanenza nel tempo."}}, {"@type": "Question", "name": "Come si dice survivorship bias in italiano?", "acceptedAnswer": {"@type": "Answer", "text": "Si usano bias del sopravvissuto, bias di sopravvivenza o distorsione da sopravvivenza. Il termine inglese resta il più diffuso, anche nei testi italiani."}}, {"@type": "Question", "name": "Perché i case study di successo sono poco affidabili?", "acceptedAnswer": {"@type": "Answer", "text": "Non perché siano falsi, ma perché sono selezionati: si pubblicano quando la strategia ha funzionato, e restano nel cassetto quando non ha funzionato. Una raccolta di case study mostra quindi solo la coda fortunata dei risultati, e la media dei successi raccontati può essere molto lontana dall'effetto reale, che può anche essere nullo."}}, {"@type": "Question", "name": "Si può correggere il survivorship bias dopo aver raccolto i dati?", "acceptedAnswer": {"@type": "Answer", "text": "Solo se si recuperano le informazioni sui casi esclusi, o se si conosce con precisione il meccanismo che li ha esclusi e lo si può modellare. Se gli esclusi sono semplicemente spariti, nessuna tecnica statistica li restituisce: per questo conviene prevenirlo, definendo il gruppo da studiare prima di conoscere l'esito e conservando i dati dei casi falliti."}}]}</script>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Il codice dell&#8217;articolo si presta a qualche esperimento.</p>



<ul class="wp-block-list"><li>Si abbassa la soglia di pubblicazione dal +25% al +10% (<code>pubblica &lt;- var_trim1 &gt;= 0.10</code>): quanti case study escono ora, e quanto vale la loro media? La distorsione si riduce, ma sparisce?</li><li>Si dà alla modifica un effetto vero ma piccolo, del +3%, aggiungendo <code>log(1.03)</code> alla media di <code>rnorm()</code>. Di quante volte i case study pubblicati esagerano l&#8217;effetto reale?</li><li>Nella potatura si porta la probabilità di rimozione da 1/2 a 1 (<code>runif(nrow(post)) &lt; 1</code>): come cambia la tabella degli articoli online? E se la soglia passa da 30 a 60 visite?</li></ul>



<p class="wp-block-paragraph">L&#8217;ultimo esperimento è il più istruttivo: più la potatura è severa, più l&#8217;archivio «dimostra» che i contenuti vecchi sono migliori, senza che sia cambiato niente nei contenuti.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Abbiamo visto che guardare solo chi è sopravvissuto alla selezione distorce le medie, anche quando ogni dato è vero. C&#8217;è una trappola parente, che ci aspetta ogni volta che una notizia ci sembra molto più probabile di quanto sia: dimenticare quanto è raro, in partenza, l&#8217;evento che stiamo cercando. Un test accurato al 99%, se l&#8217;evento che cerca è abbastanza raro, può sbagliare la maggior parte delle volte che dà l&#8217;allarme. È la <em>base rate fallacy</em>, e sarà la prossima tappa del nostro viaggio tra le trappole dei dati.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Sul ruolo del caso nel successo e sui sopravvissuti che scambiamo per vincitori, consiglio <a href="https://www.amazon.it/dp/8842820237?tag=consulenzeinf-21&amp;ascsubtag=survivorship-bias" rel="nofollow sponsored noopener" target="_blank"><em>Giocati dal caso</em></a> di Nassim Nicholas Taleb: un libro brillante e polemico, nato nel mondo della finanza ma perfetto per chi deve giudicare strategie, case study e consulenti a partire dai loro risultati.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/survivorship-bias/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Il p-value e i suoi malintesi: cosa significa davvero p &lt; 0,05</title>
		<link>https://www.gironi.it/blog/p-value/</link>
					<comments>https://www.gironi.it/blog/p-value/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 08:32:23 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=4519</guid>

					<description><![CDATA[«Il nuovo titolo ha un p-value di 0,03: c&#8217;è il 97% di probabilità che funzioni.» È una frase che si legge nei report, nei case study, a volte perfino nei manuali. Ed è sbagliata. Non di poco, non per pedanteria: è sbagliata in un modo che può farci prendere metà delle decisioni sulla base di &#8230; <a href="https://www.gironi.it/blog/p-value/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Il p-value e i suoi malintesi: cosa significa davvero p < 0,05"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">«Il nuovo titolo ha un p-value di 0,03: c&#8217;è il 97% di probabilità che funzioni.» È una frase che si legge nei report, nei case study, a volte perfino nei manuali. Ed è sbagliata. Non di poco, non per pedanteria: è sbagliata in un modo che può farci prendere metà delle decisioni sulla base di risultati che non esistono.</p>



<p class="wp-block-paragraph">Su questo blog il p-value compare in decine di articoli, dal <a href="https://www.gironi.it/blog/il-test-delle-ipotesi/">test delle ipotesi</a> all&#8217;<a href="https://www.gironi.it/blog/ab-testing/">A/B testing</a>, dalla regressione ai <a href="https://www.gironi.it/blog/confronti-multipli/">confronti multipli</a>. È il numero più usato della statistica applicata, e probabilmente il più frainteso. Vale la pena fermarsi una volta su che cosa dice, e soprattutto su che cosa <strong>non</strong> dice.</p>



<span id="more-4519"></span>



<p class="wp-block-paragraph"><strong>In sintesi:</strong> il <strong>p-value</strong> è la probabilità di osservare dati almeno così estremi come quelli ottenuti, <strong>supponendo che l&#8217;ipotesi nulla sia vera</strong> (cioè che l&#8217;effetto non esista). Non è la probabilità che l&#8217;ipotesi nulla sia vera, né la probabilità che il risultato sia dovuto al caso. Un p-value piccolo non dice che l&#8217;effetto è grande, e un p-value sopra 0,05 non dice che l&#8217;effetto non c&#8217;è. La soglia di 0,05 è una convenzione, non un confine naturale. Per decidere servono anche la dimensione dell&#8217;effetto, l&#8217;intervallo di confidenza e una stima di quante delle idee che testiamo funzionano davvero.</p>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#definizione">Cos&#8217;è il p-value, in una frase</a></li><li><a href="#probabilita">Malinteso 1: «c&#8217;è il 97% di probabilità che funzioni»</a></li><li><a href="#effetto">Malinteso 2: «un p piccolo vuol dire un effetto grande»</a></li><li><a href="#assenza">Malinteso 3: «p sopra 0,05, quindi non c&#8217;è nessun effetto»</a></li><li><a href="#soglia">Malinteso 4: «0,05 è il confine fra vero e falso»</a></li><li><a href="#cosa-riportare">Cosa riportare, allora?</a></li><li><a href="#domande-frequenti">Domande frequenti</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="definizione">Cos&#8217;è il p-value, in una frase</h2>



<p class="wp-block-paragraph">Partiamo da un A/B test su due titoli di annuncio: 4.000 impressioni ciascuno, 200 clic per il titolo A (CTR 5,0%) e 240 per il titolo B (CTR 6,0%). Il test sulle due proporzioni restituisce p = 0,0498.</p>



<p class="wp-block-paragraph">Per leggere quel numero bisogna fare un piccolo esercizio di immaginazione. <strong>Supponiamo che i due titoli siano identici</strong>, che la differenza di CTR non esista e tutto dipenda dal caso dei clic. È l&#8217;ipotesi nulla. In quel mondo immaginario, con quale probabilità vedremmo una differenza grande almeno quanto quella osservata, 40 clic su 4.000? La risposta è il p-value: circa il 5%.</p>



<p class="wp-block-paragraph">In formula, con H₀ l&#8217;ipotesi nulla:</p>



<p class="wp-block-paragraph">\(p = P(\text{dati almeno così estremi} \mid H_0 \text{ vera})\)</p>



<p class="wp-block-paragraph">La barra verticale si legge «supponendo che»: tutto il calcolo avviene <em>dentro</em> l&#8217;ipotesi nulla. <strong>Il p-value misura quanto i dati sono sorprendenti se l&#8217;effetto non esiste.</strong> Non dice niente, direttamente, su quanto sia probabile che l&#8217;effetto esista. Ed è proprio da qui che nascono quasi tutti i malintesi.</p>



<p class="wp-block-paragraph">Una conseguenza che pochi conoscono, ma che aiuta a capire tutto il resto: quando l&#8217;ipotesi nulla è vera, <strong>il p-value è un numero a caso fra 0 e 1, con distribuzione uniforme</strong>. Un p di 0,03 è raro esattamente quanto un p di 0,73. Lo verifichiamo simulando 10.000 A/B test in cui B non cambia niente, e altri 10.000 in cui B porta il CTR dal 5% al 6%:</p>



<pre class="wp-block-code"><code>un_test &lt;- function(ctr_b, n = 4000, ctr_a = 0.05) {
  clic_a &lt;- rbinom(1, n, ctr_a)
  clic_b &lt;- rbinom(1, n, ctr_b)
  prop.test(c(clic_a, clic_b), c(n, n), correct = FALSE)$p.value
}

set.seed(1)
p_nulla &lt;- replicate(10000, un_test(0.05))     # B non cambia niente
p_effetto &lt;- replicate(10000, un_test(0.06))   # B funziona davvero (+20%)

mean(p_nulla &lt; 0.05)
# [1] 0.0481
quantile(p_nulla, c(0.25, 0.50, 0.75))
#       25%       50%       75%
# 0.2483948 0.4970851 0.7524771
mean(p_effetto &lt; 0.05)
# [1] 0.501</code></pre>



<p class="wp-block-paragraph">Senza effetto, il 4,8% dei test scende sotto 0,05 (il 5% promesso, a meno del caso) e i quartili cadono a 0,25, 0,50 e 0,75: la distribuzione è piatta. Con l&#8217;effetto vero, i p-value si ammassano vicino a zero, ma <strong>solo la metà scende sotto 0,05</strong>: con 4.000 impressioni per variante, la <a href="https://www.gironi.it/blog/effect-size-e-power-analysis/">potenza</a> del test è del 50%.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/p-value-distribuzione.png" alt="La distribuzione dei p-value di 10.000 A/B test. Se B non cambia niente (sinistra) i p-value sono uniformi fra 0 e 1, e circa il 5% cade sotto 0,05 per puro caso. Se B funziona davvero (destra) si ammassano vicino a zero, ma con una potenza del 50% la metà resta sopra la soglia."/><figcaption class="wp-element-caption">La distribuzione dei p-value di 10.000 A/B test. Se B non cambia niente (sinistra) i p-value sono uniformi fra 0 e 1, e circa il 5% cade sotto 0,05 per puro caso. Se B funziona davvero (destra) si ammassano vicino a zero, ma con una potenza del 50% la metà resta sopra la soglia.</figcaption></figure>



<h2 class="wp-block-heading" id="probabilita">Malinteso 1: «c&#8217;è il 97% di probabilità che funzioni»</h2>



<p class="wp-block-paragraph">È il malinteso più diffuso, e il più costoso. Un p di 0,03 non vuol dire che c&#8217;è il 3% di probabilità che l&#8217;ipotesi nulla sia vera, né il 97% che l&#8217;effetto esista. Il p-value è la probabilità dei dati supponendo H₀, non la probabilità di H₀ dati i dati. Sembra un gioco di parole, ma le due probabilità possono essere lontanissime.</p>



<p class="wp-block-paragraph">Un esempio fuori dalla statistica. La probabilità che una persona sia italiana, sapendo che è il Presidente della Repubblica, è altissima. La probabilità che sia il Presidente della Repubblica, sapendo che è italiana, è di circa una su sessanta milioni. Invertire il «sapendo che» cambia tutto.</p>



<p class="wp-block-paragraph">Per passare dal p-value alla domanda che ci interessa davvero (questa idea funziona?) manca un ingrediente: <strong>quante delle idee che mettiamo alla prova funzionano in partenza</strong>. Facciamo un esempio realistico. Testiamo 1.000 idee (titoli, meta description, call to action) e solo una su dieci funziona davvero; le altre nove sono neutre. Lo simulo in R con gli stessi test di prima:</p>



<pre class="wp-block-code"><code>set.seed(5)
funziona &lt;- seq_len(1000) &lt;= 100        # le idee buone sono 100 su 1.000
p_idee &lt;- sapply(funziona, function(f) un_test(ifelse(f, 0.06, 0.05)))

table(significativo = p_idee &lt; 0.05, funziona)

#              funziona
# significativo FALSE TRUE
#         FALSE   853   50
#         TRUE     47   50</code></pre>



<p class="wp-block-paragraph">Su 1.000 test, 97 risultano «significativi». Cinquanta sono idee che funzionano davvero; <strong>47 sono idee neutre che hanno superato la soglia per caso</strong>. Fra i nostri «vincitori», quasi uno su due è un falso allarme, eppure ciascuno di loro aveva un p-value sotto 0,05.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/p-value-mille-idee.png" alt="Mille idee testate, una su dieci funziona davvero. Ogni quadratino è un test. I vincitori sono i quadratini blu (scoperte vere) e arancioni (falsi allarmi): sono quasi lo stesso numero. Le idee buone perse perché il test non le ha viste (celeste) sono tante quante quelle trovate."/><figcaption class="wp-element-caption">Mille idee testate, una su dieci funziona davvero. Ogni quadratino è un test. I vincitori sono i quadratini blu (scoperte vere) e arancioni (falsi allarmi): sono quasi lo stesso numero. Le idee buone perse perché il test non le ha viste (celeste) sono tante quante quelle trovate.</figcaption></figure>



<p class="wp-block-paragraph">Il conto si può fare anche senza simulazione. Se π è la quota di idee che funzionano, α la soglia e la potenza la probabilità di accorgersi di un&#8217;idea buona, la quota di falsi allarmi fra i risultati significativi è:</p>



<p class="wp-block-paragraph">\(\text{quota di falsi allarmi} = \frac{\alpha \, (1 &#8211; \pi)}{\alpha \, (1 &#8211; \pi) + \text{potenza} \cdot \pi}\)</p>



<p class="wp-block-paragraph">Il numeratore sono le idee neutre che passano per caso (il 5% delle 900); al denominatore si aggiungono le idee buone che il test riconosce (il 50% delle 100). Con i nostri numeri: 45 / (45 + 50) = <strong>47%</strong>. La tabella mostra come cambia la quota al variare delle due incognite:</p>



<figure class="wp-block-table"><table><thead><tr><th>Idee che funzionano (π)</th><th>Potenza 50%</th><th>Potenza 80%</th></tr></thead><tbody><tr><td>10%</td><td>47,4%</td><td>36,0%</td></tr><tr><td>25%</td><td>23,1%</td><td>15,8%</td></tr><tr><td>50%</td><td>9,1%</td><td>5,9%</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>La stessa soglia del 5% produce un 6% di falsi allarmi o quasi la metà, a seconda di quanto sono buone le idee che testiamo e di quanti dati abbiamo.</strong> È lo stesso meccanismo della <em>base rate fallacy</em> nei test medici: un test accurato su una malattia rara produce comunque una valanga di falsi positivi.</p>



<h2 class="wp-block-heading" id="effetto">Malinteso 2: «un p piccolo vuol dire un effetto grande»</h2>



<p class="wp-block-paragraph">Il p-value mescola due cose: quanto è grande l&#8217;effetto e quanti dati abbiamo. Con dati abbondanti, anche un effetto minuscolo produce un p-value minuscolo.</p>



<p class="wp-block-paragraph">Search Console ce ne offre l&#8217;occasione tutti i giorni. Due sezioni del sito, 10 milioni di impressioni ciascuna, CTR del 5,00% contro il 5,05%: una differenza di cinque centesimi di punto, l&#8217;1% in termini relativi. Lo calcolo in R:</p>



<pre class="wp-block-code"><code>n &lt;- 1e7
prop.test(c(n * 0.05, n * 0.0505), c(n, n), correct = FALSE)$p.value
# [1] 3.091392e-07</code></pre>



<p class="wp-block-paragraph">Un p-value di 0,0000003: «altamente significativo». Ma l&#8217;effetto vale cinque clic ogni diecimila impressioni, e quasi certamente non giustifica nessuna decisione. <strong>Significativo, in statistica, vuol dire «difficilmente dovuto al caso», non «importante».</strong> Per l&#8217;importanza serve guardare la dimensione dell&#8217;effetto, il tema dell&#8217;articolo su <a href="https://www.gironi.it/blog/effect-size-e-power-analysis/">effect size e power analysis</a>.</p>



<h2 class="wp-block-heading" id="assenza">Malinteso 3: «p sopra 0,05, quindi non c&#8217;è nessun effetto»</h2>



<p class="wp-block-paragraph">È il malinteso speculare, e altrettanto pericoloso. Torniamo alla figura con le due distribuzioni: quando B funziona davvero, con 4.000 impressioni per variante, metà dei test resta sopra 0,05. Un p-value «non significativo» vuol dire che <strong>i dati non bastano a escludere il caso</strong>, non che l&#8217;effetto è stato escluso.</p>



<p class="wp-block-paragraph">Facciamo un esempio. Titolo A: 200 clic su 4.000. Titolo B: 222 clic su 4.000, un CTR più alto dell&#8217;11%. Lo verifico in R, guardando anche l&#8217;intervallo di confidenza:</p>



<pre class="wp-block-code"><code>t &lt;- prop.test(c(222, 200), c(4000, 4000), correct = FALSE)
t$p.value
# [1] 0.271176
t$conf.int     # differenza di CTR, B meno A
# [1] -0.004295887  0.015295887</code></pre>



<p class="wp-block-paragraph">Il p-value è 0,27, ben sopra la soglia. Ma l&#8217;<a href="https://www.gironi.it/blog/intervalli-di-confidenza/">intervallo di confidenza</a> della differenza va da −0,43 a +1,53 punti di CTR: in termini relativi, da un calo dell&#8217;8,6% a un <strong>aumento del 30,6%</strong>. I dati sono compatibili con un titolo leggermente peggiore e con uno nettamente migliore. Concludere «il titolo B non serve» sarebbe come dire che una partita non ancora giocata è finita in pareggio.</p>



<p class="wp-block-paragraph"><strong>L&#8217;assenza di prove non è la prova di un&#8217;assenza.</strong> Il test ci sta dicendo che servono più dati, non che l&#8217;idea è da buttare.</p>



<h2 class="wp-block-heading" id="soglia">Malinteso 4: «0,05 è il confine fra vero e falso»</h2>



<p class="wp-block-paragraph">La soglia di 0,05 non viene dalla natura: viene da Ronald Fisher, che negli anni Venti la propose come riferimento comodo, uno su venti, per segnalare i risultati che meritavano un&#8217;occhiata più attenta. Nel tempo è diventata un confine burocratico, con un effetto curioso: un risultato con p = 0,049 viene pubblicato, comunicato, applicato; uno con p = 0,051 finisce nel cassetto.</p>



<p class="wp-block-paragraph">Quanto sono diversi, nei dati, quei due risultati? Nel nostro A/B test con 200 clic per il titolo A, se il titolo B ne prende 239 il p-value è 0,0555; se ne prende 240 è 0,0498. <strong>Un clic su ottomila separa il «non significativo» dal «significativo».</strong> Nessuna persona ragionevole cambierebbe idea per un clic, eppure la soglia ci chiede di farlo.</p>



<p class="wp-block-paragraph">Nel 2016 l&#8217;American Statistical Association ha pubblicato una dichiarazione ufficiale sul p-value, la prima volta in 177 anni di storia in cui prendeva posizione su un singolo aspetto della pratica statistica. Contiene sei principi. Tre dicono esattamente quello che abbiamo visto: il p-value non misura la probabilità che l&#8217;ipotesi sia vera; non misura la dimensione di un effetto; le conclusioni non dovrebbero basarsi soltanto sul superamento di una soglia.</p>



<p class="wp-block-paragraph">C&#8217;è poi un quarto modo di sbagliare, il più subdolo, perché non riguarda la lettura del p-value ma il modo in cui ci si arriva. Il p-value vale <strong>per un test deciso prima di guardare i dati</strong>. Se proviamo tante metriche, tanti segmenti, tanti momenti di stop, e teniamo il risultato che passa la soglia, il 5% non vale più. È quello che abbiamo visto parlando di <a href="https://www.gironi.it/blog/confronti-multipli/">confronti multipli</a> e di <a href="https://www.gironi.it/blog/peeking-problem/">peeking</a>, e ha anche un nome poco lusinghiero: <em>p-hacking</em>.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: <strong>un p-value sotto 0,05 non è una promessa, è un indizio.</strong> <br>Dice che i dati sarebbero sorprendenti se l&#8217;effetto non esistesse. Quanto quell&#8217;indizio sia convincente dipende da cose che il p-value non contiene: quante delle idee che testiamo funzionano davvero, quanta potenza aveva il test, quanti altri test abbiamo fatto prima di trovarlo.</p>
</div></div>



<h2 class="wp-block-heading" id="cosa-riportare">Cosa riportare, allora?</h2>



<p class="wp-block-paragraph">Il p-value non va buttato: risponde onestamente alla sua domanda, quella sul caso. Va solo smesso di fargli rispondere alle domande che non gli competono. Nella pratica quotidiana, un risultato ben riportato ha tre parti.</p>



<p class="wp-block-paragraph"><strong>La dimensione dell&#8217;effetto</strong>, nell&#8217;unità che conta per il business: punti di CTR, clic al mese, euro. Non «significativo», ma «+0,8 punti di CTR, circa 1.200 clic in più al mese».</p>



<p class="wp-block-paragraph"><strong>L&#8217;intervallo di confidenza</strong>, che dice quanto siamo incerti su quella dimensione: «fra +0,1 e +1,5 punti». Contiene tutto ciò che c&#8217;è nel p-value (se esclude lo zero, il test è significativo) e molto di più.</p>



<p class="wp-block-paragraph"><strong>Il contesto del test</strong>: era l&#8217;unico test previsto, o uno dei venti? La potenza era sufficiente? Quanto erano promettenti le idee in partenza?</p>



<p class="wp-block-paragraph">Chi vuole rispondere direttamente alla domanda «con quale probabilità B è migliore di A?» deve cambiare approccio: è il terreno dell&#8217;<a href="https://www.gironi.it/blog/ab-test-bayesiano/">A/B test bayesiano</a>, che a quella domanda risponde, a patto di dichiarare le proprie ipotesi di partenza.</p>



<h2 class="wp-block-heading" id="domande-frequenti">Domande frequenti</h2>



<h3 class="wp-block-heading">Cosa significa un p-value di 0,05?</h3>



<p class="wp-block-paragraph">Significa che, se l&#8217;effetto non esistesse, osserveremmo dati almeno così estremi in circa 5 casi su 100. Non significa che c&#8217;è il 95% di probabilità che l&#8217;effetto esista, né il 5% di probabilità che il risultato sia dovuto al caso.</p>



<h3 class="wp-block-heading">Il p-value è la probabilità che l&#8217;ipotesi nulla sia vera?</h3>



<p class="wp-block-paragraph">No. È la probabilità dei dati supponendo vera l&#8217;ipotesi nulla, non la probabilità dell&#8217;ipotesi nulla dati i dati. Per ottenere la seconda serve sapere anche quanto era plausibile l&#8217;ipotesi prima del test: con idee poco promettenti, anche fra i risultati con p sotto 0,05 i falsi allarmi possono essere quasi la metà.</p>



<h3 class="wp-block-heading">Un p-value molto piccolo indica un effetto importante?</h3>



<p class="wp-block-paragraph">No. Il p-value dipende sia dalla dimensione dell&#8217;effetto sia dalla quantità di dati: con milioni di impressioni, anche una differenza di CTR irrilevante produce un p-value minuscolo. L&#8217;importanza si giudica dalla dimensione dell&#8217;effetto e dal suo intervallo di confidenza.</p>



<h3 class="wp-block-heading">Se il p-value è maggiore di 0,05 l&#8217;effetto non esiste?</h3>



<p class="wp-block-paragraph">Non necessariamente. Vuol dire che i dati non bastano a escludere il caso. Se il test aveva poca potenza, un effetto reale può facilmente produrre un p-value alto: l&#8217;intervallo di confidenza mostra quali effetti sono ancora compatibili con i dati.</p>



<h3 class="wp-block-heading">Perché proprio 0,05?</h3>



<p class="wp-block-paragraph">È una convenzione proposta da Ronald Fisher negli anni Venti come riferimento comodo, una probabilità su venti. Non ha nessun fondamento naturale: un risultato con p = 0,049 e uno con p = 0,051 portano praticamente la stessa evidenza.</p>



<script type="application/ld+json">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "Cosa significa un p-value di 0,05?", "acceptedAnswer": {"@type": "Answer", "text": "Significa che, se l'effetto non esistesse, osserveremmo dati almeno così estremi in circa 5 casi su 100. Non significa che c'è il 95% di probabilità che l'effetto esista, né il 5% di probabilità che il risultato sia dovuto al caso."}}, {"@type": "Question", "name": "Il p-value è la probabilità che l'ipotesi nulla sia vera?", "acceptedAnswer": {"@type": "Answer", "text": "No. È la probabilità dei dati supponendo vera l'ipotesi nulla, non la probabilità dell'ipotesi nulla dati i dati. Per ottenere la seconda serve sapere anche quanto era plausibile l'ipotesi prima del test: con idee poco promettenti, anche fra i risultati con p sotto 0,05 i falsi allarmi possono essere quasi la metà."}}, {"@type": "Question", "name": "Un p-value molto piccolo indica un effetto importante?", "acceptedAnswer": {"@type": "Answer", "text": "No. Il p-value dipende sia dalla dimensione dell'effetto sia dalla quantità di dati: con milioni di impressioni, anche una differenza di CTR irrilevante produce un p-value minuscolo. L'importanza si giudica dalla dimensione dell'effetto e dal suo intervallo di confidenza."}}, {"@type": "Question", "name": "Se il p-value è maggiore di 0,05 l'effetto non esiste?", "acceptedAnswer": {"@type": "Answer", "text": "Non necessariamente. Vuol dire che i dati non bastano a escludere il caso. Se il test aveva poca potenza, un effetto reale può facilmente produrre un p-value alto: l'intervallo di confidenza mostra quali effetti sono ancora compatibili con i dati."}}, {"@type": "Question", "name": "Perché proprio 0,05?", "acceptedAnswer": {"@type": "Answer", "text": "È una convenzione proposta da Ronald Fisher negli anni Venti come riferimento comodo, una probabilità su venti. Non ha nessun fondamento naturale: un risultato con p = 0,049 e uno con p = 0,051 portano praticamente la stessa evidenza."}}]}</script>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Il codice dell&#8217;articolo si presta a qualche esperimento rivelatore.</p>



<ul class="wp-block-list"><li>Si porta la quota di idee buone da 100 a 500 su 1.000 (<code>funziona &lt;- seq_len(1000) &lt;= 500</code>) e si ricalcola la tabella: quanti dei «vincitori» sono ora falsi allarmi? Il risultato dovrebbe avvicinarsi al 9% della tabella.</li><li>Si aumentano le impressioni per variante da 4.000 a 16.000 nella funzione <code>un_test()</code>: cosa succede alla potenza, e alla quota di falsi allarmi fra le idee significative?</li><li>Si ripete la simulazione <code>p_nulla</code> e si contano i test con p fra 0,04 e 0,05, e quelli fra 0,94 e 0,95. Perché sono circa lo stesso numero?</li></ul>



<p class="wp-block-paragraph">L&#8217;ultimo esercizio è il più breve, e forse il più istruttivo: quando l&#8217;effetto non c&#8217;è, un p-value «quasi significativo» non è più vicino alla verità di un p-value altissimo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Abbiamo visto che, fra i risultati significativi, la quota di falsi allarmi dipende da quanto sono buone le idee che testiamo. Ma c&#8217;è un modo ancora più insidioso di farsi ingannare dai successi: guardare solo quelli che ce l&#8217;hanno fatta. I case study che leggiamo raccontano le strategie che hanno funzionato, mai le cento identiche che sono fallite. È il <a href="https://www.gironi.it/blog/survivorship-bias/"><em>survivorship bias</em></a>, ed è il prossimo passo del nostro viaggio tra le trappole dei dati.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Sul p-value, sui suoi usi e sui suoi abusi, consiglio <a href="https://www.amazon.it/dp/8806246623?tag=consulenzeinf-21&amp;ascsubtag=p-value" rel="nofollow sponsored noopener" target="_blank"><em>L&#8217;arte della statistica</em></a> di David Spiegelhalter: spiega cosa dice davvero un test di significatività, perché tanti risultati «significativi» non si replicano e come ragionare sull&#8217;incertezza senza farsi abbagliare da una soglia.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/p-value/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Confronti multipli: perché su 200 query qualcuna «migliora» sempre per caso</title>
		<link>https://www.gironi.it/blog/confronti-multipli/</link>
					<comments>https://www.gironi.it/blog/confronti-multipli/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Wed, 07 Oct 2026 08:04:26 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=4503</guid>

					<description><![CDATA[Nel 2009 un gruppo di neuroscienziati mise un salmone in uno scanner per la risonanza magnetica funzionale, gli mostrò una serie di fotografie di persone in situazioni sociali e gli &#8220;chiese&#8221; quali emozioni provassero. L&#8217;analisi trovò una piccola area del cervello con un&#8217;attività statisticamente significativa. Il dettaglio è che il salmone era morto. Lo studio &#8230; <a href="https://www.gironi.it/blog/confronti-multipli/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Confronti multipli: perché su 200 query qualcuna «migliora» sempre per caso"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Nel 2009 un gruppo di neuroscienziati mise un salmone in uno scanner per la risonanza magnetica funzionale, gli mostrò una serie di fotografie di persone in situazioni sociali e gli &#8220;chiese&#8221; quali emozioni provassero. L&#8217;analisi trovò una piccola area del cervello con un&#8217;attività statisticamente significativa. Il dettaglio è che il salmone era morto. <br>Lo studio era uno scherzo serissimo (vinse un Ig Nobel nel 2012), e il suo bersaglio non era la risonanza magnetica: era un errore di metodo diffusissimo. <strong>Con abbastanza test, qualcosa di &#8220;significativo&#8221; salta fuori comunque.</strong> Lo scanner aveva confrontato decine di migliaia di piccoli volumi del cervello, uno per uno, ciascuno con la soglia del 5%.</p>



<p class="wp-block-paragraph">Nel nostro lavoro il salmone morto ha un nome familiare: Search Console. Confrontiamo il CTR di questo mese con quello del mese scorso su 200 query, troviamo una dozzina di variazioni &#8220;significative&#8221; e scriviamo nel report quali query sono migliorate. Ma quante di quelle dodici sono cambiate davvero? È il problema dei <strong>confronti multipli</strong>, e abbiamo già visto spuntarne il profilo parlando di <a href="https://www.gironi.it/blog/split-test-seo/">split test SEO</a> e di <a href="https://www.gironi.it/blog/peeking-problem/">peeking</a>.</p>



<span id="more-4503"></span>



<p class="wp-block-paragraph"><strong>In sintesi:</strong> quando si eseguono molti test statistici insieme, la soglia del 5% non vale più per l&#8217;insieme: con 10 test indipendenti la probabilità di almeno un falso positivo sale al 40%, con 100 al 99%. I <strong>confronti multipli</strong> si correggono in due modi. La <strong>correzione di Bonferroni</strong> (o la sua versione migliorata, il metodo di <strong>Holm</strong>) controlla la probabilità di commettere anche un solo falso positivo, dividendo la soglia per il numero di test: è prudente, ma perde molti effetti veri. Il metodo di <strong>Benjamini-Hochberg</strong> controlla invece la <strong>quota di falsi positivi fra le scoperte</strong> (<em>false discovery rate</em>): è meno severo e più adatto quando si scandagliano molte pagine o query. In R si usa <code>p.adjust()</code>.</p>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#perche">Perché più test fanno più falsi allarmi?</a></li><li><a href="#esempio">Un esempio: 200 query in Search Console</a></li><li><a href="#bonferroni">La correzione di Bonferroni (e quella di Holm)</a></li><li><a href="#fwer-fdr">Errore sulla famiglia o quota di scoperte false?</a></li><li><a href="#benjamini-hochberg">Il metodo di Benjamini-Hochberg</a></li><li><a href="#quale">Quale correzione scegliere?</a></li><li><a href="#dove">Dove si nascondono i confronti multipli nella SEO</a></li><li><a href="#domande-frequenti">Domande frequenti</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="perche">Perché più test fanno più falsi allarmi?</h2>



<p class="wp-block-paragraph">Ricordiamo cosa promette la soglia del 5% in un <a href="https://www.gironi.it/blog/il-test-delle-ipotesi/">test delle ipotesi</a>: se non c&#8217;è nessun effetto, il test ci darà un falso allarme in 5 casi su 100. È una promessa che vale <strong>per un test</strong>. Il guaio comincia quando di test ne facciamo molti, e ci basta che <em>uno qualsiasi</em> risulti significativo per alzare la mano.</p>



<p class="wp-block-paragraph">Facciamo un esempio al volo. Lanciando un dado una volta, la probabilità di fare 6 è un sesto; lanciandolo dieci volte, la probabilità di fare <em>almeno un</em> 6 sale all&#8217;84%. Con i test funziona allo stesso modo. Se un test da solo ha il 95% di probabilità di non dare un falso allarme, due test indipendenti ne hanno il 95% del 95%, e così via. La probabilità di <strong>almeno un falso positivo</strong> su m test indipendenti, che gli statistici chiamano <em>family-wise error rate</em> (FWER), è dunque:</p>



<p class="wp-block-paragraph">\(FWER = 1 &#8211; (1 &#8211; \alpha)^m\)</p>



<p class="wp-block-paragraph">dove α è la soglia del singolo test (0,05) e m il numero di test. Con 10 test vale 1 − 0,95¹⁰ = <strong>40%</strong>; con 14 supera il 50%; con 100 arriva al 99,4%.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/confronti-multipli-fwer.png" alt="La probabilità di almeno un falso positivo al crescere del numero di test, ciascuno con la soglia del 5%. Già con 14 test un falso allarme è più probabile del contrario; con 100 è praticamente certo."/><figcaption class="wp-element-caption">La probabilità di almeno un falso positivo al crescere del numero di test, ciascuno con la soglia del 5%. Già con 14 test un falso allarme è più probabile del contrario; con 100 è praticamente certo.</figcaption></figure>



<p class="wp-block-paragraph">C&#8217;è un altro modo di vederla, ancora più diretto: su m test senza alcun effetto reale, <strong>il numero atteso di falsi positivi è m × α</strong>. Su 200 query che non sono cambiate affatto, dobbiamo aspettarci una decina di variazioni &#8220;significative&#8221; (200 × 0,05 = 10). Non sono un&#8217;anomalia: sono esattamente ciò che la soglia del 5% promette.</p>



<h2 class="wp-block-heading" id="esempio">Un esempio: 200 query in Search Console</h2>



<p class="wp-block-paragraph">Costruiamo l&#8217;esempio per simulazione, così conosciamo la verità. Prendiamo 200 query, con qualche migliaio di impressioni al mese ciascuna e un CTR fra l&#8217;1% e il 6%. Per 180 di esse <strong>non cambia niente</strong> fra un mese e l&#8217;altro; per le prime 20 il CTR sale davvero, del 50%. Per ogni query confrontiamo i due mesi con un test sulle proporzioni, come faremmo in un report.</p>



<p class="wp-block-paragraph">Genero i dati in R:</p>



<pre class="wp-block-code"><code>set.seed(7)
n_query &lt;- 200
impressioni &lt;- round(300 + rlnorm(n_query, log(5000), 0.8))   # impressioni al mese
ctr &lt;- runif(n_query, 0.01, 0.06)                              # CTR fra l'1% e il 6%
vera &lt;- seq_len(n_query) &lt;= 20                                 # le 20 che cambiano davvero
clic_prima &lt;- rbinom(n_query, impressioni, ctr)
clic_dopo  &lt;- rbinom(n_query, impressioni, ifelse(vera, ctr * 1.5, ctr))

# un test per query: il CTR del mese scorso contro quello di questo mese
p &lt;- mapply(function(a, b, n) prop.test(c(a, b), c(n, n))$p.value,
            clic_prima, clic_dopo, impressioni)</code></pre>



<p class="wp-block-paragraph">Ora facciamo quello che si fa di solito: segniamo come &#8220;cambiate&#8221; le query con p-value sotto 0,05, e confrontiamo con la verità.</p>



<pre class="wp-block-code"><code>table(scoperta = p &lt; 0.05, vera)

#         vera
# scoperta FALSE TRUE
#    FALSE   171    2
#    TRUE      9   18</code></pre>



<p class="wp-block-paragraph">Il test ha segnalato <strong>27 query</strong>. Diciotto sono cambiate davvero, ma <strong>nove no</strong>: sono falsi allarmi, esattamente il 5% delle 180 query immobili. In termini più chiari e diretti: <strong>un terzo delle &#8220;scoperte&#8221; del nostro report è rumore</strong>, e niente nei numeri ci dice quale terzo. Se a ciascuna di quelle nove query dedicassimo un&#8217;analisi, un intervento, una riga di commento al cliente, staremmo lavorando su un fantasma.</p>



<h2 class="wp-block-heading" id="bonferroni">La correzione di Bonferroni (e quella di Holm)</h2>



<p class="wp-block-paragraph">La soluzione più antica, e la più intuitiva, è rendere ogni singolo test più severo. Se vogliamo che la probabilità di <em>almeno un</em> falso positivo sull&#8217;intera famiglia di test resti al 5%, basta dividere la soglia per il numero di test. È la <strong>correzione di Bonferroni</strong>:</p>



<p class="wp-block-paragraph">\(\alpha_{Bonferroni} = \frac{\alpha}{m} = \frac{0{,}05}{200} = 0{,}00025\)</p>



<p class="wp-block-paragraph">Una query è &#8220;cambiata&#8221; solo se il suo p-value scende sotto 0,00025. In R non serve fare la divisione a mano: <code>p.adjust()</code> restituisce i p-value <strong>corretti</strong>, da confrontare con la soglia abituale. Lo verifico in R:</p>



<pre class="wp-block-code"><code>table(scoperta = p.adjust(p, method = "bonferroni") &lt; 0.05, vera)

#         vera
# scoperta FALSE TRUE
#    FALSE   180    6
#    TRUE      0   14</code></pre>



<p class="wp-block-paragraph">Nessun falso allarme: le 14 query segnalate sono tutte vere. Il prezzo, però, si vede nell&#8217;angolo in alto a destra. <strong>Sei query cambiate davvero sono sparite dal radar</strong>, perché un aumento del 50% su poche centinaia di clic non basta a scendere sotto una soglia così bassa. Bonferroni compra la certezza di non sbagliare con la cecità su una parte degli effetti reali.</p>



<p class="wp-block-paragraph">C&#8217;è un perfezionamento che non costa nulla, il <strong>metodo di Holm</strong>. Si ordinano i p-value dal più piccolo al più grande; il primo si confronta con α/m, il secondo con α/(m − 1), il terzo con α/(m − 2), e ci si ferma al primo che non supera la sua soglia. Garantisce lo stesso controllo di Bonferroni ed è <strong>sempre almeno altrettanto potente</strong>, spesso di più. Nel nostro esempio dà lo stesso risultato (14 scoperte, nessuna falsa); con <code>p.adjust(p, method = "holm")</code> non c&#8217;è motivo di preferirgli Bonferroni.</p>



<h2 class="wp-block-heading" id="fwer-fdr">Errore sulla famiglia o quota di scoperte false?</h2>



<p class="wp-block-paragraph">Prima di andare oltre, conviene fermarsi su una domanda che sembra filosofica ed è invece pratica: <em>che cosa</em> vogliamo controllare?</p>



<p class="wp-block-paragraph">Bonferroni e Holm controllano la probabilità di commettere <strong>anche un solo</strong> falso positivo. È la richiesta giusta quando ogni errore costa caro: decidiamo di rifare il template di tutte le schede prodotto sulla base di un test, e un falso positivo vuol dire settimane di lavoro buttate.</p>



<p class="wp-block-paragraph">Ma in molte analisi SEO la situazione è diversa. Scandagliamo 200 query o 500 pagine per capire <strong>quali meritano un approfondimento</strong>. Qui un falso positivo non è una catastrofe: è un&#8217;occhiata in più a una pagina che non ne aveva bisogno. Quello che non vogliamo è che le scoperte siano <em>per lo più</em> falsi allarmi. La grandezza da controllare diventa allora la <strong>quota di falsi positivi fra le scoperte</strong>, che gli statistici chiamano <em>false discovery rate</em> (FDR). Un FDR del 5% vuol dire: delle query che segnaliamo, in media il 5% sarà rumore.</p>



<p class="wp-block-paragraph"><strong>Sono due promesse diverse, e la seconda è molto meno esigente della prima</strong>: per questo, a parità di dati, lascia passare più effetti veri.</p>



<h2 class="wp-block-heading" id="benjamini-hochberg">Il metodo di Benjamini-Hochberg</h2>



<p class="wp-block-paragraph">Il metodo che controlla il FDR, proposto da Yoav Benjamini e Yosef Hochberg nel 1995, è sorprendentemente semplice. A parole: ordiniamo i p-value dal più piccolo al più grande, e invece di una soglia fissa usiamo una soglia che <strong>cresce con la posizione in classifica</strong>. Il p-value al primo posto deve stare sotto 1/m × 0,05, quello al secondo sotto 2/m × 0,05, e così via:</p>



<p class="wp-block-paragraph">\(p_{(i)} \le \frac{i}{m} \cdot q\)</p>



<p class="wp-block-paragraph">dove p₍ᵢ₎ è il p-value in posizione i, m il numero di test e q il FDR che accettiamo (qui 0,05). Si cerca la posizione <strong>più bassa in classifica</strong> (il p-value più grande) che rispetta la disuguaglianza: quella query e tutte quelle che la precedono sono le scoperte.</p>



<p class="wp-block-paragraph">Vediamo i p-value del nostro esempio dalla posizione 14 alla 20, con la soglia di ciascuno:</p>



<figure class="wp-block-table"><table><thead><tr><th>Rango</th><th>p-value</th><th>Soglia BH (rango/200 × 0,05)</th><th>CTR cambiato davvero?</th></tr></thead><tbody><tr><td>14</td><td>0,000018</td><td>0,00350</td><td>sì</td></tr><tr><td>15</td><td>0,00070</td><td>0,00375</td><td>sì</td></tr><tr><td>16</td><td>0,00124</td><td>0,00400</td><td>sì</td></tr><tr><td><strong>17</strong></td><td><strong>0,00356</strong></td><td><strong>0,00425</strong></td><td><strong>no</strong></td></tr><tr><td>18</td><td>0,00531</td><td>0,00450</td><td>sì</td></tr><tr><td>19</td><td>0,00683</td><td>0,00475</td><td>no</td></tr><tr><td>20</td><td>0,00720</td><td>0,00500</td><td>sì</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">L&#8217;ultima posizione che sta sotto la sua soglia è la 17: le prime 17 query sono le scoperte. Bonferroni, con la sua soglia fissa di 0,00025, si sarebbe fermato alla 14. Lo verifico in R:</p>



<pre class="wp-block-code"><code>table(scoperta = p.adjust(p, method = "BH") &lt; 0.05, vera)

#         vera
# scoperta FALSE TRUE
#    FALSE   179    4
#    TRUE      1   16</code></pre>



<p class="wp-block-paragraph">Diciassette scoperte, <strong>sedici vere e una falsa</strong>: un falso allarme su diciassette, il 5,9%, in linea con il 5% promesso. Rispetto a Bonferroni abbiamo recuperato due query cambiate davvero, al prezzo di un solo errore. E rispetto al report senza correzioni siamo passati da nove falsi allarmi su 27 a uno su 17.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/confronti-multipli-pvalue.png" alt="I 40 p-value più piccoli delle 200 query, in ordine, colorati secondo la verità. La soglia piatta del 5% cattura anche molte query che non sono cambiate. La soglia di Bonferroni è così bassa da lasciarne fuori diverse vere. La retta di Benjamini-Hochberg sale con il rango e taglia dopo la diciassettesima."/><figcaption class="wp-element-caption">I 40 p-value più piccoli delle 200 query, in ordine, colorati secondo la verità. La soglia piatta del 5% cattura anche molte query che non sono cambiate. La soglia di Bonferroni è così bassa da lasciarne fuori diverse vere. La retta di Benjamini-Hochberg sale con il rango e taglia dopo la diciassettesima.</figcaption></figure>



<p class="wp-block-paragraph">Il grafico mostra bene la logica. In cima alla classifica ci sono gli effetti grandi, con p-value minuscoli: li trova qualunque metodo. Il problema è la zona di mezzo, dove effetti veri ma modesti si mescolano al rumore. Bonferroni la scarta in blocco; la soglia piatta la accoglie in blocco; Benjamini-Hochberg la attraversa con una soglia che si allarga quanto basta.</p>



<p class="wp-block-paragraph">(n.b. il metodo garantisce un FDR <em>medio</em> del 5%: in un singolo report la quota effettiva può essere più alta o più bassa. Ripetendo la simulazione 300 volte, Benjamini-Hochberg produce in media 0,6 scoperte false per report, una quota del 3,7%: sotto il 5% promesso.)</p>



<h2 class="wp-block-heading" id="quale">Quale correzione scegliere?</h2>



<p class="wp-block-paragraph">Non esiste una correzione giusta in assoluto: esiste quella adatta alla domanda. In sintesi:</p>



<figure class="wp-block-table"><table><thead><tr><th>Situazione</th><th>Correzione</th><th>Perché</th></tr></thead><tbody><tr><td>Un solo test, deciso prima di guardare i dati</td><td>nessuna</td><td>non c&#8217;è molteplicità</td></tr><tr><td>Poche decisioni costose (un template, una migrazione, la variante vincente di un A/B/n)</td><td>Holm</td><td>anche un solo errore costa caro</td></tr><tr><td>Molte query, pagine o segmenti da cui scegliere cosa approfondire</td><td>Benjamini-Hochberg</td><td>conta la quota di scoperte false, non l&#8217;errore singolo</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Il caso più comune nella pratica quotidiana, però, è un altro: <strong>non avevamo deciso niente prima</strong>. Abbiamo aperto Search Console, filtrato, segmentato, confrontato, e ci siamo fermati sui numeri che ci sembravano interessanti. In questo scenario nessuna correzione è possibile, perché non sappiamo nemmeno quanti test abbiamo fatto davvero.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: <strong>una correzione per confronti multipli vale solo se conta tutti i test che abbiamo fatto, non solo quelli che mostriamo.</strong> <br>Se abbiamo guardato 30 segmenti e ne riportiamo 5, la correzione va fatta su 30. Se abbiamo provato tre modi diversi di calcolare la metrica e teniamo quello che &#8220;funziona&#8221;, i test sono tre volte tanti. Il modo più semplice per non perdersi è decidere prima, e mettere per iscritto, quali confronti faremo.</p>
</div></div>



<h2 class="wp-block-heading" id="dove">Dove si nascondono i confronti multipli nella SEO</h2>



<p class="wp-block-paragraph">Il report mese su mese delle query è il caso più evidente, ma la molteplicità ha molti travestimenti.</p>



<p class="wp-block-paragraph"><strong>I segmenti.</strong> &#8220;Il CTR non è cambiato, ma da mobile in Lombardia sì&#8221;: a forza di affettare i dati per dispositivo, paese, categoria e tipo di pagina, qualche fetta significativa salta fuori sempre. È la stessa trappola del <a href="https://www.gironi.it/blog/il-paradosso-di-simpson-nella-seo-quando-i-dati-aggregati-possono-mentire/">paradosso di Simpson</a>, vista dal lato opposto.</p>



<p class="wp-block-paragraph"><strong>Le metriche.</strong> In uno <a href="https://www.gironi.it/blog/split-test-seo/">split test SEO</a> guardare clic, impressioni, CTR, posizione e conversioni vuol dire fare cinque test. Per questo la metrica principale si sceglie in anticipo.</p>



<p class="wp-block-paragraph"><strong>Le varianti.</strong> Un <a href="https://www.gironi.it/blog/ab-testing/">A/B test</a> con cinque varianti contro il controllo sono cinque confronti; se poi confrontiamo anche le varianti fra loro diventano quindici. Per i confronti fra tutte le coppie di medie l&#8217;<a href="https://www.gironi.it/blog/lanalisi-della-varianza-anova-spiegata-semplice/">ANOVA</a> ha un suo strumento dedicato, il test di Tukey.</p>



<p class="wp-block-paragraph"><strong>Il tempo.</strong> Guardare un test ogni giorno riservandosi di fermarlo è una molteplicità nascosta nel calendario: è il <a href="https://www.gironi.it/blog/peeking-problem/">peeking problem</a>.</p>



<p class="wp-block-paragraph"><strong>Le correlazioni.</strong> Una matrice di <a href="https://www.gironi.it/blog/correlazione/">correlazione</a> fra 20 metriche contiene 190 coppie: una decina risulterà &#8220;significativa&#8221; anche se le metriche fossero tutte indipendenti.</p>



<p class="wp-block-paragraph">(n.b. nella realtà le cose vanno anche peggio che nella nostra simulazione. Il CTR di una query non oscilla solo per il caso binomiale dei clic: cambia con la posizione, con le funzioni della SERP, con il mix di dispositivi. <code>prop.test()</code> ignora queste fonti di variabilità e sottostima l&#8217;incertezza, quindi sui dati veri i falsi allarmi sono più del 5%. Le correzioni per confronti multipli non rimediano a un modello sbagliato: lo presuppongono giusto.)</p>



<h2 class="wp-block-heading" id="domande-frequenti">Domande frequenti</h2>



<h3 class="wp-block-heading">Cosa sono i confronti multipli in statistica?</h3>



<p class="wp-block-paragraph">Sono le situazioni in cui si eseguono molti test statistici sugli stessi dati o sulla stessa domanda: confrontare molte query, molti segmenti, molte metriche o molte varianti. Ogni test ha la sua probabilità di falso positivo, e insieme le probabilità si accumulano: con abbastanza test, qualche risultato &#8220;significativo&#8221; arriva per puro caso.</p>



<h3 class="wp-block-heading">Come si applica la correzione di Bonferroni?</h3>



<p class="wp-block-paragraph">Si divide la soglia di significatività per il numero di test: con 20 test e una soglia del 5%, ogni test deve avere un p-value sotto 0,05 / 20 = 0,0025. In modo equivalente si moltiplica ogni p-value per il numero di test e lo si confronta con 0,05. In R lo fa <code>p.adjust(p, method = "bonferroni")</code>.</p>



<h3 class="wp-block-heading">Che differenza c&#8217;è tra FWER e FDR?</h3>



<p class="wp-block-paragraph">Il FWER (family-wise error rate) è la probabilità di commettere almeno un falso positivo nell&#8217;insieme dei test; il FDR (false discovery rate) è la quota media di falsi positivi fra i risultati dichiarati significativi. Controllare il FWER è più severo e adatto a poche decisioni costose; controllare il FDR è adatto all&#8217;esplorazione di molte ipotesi.</p>



<h3 class="wp-block-heading">Quando usare Benjamini-Hochberg invece di Bonferroni?</h3>



<p class="wp-block-paragraph">Quando i test sono molti e lo scopo è selezionare cosa approfondire, accettando che una piccola quota delle scoperte sia falsa: analisi di centinaia di query o pagine, screening di segmenti, ricerca di anomalie. Bonferroni, in questi casi, è così severo da perdere buona parte degli effetti veri.</p>



<h3 class="wp-block-heading">La correzione serve anche con pochi test?</h3>



<p class="wp-block-paragraph">Con due o tre test il rischio complessivo cresce di poco (dal 5% a circa il 10-14%), ma cresce. Se i confronti sono stati decisi in anticipo e sono pochi, il metodo di Holm costa pochissimo in potenza e mette al riparo. Se invece è stato deciso in anticipo un solo confronto principale, la correzione non serve.</p>



<script type="application/ld+json">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "Cosa sono i confronti multipli in statistica?", "acceptedAnswer": {"@type": "Answer", "text": "Sono le situazioni in cui si eseguono molti test statistici sugli stessi dati o sulla stessa domanda: confrontare molte query, molti segmenti, molte metriche o molte varianti. Ogni test ha la sua probabilità di falso positivo, e insieme le probabilità si accumulano: con abbastanza test, qualche risultato \"significativo\" arriva per puro caso."}}, {"@type": "Question", "name": "Come si applica la correzione di Bonferroni?", "acceptedAnswer": {"@type": "Answer", "text": "Si divide la soglia di significatività per il numero di test: con 20 test e una soglia del 5%, ogni test deve avere un p-value sotto 0,05 / 20 = 0,0025. In modo equivalente si moltiplica ogni p-value per il numero di test e lo si confronta con 0,05. In R lo fa p.adjust(p, method = \"bonferroni\")."}}, {"@type": "Question", "name": "Che differenza c'è tra FWER e FDR?", "acceptedAnswer": {"@type": "Answer", "text": "Il FWER (family-wise error rate) è la probabilità di commettere almeno un falso positivo nell'insieme dei test; il FDR (false discovery rate) è la quota media di falsi positivi fra i risultati dichiarati significativi. Controllare il FWER è più severo e adatto a poche decisioni costose; controllare il FDR è adatto all'esplorazione di molte ipotesi."}}, {"@type": "Question", "name": "Quando usare Benjamini-Hochberg invece di Bonferroni?", "acceptedAnswer": {"@type": "Answer", "text": "Quando i test sono molti e lo scopo è selezionare cosa approfondire, accettando che una piccola quota delle scoperte sia falsa: analisi di centinaia di query o pagine, screening di segmenti, ricerca di anomalie. Bonferroni, in questi casi, è così severo da perdere buona parte degli effetti veri."}}, {"@type": "Question", "name": "La correzione serve anche con pochi test?", "acceptedAnswer": {"@type": "Answer", "text": "Con due o tre test il rischio complessivo cresce di poco (dal 5% a circa il 10-14%), ma cresce. Se i confronti sono stati decisi in anticipo e sono pochi, il metodo di Holm costa pochissimo in potenza e mette al riparo. Se invece è stato deciso in anticipo un solo confronto principale, la correzione non serve."}}]}</script>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Il codice dell&#8217;esempio è completo, e si presta a qualche esperimento.</p>



<ul class="wp-block-list"><li>Si mette a 0 il numero di query che cambiano davvero (<code>vera &lt;- rep(FALSE, n_query)</code>) e si contano le scoperte con e senza correzione. Senza correzione dovrebbero essere una decina; con Bonferroni e con Benjamini-Hochberg quasi sempre zero. È il caso del salmone morto.</li><li>Si porta l&#8217;aumento del CTR da 1,5 a 1,2: un effetto più piccolo, più realistico per un title riscritto. Quante delle 20 query vere ritrova ciascun metodo?</li><li>Si ripete la simulazione con 2.000 query invece di 200, sempre con 20 vere. Cosa succede alla soglia di Bonferroni, e quante query vere sopravvivono?</li></ul>



<p class="wp-block-paragraph">L&#8217;ultimo esercizio, in particolare, mostra perché i grandi siti hanno bisogno del FDR: più test facciamo, più Bonferroni ci rende ciechi.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Abbiamo parlato di p-value per tutto l&#8217;articolo: da correggere, da ordinare, da confrontare con soglie diverse. Ma che cosa <em>dice</em>, esattamente, un p-value di 0,03? Che c&#8217;è il 97% di probabilità che l&#8217;effetto sia reale? Che l&#8217;ipotesi nulla ha il 3% di probabilità di essere vera? Nessuna delle due, ed è forse il numero più frainteso di tutta la statistica applicata. Sarà il tema del prossimo passo: <strong><a href="https://www.gironi.it/blog/p-value/">il p-value e i suoi malintesi</a></strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Per capire perché tanti risultati «significativi» non reggono alla prova dei fatti, e quanto conti il numero di domande che abbiamo posto ai dati, consiglio <a href="https://www.amazon.it/dp/8806246623?tag=consulenzeinf-21&amp;ascsubtag=confronti-multipli" rel="nofollow sponsored noopener" target="_blank"><em>L&#8217;arte della statistica</em></a> di David Spiegelhalter: un libro che spiega l&#8217;incertezza senza formule inutili e senza scorciatoie. Per i confronti multipli negli esperimenti online, dalle metriche alle varianti, il riferimento in inglese resta <a href="https://www.amazon.it/dp/1108724264?tag=consulenzeinf-21&amp;ascsubtag=confronti-multipli" rel="nofollow sponsored noopener" target="_blank"><em>Trustworthy Online Controlled Experiments</em></a> di Ron Kohavi, Diane Tang e Ya Xu.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/confronti-multipli/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Split test SEO: come capire se una modifica ha funzionato davvero</title>
		<link>https://www.gironi.it/blog/split-test-seo/</link>
					<comments>https://www.gironi.it/blog/split-test-seo/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 08:12:39 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=4481</guid>

					<description><![CDATA[In queste settimane ho rifatto da cima a fondo alcune delle pagine più lette di questo blog: nuove aperture, title riscritti, domande frequenti. Fra un mese aprirò Search Console, e la tentazione sarà la solita: confrontare i clic di prima con quelli di dopo. Se saranno saliti, sarà merito mio? E se saranno scesi, avrò &#8230; <a href="https://www.gironi.it/blog/split-test-seo/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Split test SEO: come capire se una modifica ha funzionato davvero"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In queste settimane ho rifatto da cima a fondo alcune delle pagine più lette di questo blog: nuove aperture, title riscritti, domande frequenti. Fra un mese aprirò Search Console, e la tentazione sarà la solita: confrontare i clic di prima con quelli di dopo. <br>Se saranno saliti, sarà merito mio? E se saranno scesi, avrò sbagliato tutto? <strong>Il confronto prima/dopo non può rispondere a nessuna delle due domande</strong>: nel frattempo cambiano la stagione, la concorrenza, l&#8217;algoritmo. Basta un core update nella settimana sbagliata e il miglior lavoro del mondo sembra un disastro.</p>



<p class="wp-block-paragraph">Nel marketing la risposta classica a questo problema è l&#8217;<a href="https://www.gironi.it/blog/ab-testing/">A/B test</a>: due versioni, utenti divisi a caso, si confrontano i risultati. Con la SEO però c&#8217;è un ostacolo insormontabile, perché Google vede una sola versione della pagina. Non possiamo dividere gli utenti: possiamo dividere <strong>le pagine</strong>. È l&#8217;idea dello <em>split test SEO</em>, e lo strumento statistico che lo rende affidabile ha un nome che sembra uno scioglilingua: la <strong>differenza delle differenze</strong>.</p>



<span id="more-4481"></span>



<p class="wp-block-paragraph"><strong>In sintesi:</strong> uno <strong>split test SEO</strong> misura l&#8217;effetto di una modifica (title, template, dati strutturati) dividendo a caso un gruppo di pagine simili in <strong>pagine test</strong>, che ricevono la modifica, e <strong>pagine di controllo</strong>, lasciate intatte. Il confronto prima/dopo da solo inganna, perché stagionalità e core update colpiscono tutte le pagine; la <strong>differenza delle differenze</strong> (<em>difference-in-differences</em>, DiD) sottrae alla variazione delle pagine test quella delle pagine di controllo, e ciò che resta è l&#8217;effetto della modifica. Con un t-test sulle variazioni pagina per pagina si stabilisce se l&#8217;effetto è distinguibile dal caso. Servono pagine numerose e omogenee: per accorgersi di un +8% ne servono, di norma, alcune centinaia.</p>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#perche-non-ab">Perché l&#8217;A/B test classico non funziona per la SEO</a></li><li><a href="#prima-dopo">Perché il confronto prima/dopo inganna</a></li><li><a href="#costruire">Come si costruisce uno split test SEO?</a></li><li><a href="#esempio">Un esempio: 200 schede prodotto e un core update</a></li><li><a href="#did">Cos&#8217;è la differenza delle differenze?</a></li><li><a href="#tendenze-parallele">Come verifichiamo che il controllo sia un buon controllo?</a></li><li><a href="#quante-pagine">Quante pagine servono, e per quanto tempo?</a></li><li><a href="#insidie">Le insidie dello split test</a></li><li><a href="#controfattuale">E quando non si può sorteggiare?</a></li><li><a href="#domande-frequenti">Domande frequenti</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="perche-non-ab">Perché l&#8217;A/B test classico non funziona per la SEO</h2>



<p class="wp-block-paragraph">In un A/B test sulla conversione di una landing page, ogni visitatore vede la versione A o la versione B, assegnata a caso. Siccome il sorteggio rende i due gruppi di utenti simili in tutto il resto, una differenza nei risultati si può attribuire alla versione. Il caso fa da garante.</p>



<p class="wp-block-paragraph">Con il ranking il meccanismo si inceppa subito. <strong>L&#8217;utente che conta, nella SEO, è uno solo: Googlebot.</strong> Se gli mostriamo due versioni della stessa pagina a seconda della visita, nel migliore dei casi non capisce quale indicizzare, nel peggiore si chiama <em>cloaking</em>. E il risultato che vogliamo misurare (posizioni, impressioni, clic organici) dipende da come Google valuta <em>la</em> pagina, non da come la vive il singolo visitatore.</p>



<p class="wp-block-paragraph">La via d&#8217;uscita è spostare il sorteggio un livello più su. Al posto degli utenti si sorteggiano le pagine: su un gruppo la modifica, sull&#8217;altro niente. Funziona quando il sito ha molte pagine <strong>costruite con lo stesso template</strong>, che rispondono allo stesso tipo di ricerca: le schede prodotto di un e-commerce, le schede di un sito di annunci, le pagine di categoria, le pagine città di un portale. Un sito con dieci pagine tutte diverse, va detto subito, non può fare uno split test: non ha abbastanza unità da sorteggiare.</p>



<h2 class="wp-block-heading" id="prima-dopo">Perché il confronto prima/dopo inganna</h2>



<p class="wp-block-paragraph">Se non abbiamo un gruppo di controllo, l&#8217;unico confronto possibile è con il passato. Il guaio è che il passato differisce dal presente in mille modi che non c&#8217;entrano con la nostra modifica:</p>



<ul class="wp-block-list"><li>la <strong>stagionalità</strong>: a dicembre si cercano regali, ad agosto molto meno di tutto il resto;</li><li>gli <strong>aggiornamenti dell&#8217;algoritmo</strong>, che spostano il traffico di interi settori nel giro di pochi giorni;</li><li>la <strong>concorrenza</strong>, che nel frattempo pubblica, migliora, sparisce;</li><li>la <a href="https://www.gironi.it/blog/regressione-verso-la-media/">regressione verso la media</a>: se abbiamo scelto di intervenire proprio sulle pagine andate peggio, una parte del miglioramento sarebbe arrivata comunque.</li></ul>



<p class="wp-block-paragraph">Ognuno di questi fattori può essere più grande dell&#8217;effetto che cerchiamo. Un title migliore vale magari un 5-10% di clic in più; un core update può toglierne il 20% in una settimana. <strong>Confrontando solo il prima con il dopo, misuriamo la somma di tutto ciò che è cambiato, e la nostra modifica ne è solo un pezzo, spesso il più piccolo.</strong></p>



<p class="wp-block-paragraph">Il gruppo di controllo risolve il problema alla radice: le pagine di controllo vivono la stessa stagione, lo stesso core update, la stessa concorrenza delle pagine test. L&#8217;unica cosa che le distingue è la modifica.</p>



<h2 class="wp-block-heading" id="costruire">Come si costruisce uno split test SEO?</h2>



<p class="wp-block-paragraph">Le regole sono poche, ma vanno rispettate tutte.</p>



<p class="wp-block-paragraph"><strong>Pagine omogenee.</strong> Test e controllo devono essere pagine dello stesso tipo, con lo stesso template e lo stesso intento di ricerca. Mescolare schede prodotto e articoli del blog vuol dire confrontare mele con pere.</p>



<p class="wp-block-paragraph"><strong>Assegnazione casuale.</strong> Non scegliamo noi quali pagine modificare: le sorteggiamo. Se scegliessimo &#8220;le più promettenti&#8221;, introdurremmo esattamente la differenza di partenza che il test deve evitare. Un accorgimento utile è la <strong>randomizzazione a coppie</strong>: si ordinano le pagine per traffico, si formano coppie di pagine vicine e dentro ogni coppia si tira a sorte quale va nel test. I due gruppi nascono così bilanciati anche sul traffico, che è il fattore più ingombrante.</p>



<p class="wp-block-paragraph"><strong>Un periodo &#8220;prima&#8221; abbastanza lungo.</strong> Ci serve per sapere come si comportano le pagine quando nessuno le tocca, e per verificare che test e controllo si muovano insieme (ci torneremo). Sei-otto settimane sono un buon punto di partenza.</p>



<p class="wp-block-paragraph"><strong>Decidere prima quando guardare.</strong> Durata del test e metrica si fissano in anticipo. Guardare i dati ogni mattina e fermarsi al primo segnale favorevole è il <a href="https://www.gironi.it/blog/peeking-problem/">peeking problem</a>, e gonfia i falsi positivi esattamente come in un A/B test.</p>



<h2 class="wp-block-heading" id="esempio">Un esempio: 200 schede prodotto e un core update</h2>



<p class="wp-block-paragraph">Come sempre, un esempio è utile a chiarire il tutto. Lo costruiamo per simulazione, così conosciamo la verità e possiamo vedere quali metodi la ritrovano.</p>



<p class="wp-block-paragraph">Immaginiamo un e-commerce con 200 schede prodotto dello stesso template. Decidiamo di riscrivere i title di metà di esse, sorteggiate a coppie. Nel nostro mondo simulato la modifica funziona: <strong>porta l&#8217;8% di clic in più</strong>. Ma una settimana dopo la messa online arriva un core update che toglie il 15% dei clic a tutte le pagine del sito, test e controllo. E ogni pagina, come accade sempre, oscilla anche per conto suo, salendo o scendendo di qualche posizione.</p>



<p class="wp-block-paragraph">Costruisco le pagine in R, con il sorteggio a coppie:</p>



<pre class="wp-block-code"><code>set.seed(2020)
n_pagine &lt;- 200
base &lt;- rlnorm(n_pagine, meanlog = log(30), sdlog = 1)   # clic medi al giorno

# randomizzazione a coppie: ordino per traffico e sorteggio dentro ogni coppia
ordine &lt;- order(base)
gruppo &lt;- character(n_pagine)
for (k in seq(1, n_pagine, by = 2)) {
  gruppo[ordine[k:(k + 1)]] &lt;- sample(c("test", "controllo"))
}
pagine &lt;- data.frame(pagina = 1:n_pagine, base = base, gruppo = gruppo,
                     deriva = rlnorm(n_pagine, -0.25^2 / 2, 0.25))

tapply(pagine$base, pagine$gruppo, median)
# controllo      test
#    32.36      31.96</code></pre>



<p class="wp-block-paragraph">Il sorteggio a coppie ha fatto il suo lavoro: la pagina &#8220;tipica&#8221; dei due gruppi fa circa 32 clic al giorno in entrambi.</p>



<p class="wp-block-paragraph">Genero poi 14 settimane di clic giornalieri: 8 prima della modifica e 6 dopo. Il giorno 57 arrivano i nuovi title (solo sulle pagine test), il giorno 64 il core update (su tutte); nel weekend il traffico cala del 30%.</p>



<pre class="wp-block-code"><code>simula_clic &lt;- function(pagine, effetto = 1.08, modifica = 57, core_update = 64) {
  d &lt;- expand.grid(pagina = pagine$pagina, giorno = 1:98)
  d$gruppo &lt;- pagine$gruppo[d$pagina]
  lambda &lt;- pagine$base[d$pagina] *
    ifelse(d$giorno %% 7 %in% c(6, 0), 0.7, 1) *                    # weekend
    ifelse(d$giorno &gt;= core_update, 0.85, 1) *                      # core update
    ifelse(d$giorno &gt;= modifica, pagine$deriva[d$pagina], 1) *      # deriva
    ifelse(d$gruppo == "test" &amp; d$giorno &gt;= modifica, effetto, 1) * # la modifica
    rlnorm(nrow(d), 0, 0.15)                                        # rumore
  d$clic &lt;- rpois(nrow(d), lambda)
  d$periodo &lt;- ifelse(d$giorno &gt;= modifica, "dopo", "prima")
  d
}
clic &lt;- simula_clic(pagine)</code></pre>



<p class="wp-block-paragraph">(n.b. la <code>deriva</code> è l&#8217;oscillazione propria di ogni pagina dopo la modifica: concorrenti nuovi, link guadagnati o persi, piccoli spostamenti di posizione. Vale in media 1, ma la singola pagina può guadagnare o perdere anche il 30-40%.)</p>



<p class="wp-block-paragraph">Ora mettiamoci nei panni di chi ha fatto la modifica e non ha un gruppo di controllo. Per ogni pagina calcolo i clic medi al giorno prima e dopo, e la loro variazione:</p>



<pre class="wp-block-code"><code>variazioni &lt;- function(d) {
  m &lt;- aggregate(clic ~ pagina + gruppo + periodo, data = d, FUN = mean)
  w &lt;- reshape(m, idvar = c("pagina", "gruppo"), timevar = "periodo",
               direction = "wide")
  w$delta &lt;- log(w$clic.dopo / w$clic.prima)
  w
}
w &lt;- variazioni(clic)

# variazione media delle pagine test, in percentuale
exp(mean(w$delta[w$gruppo == "test"])) - 1
# [1] -0.101</code></pre>



<p class="wp-block-paragraph"><strong>Le pagine con i title nuovi hanno perso, in media, il 10% dei clic.</strong> Senza un controllo, la conclusione sarebbe immediata: la modifica ha fatto danni, torniamo indietro. E sarebbe sbagliata, perché sappiamo (l&#8217;abbiamo deciso noi) che la modifica funziona.</p>



<p class="wp-block-paragraph">(n.b. lavoriamo sul <strong>logaritmo</strong> del rapporto dopo/prima per due ragioni. Le pagine hanno volumi diversissimi, da un clic al giorno a qualche centinaio, e un effetto come &#8220;+8%&#8221; è proporzionale, non un numero fisso di clic: sulla scala logaritmica le variazioni percentuali diventano differenze, che si possono sommare e mediare. E la media dei logaritmi, riportata indietro con <code>exp()</code>, è la media geometrica: non si fa trascinare da una pagina che passa da 2 a 9 clic.)</p>



<h2 class="wp-block-heading" id="did">Cos&#8217;è la differenza delle differenze?</h2>



<p class="wp-block-paragraph">Guardiamo ora anche le pagine di controllo:</p>



<pre class="wp-block-code"><code>tapply(w$delta, w$gruppo, function(x) exp(mean(x)) - 1)
# controllo      test
#    -0.166    -0.101</code></pre>



<p class="wp-block-paragraph">Le pagine di controllo, che nessuno ha toccato, hanno perso il 16,6%. È il core update, più le oscillazioni di periodo: <strong>è ciò che sarebbe successo anche alle pagine test, se non le avessimo modificate</strong>. Gli economisti lo chiamano <em>controfattuale</em>, la storia che non è andata in scena.</p>



<p class="wp-block-paragraph">L&#8217;idea della differenza delle differenze è tutta qui. Calcoliamo prima la variazione di ciascun gruppo (prima differenza), poi la differenza fra le due variazioni (seconda differenza). A parole: <strong>l&#8217;effetto della modifica è quanto le pagine test sono andate meglio, o peggio, rispetto alle pagine di controllo.</strong> In formula, con la variazione media sulla scala logaritmica indicata con Δ:</p>



<p class="wp-block-paragraph">\(DiD = \bar{\Delta}_{test} &#8211; \bar{\Delta}_{controllo} = (\log T_{dopo} &#8211; \log T_{prima}) &#8211; (\log C_{dopo} &#8211; \log C_{prima})\)</p>



<p class="wp-block-paragraph">dove T e C sono i clic delle pagine test e di controllo. Tutto ciò che ha colpito entrambi i gruppi allo stesso modo (stagionalità, core update, il weekend) compare in tutte e due le parentesi e si cancella nella sottrazione. Resta solo ciò che distingue i gruppi: la modifica.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/split-test-seo-serie.png" alt="I due gruppi giorno per giorno, ogni pagina rapportata alla propria media del periodo «prima» (media mobile a 7 giorni). Prima della modifica le due linee camminano insieme; il core update le fa scendere entrambe, ma dopo le pagine test restano stabilmente sopra quelle di controllo. L'effetto non è il calo rispetto a 100: è lo scarto fra le due linee."/><figcaption class="wp-element-caption">I due gruppi giorno per giorno, ogni pagina rapportata alla propria media del periodo «prima» (media mobile a 7 giorni). Prima della modifica le due linee camminano insieme; il core update le fa scendere entrambe, ma dopo le pagine test restano stabilmente sopra quelle di controllo. L&#8217;effetto non è il calo rispetto a 100: è lo scarto fra le due linee.</figcaption></figure>



<p class="wp-block-paragraph">Il grafico racconta la storia meglio di qualunque tabella. Nella prima settimana dopo la modifica le pagine test salgono davvero, e chi guardasse solo quei giorni griderebbe al successo; poi arriva il core update e tutto precipita. Ma il controllo precipita di più. Lo scarto fra le due linee, che si mantiene fino alla fine, è il nostro effetto.</p>



<p class="wp-block-paragraph">Resta da capire se quello scarto è abbastanza grande da non essere un caso. Le pagine sono poche e rumorose: magari il sorteggio ha messo nel gruppo test, per pura sfortuna del controllo, qualche pagina che sarebbe salita comunque. È una domanda da test statistico, e siccome abbiamo una variazione per pagina e due gruppi da confrontare, lo strumento è il familiare <a href="https://www.gironi.it/blog/il-t-test-per-due-campioni-testare-una-ipotesi-per-campioni-dipendenti-o-indipendenti/">t-test per due campioni</a>. Lo calcolo in R:</p>



<pre class="wp-block-code"><code>t.test(delta ~ gruppo, data = w)

# t = -2.1086, df = 197.34, p-value = 0.03624
# 95 percent confidence interval:
#  -0.144123939 -0.004821942
# mean in group controllo      mean in group test
#              -0.1809326              -0.1064596</code></pre>



<p class="wp-block-paragraph">La differenza fra le due medie è 0,0745 sulla scala logaritmica, cioè exp(0,0745) − 1 = <strong>+7,7% di clic</strong>: molto vicino all&#8217;8% vero. Il p-value di 0,036 ci dice che una differenza così grande, se la modifica fosse stata inutile, uscirebbe per caso meno di 4 volte su 100.</p>



<p class="wp-block-paragraph">L&#8217;<a href="https://www.gironi.it/blog/intervalli-di-confidenza/">intervallo di confidenza</a> è però l&#8217;informazione più onesta. R lo riporta per la differenza controllo − test; girato nel verso che ci interessa e riportato in percentuale va da <strong>+0,5% a +15,5%</strong>. In termini più chiari e diretti: la modifica ha quasi certamente funzionato, ma i dati sono compatibili sia con un effetto trascurabile sia con un effetto doppio di quello stimato.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/split-test-seo-pagine.png" alt="Ogni punto è una pagina: la sua variazione di clic dopo la modifica. Le due nuvole si sovrappongono quasi del tutto, e decine di pagine test vanno peggio di pagine di controllo. Le barre verticali sono le medie geometriche dei due gruppi: l'effetto della modifica vive solo nello scarto fra le due barre."/><figcaption class="wp-element-caption">Ogni punto è una pagina: la sua variazione di clic dopo la modifica. Le due nuvole si sovrappongono quasi del tutto, e decine di pagine test vanno peggio di pagine di controllo. Le barre verticali sono le medie geometriche dei due gruppi: l&#8217;effetto della modifica vive solo nello scarto fra le due barre.</figcaption></figure>



<p class="wp-block-paragraph">Questa seconda figura spiega perché nessuno dovrebbe giudicare una modifica guardando una pagina sola, o anche dieci. <strong>Le oscillazioni di una singola pagina sono molto più grandi dell&#8217;effetto che cerchiamo</strong>: decine di pagine con i title nuovi hanno perso più di pagine di controllo intatte. Solo la media su molte pagine, confrontata con la media del controllo, fa emergere il segnale.</p>



<p class="wp-block-paragraph">(n.b. lo stesso risultato si ottiene con una regressione, <code>lm(delta ~ gruppo, data = w)</code>: il coefficiente di <code>gruppotest</code> vale 0,0745, con lo stesso p-value. È la forma che conviene quando vogliamo aggiungere altre variabili, per esempio il tipo di pagina, e il motivo per cui nei manuali la DiD compare quasi sempre come un modello di <a href="https://www.gironi.it/blog/regressione-lineare-semplice/">regressione</a>.)</p>



<h2 class="wp-block-heading" id="tendenze-parallele">Come verifichiamo che il controllo sia un buon controllo?</h2>



<p class="wp-block-paragraph">Tutto il ragionamento si regge su un&#8217;ipotesi: che, senza la modifica, test e controllo si sarebbero mossi allo stesso modo. Si chiama <strong>ipotesi delle tendenze parallele</strong> (<em>parallel trends</em>), ed è il punto in cui uno split test sta in piedi o crolla.</p>



<p class="wp-block-paragraph">Non possiamo verificarla nel periodo &#8220;dopo&#8221;, perché lì il controfattuale non lo vediamo. Ma possiamo controllarla nel periodo &#8220;prima&#8221;, dove nessuno ha toccato niente. Due strumenti, entrambi di estrema utilità.</p>



<p class="wp-block-paragraph">Il primo è il grafico che abbiamo già visto: <strong>prima della modifica le linee devono sovrapporsi</strong>. Se nel periodo &#8220;prima&#8221; le pagine test crescevano già più del controllo, il sorteggio è andato storto, o le pagine non sono davvero omogenee, e qualunque differenza successiva è sospetta.</p>



<p class="wp-block-paragraph">Il secondo è un <strong>test placebo</strong>: si finge che la modifica sia avvenuta a metà del periodo &#8220;prima&#8221; e si ripete l&#8217;analisi. Siccome in quel momento non è successo niente, la DiD deve risultare nulla. Lo verifico in R, spostando la finta modifica al giorno 29:</p>



<pre class="wp-block-code"><code>pre &lt;- subset(clic, giorno &lt; 57)
pre$periodo &lt;- ifelse(pre$giorno &gt;= 29, "dopo", "prima")
t.test(delta ~ gruppo, data = variazioni(pre))

# t = 0.071079, df = 197.57, p-value = 0.9434</code></pre>



<p class="wp-block-paragraph">Differenza praticamente zero (−0,09%), p-value 0,94: il controllo si comportava come il test quando nessuno li distingueva. È esattamente quello che vogliamo vedere. Un test placebo &#8220;significativo&#8221;, al contrario, sarebbe un campanello d&#8217;allarme da non ignorare.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: <strong>la differenza delle differenze elimina tutto ciò che colpisce test e controllo allo stesso modo, ma niente di ciò che li colpisce in modo diverso.</strong> <br>Se il core update penalizza proprio il tipo di title che abbiamo introdotto, o se nel frattempo un concorrente attacca solo alcune delle nostre categorie, la DiD attribuirà alla modifica anche quell&#8217;effetto. Il sorteggio rende improbabili queste coincidenze, non impossibili: per questo il controllo delle tendenze parallele e il test placebo non sono un optional.</p>
</div></div>



<h2 class="wp-block-heading" id="quante-pagine">Quante pagine servono, e per quanto tempo?</h2>



<p class="wp-block-paragraph">Nel nostro esempio è andata bene: p-value 0,036, effetto ritrovato. Ma ci è andata bene <em>quanto</em>? Lo scopriamo con la <a href="https://www.gironi.it/blog/effect-size-e-power-analysis/">power analysis</a>: la probabilità che il test si accorga di un effetto che c&#8217;è davvero.</p>



<p class="wp-block-paragraph">Per calcolarla servono tre ingredienti: l&#8217;effetto che vogliamo scoprire (+8%, cioè log(1,08) = 0,077 sulla scala logaritmica), il numero di pagine e la variabilità delle variazioni pagina per pagina dentro ciascun gruppo. Quest&#8217;ultima la stimiamo dai dati del test: vale circa 0,25. Calcolo in R:</p>



<pre class="wp-block-code"><code>s &lt;- sqrt(mean(tapply(w$delta, w$gruppo, var)))   # dev. standard dentro i gruppi
s
# [1] 0.2497

power.t.test(n = 100, delta = log(1.08), sd = s)$power
# [1] 0.583

power.t.test(power = 0.8, delta = log(1.08), sd = s)$n
# [1] 166.3</code></pre>



<p class="wp-block-paragraph">Con 100 pagine per gruppo, <strong>la potenza è del 58%: poco più di una moneta lanciata.</strong> Se ripetessimo lo stesso identico esperimento molte volte, in quattro casi su dieci concluderemmo che la modifica non serve, pur sapendo che funziona. Per arrivare all&#8217;80%, la soglia abituale, servono circa 166 pagine per gruppo, oltre 330 in tutto.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/split-test-seo-potenza.png" alt="Potenza del test al crescere del numero di pagine, per tre possibili effetti. Un +15% si scopre già con un centinaio di pagine; un +8% ne chiede più di 300 per arrivare all'80%; un +5% oltre 800. Il nostro esempio, con 200 pagine, sta a metà della curva dell'8%."/><figcaption class="wp-element-caption">Potenza del test al crescere del numero di pagine, per tre possibili effetti. Un +15% si scopre già con un centinaio di pagine; un +8% ne chiede più di 300 per arrivare all&#8217;80%; un +5% oltre 800. Il nostro esempio, con 200 pagine, sta a metà della curva dell&#8217;8%.</figcaption></figure>



<p class="wp-block-paragraph">La figura dà la misura del problema. <strong>Gli effetti tipici delle modifiche SEO sono piccoli, e gli effetti piccoli richiedono molte pagine.</strong> Un +15% si vede anche su un centinaio di schede; un +5%, che su un grande e-commerce vale comunque parecchio denaro, ne richiede più di 800.</p>



<p class="wp-block-paragraph">Prima di partire, dunque, conviene fare il conto all&#8217;incontrario: quante pagine abbiamo? Con quel numero, quale effetto minimo possiamo sperare di vedere? La variabilità si stima dalle stesse pagine, confrontando due periodi passati lunghi quanto il test. Se l&#8217;effetto minimo rilevabile è più grande di quello che ragionevolmente ci aspettiamo, il test è sottodimensionato. In quel caso o si allarga a più pagine, o si accetta che dirà soltanto &#8220;non lo so&#8221;.</p>



<p class="wp-block-paragraph">Quanto al tempo, due considerazioni. Google deve prima scansionare e rielaborare le pagine modificate, e su siti grandi può volerci più di una settimana: i primi giorni dopo la modifica vanno spesso esclusi dal confronto. Poi, un periodo &#8220;dopo&#8221; più lungo riduce il rumore giornaliero, ma non quello pagina per pagina, che è il grosso. <strong>Allungare il test aiuta meno che allargarlo</strong>: quattro-sei settimane sono di solito un buon compromesso.</p>



<h2 class="wp-block-heading" id="insidie">Le insidie dello split test</h2>



<p class="wp-block-paragraph">Il metodo è solido, ma nella pratica quotidiana le occasioni per farsi ingannare non mancano.</p>



<p class="wp-block-paragraph"><strong>La contaminazione.</strong> Le pagine test e controllo non vivono isolate: si linkano fra loro, condividono menu e pagine di categoria. Se la modifica cambia i link interni, per esempio aggiungendo un blocco di prodotti correlati, l&#8217;effetto si travasa anche sul controllo, e la DiD lo sottostima.</p>



<p class="wp-block-paragraph"><strong>Le pagine con pochissimi clic.</strong> Una pagina che passa da 2 a 0 clic al giorno ha una variazione infinita sulla scala logaritmica. Conviene escludere in partenza le pagine sotto una soglia minima di traffico, oppure lavorare sulle impressioni, che sono molto più numerose.</p>



<p class="wp-block-paragraph"><strong>La metrica scelta a posteriori.</strong> Clic, impressioni, CTR, posizione media, conversioni: se le guardiamo tutte, qualcuna risulterà &#8220;significativa&#8221; per caso. La metrica principale si decide prima, e le altre restano informazioni di contorno. È il problema dei <a href="https://www.gironi.it/blog/confronti-multipli/">confronti multipli</a>, e merita un discorso a sé.</p>



<p class="wp-block-paragraph"><strong>I gruppi troppo diversi.</strong> Se il sito ha pagine con volumi molto diversi, conviene randomizzare a coppie o a blocchi (per categoria, per fascia di traffico), come abbiamo fatto. Un sorteggio semplice su 40 pagine può produrre, per pura sfortuna, un gruppo test con tutte le pagine stagionali.</p>



<h2 class="wp-block-heading" id="controfattuale">E quando non si può sorteggiare?</h2>



<p class="wp-block-paragraph">Fin qui abbiamo dato per scontato di poter scegliere quali pagine modificare. Ma alcune delle decisioni più importanti, e più rischiose, non si possono dividere in due: una <strong>migrazione</strong> di dominio o di piattaforma, un nuovo template per tutto il sito, il passaggio a HTTPS. In questi casi il gruppo di controllo interno non esiste, perché tutte le pagine cambiano insieme. E il confronto prima/dopo resta inaffidabile come prima.</p>



<p class="wp-block-paragraph">L&#8217;idea della differenza delle differenze, però, sopravvive. Se non possiamo osservare il controfattuale dentro il sito, possiamo <strong>ricostruirlo da fuori</strong>: da serie che vivono le stesse stagioni e gli stessi aggiornamenti dell&#8217;algoritmo, ma che la migrazione non tocca. Un sito gemello dello stesso gruppo, una sezione del sito rimasta fuori dalla migrazione, l&#8217;indice di Google Trends per i temi principali. Nel periodo prima stimiamo come il nostro traffico si muove insieme a queste serie; nel periodo dopo usiamo la stessa relazione per prevedere <strong>quanto traffico avremmo avuto senza la migrazione</strong>. La differenza fra osservato e previsto è l&#8217;effetto.</p>



<p class="wp-block-paragraph">Facciamo un esempio. Simulo 180 giorni di clic di un sito che migra il giorno 121, perdendo il 12% del traffico; dieci giorni dopo un core update toglie il 25% a tutto il settore. Accanto ci sono due serie di controllo: un sito gemello non migrato e Google Trends.</p>



<pre class="wp-block-code"><code>set.seed(3)
giorni &lt;- 1:180; migrazione &lt;- 121                        # 4 mesi prima, 2 dopo
settore &lt;- exp(cumsum(rnorm(180, 0, 0.02))) *             # andamento del settore
           ifelse(giorni %% 7 %in% c(6, 0), 0.75, 1) *     # weekend
           (1 - 0.25 * (giorni &gt; 130))                     # core update di settore
rumore &lt;- function(sd) exp(as.numeric(arima.sim(list(ar = 0.6), 180, sd = sd)))
sito    &lt;- 800 * settore * rumore(0.05) * ifelse(giorni &gt;= migrazione, 0.88, 1)
gemello &lt;- 300 * settore * rumore(0.03)                    # sito gemello, non migrato
trends  &lt;- 60  * settore * rumore(0.05)                    # Google Trends del tema
prima &lt;- giorni &lt; migrazione

mean(sito[!prima]) / mean(sito[prima]) - 1
# [1] -0.273</code></pre>



<p class="wp-block-paragraph">Il prima/dopo dice <strong>−27%</strong>: più del doppio dell&#8217;effetto vero, perché mette sul conto della migrazione anche il core update. Costruisco allora il controfattuale con una <a href="https://www.gironi.it/blog/regressione-multipla-spiegata-semplice/">regressione multipla</a> sul periodo prima, in scala logaritmica, e lo proietto sul periodo dopo:</p>



<pre class="wp-block-code"><code>m &lt;- lm(log(sito) ~ log(gemello) + log(trends), subset = prima)
summary(m)$r.squared
# [1] 0.792

previsto &lt;- predict(m, newdata = data.frame(gemello, trends))
exp(mean(log(sito[!prima]) - previsto[!prima])) - 1
# [1] -0.125

sum(exp(previsto[!prima])) - sum(sito[!prima])   # clic persi in 60 giorni
# [1] 4532.7</code></pre>



<p class="wp-block-paragraph">Le due serie di controllo spiegano il 79% delle oscillazioni giornaliere del sito prima della migrazione. Il confronto con il controfattuale stima un effetto di <strong>−12,5%</strong>, a un passo dal −12% vero: circa 4.500 clic persi in due mesi, su 36.400 attesi.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.gironi.it/blog/wp-content/uploads/2026/10/split-test-seo-controfattuale.png" alt="Clic giornalieri del sito (media mobile a 7 giorni) e controfattuale previsto dalle serie di controllo. Prima della migrazione le due linee si inseguono; dopo, il core update fa crollare entrambe, ma i clic osservati restano sotto il previsto. L'area colorata sono i clic persi per la migrazione, non per l'algoritmo."/><figcaption class="wp-element-caption">Clic giornalieri del sito (media mobile a 7 giorni) e controfattuale previsto dalle serie di controllo. Prima della migrazione le due linee si inseguono; dopo, il core update fa crollare entrambe, ma i clic osservati restano sotto il previsto. L&#8217;area colorata sono i clic persi per la migrazione, non per l&#8217;algoritmo.</figcaption></figure>



<p class="wp-block-paragraph">Quanto fidarsi di quel −12,5%? I giorni di una serie storica non sono indipendenti fra loro (un giorno buono tende a essere seguito da un altro giorno buono), e un intervallo calcolato come se lo fossero sarebbe troppo ottimista. Anche qui ci viene in aiuto il <strong>placebo</strong>, questa volta nel tempo: fingiamo che la migrazione sia avvenuta in vari momenti del periodo prima, stimiamo il modello sui giorni precedenti e misuriamo il finto &#8220;effetto&#8221; sui 30 giorni successivi.</p>



<pre class="wp-block-code"><code>placebo &lt;- sapply(seq(40, 90, by = 5), function(k) {
  mk &lt;- lm(log(sito) ~ log(gemello) + log(trends), subset = giorni &lt; k)
  pk &lt;- predict(mk, newdata = data.frame(gemello, trends))
  exp(mean(log(sito[k:(k + 29)]) - pk[k:(k + 29)])) - 1
})
range(placebo)
# [1] -0.018  0.028</code></pre>



<p class="wp-block-paragraph">Quando non è successo niente, il metodo &#8220;trova&#8221; effetti fra −1,8% e +2,8%: è la sua soglia di rumore. Il −12,5% della migrazione sta nettamente fuori da quella fascia, e possiamo considerarlo reale.</p>



<p class="wp-block-paragraph">Il metodo che abbiamo appena costruito a mano è il cuore di <strong>CausalImpact</strong>, il pacchetto R sviluppato da Google per questo problema. Al posto della regressione semplice usa un modello bayesiano per serie storiche, che tiene conto di trend, stagionalità e dipendenza fra i giorni, e restituisce direttamente l&#8217;effetto con il suo intervallo di credibilità. La logica è la stessa: un controfattuale costruito da serie non toccate dall&#8217;intervento.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: <strong>il controfattuale è credibile solo quanto le serie di controllo.</strong> <br>Devono muoversi con il sito prima dell&#8217;intervento (lo si verifica con l&#8217;R² e con il placebo), e soprattutto non devono essere toccate dall&#8217;intervento stesso. Se la migrazione cambia anche il sito gemello, o se la usiamo come controllo per una campagna che ha spinto anche le ricerche su Google Trends, il controfattuale si sposta insieme al sito e l&#8217;effetto sparisce dai conti.</p>
</div></div>



<h2 class="wp-block-heading" id="domande-frequenti">Domande frequenti</h2>



<h3 class="wp-block-heading">Cos&#8217;è uno split test SEO?</h3>



<p class="wp-block-paragraph">È un esperimento che misura l&#8217;effetto di una modifica sul traffico organico dividendo a caso un insieme di pagine simili in due gruppi: le pagine test ricevono la modifica, quelle di controllo restano invariate. Confrontando come cambiano i due gruppi si separa l&#8217;effetto della modifica da stagionalità, aggiornamenti dell&#8217;algoritmo e concorrenza.</p>



<h3 class="wp-block-heading">Che differenza c&#8217;è tra split test SEO e A/B test?</h3>



<p class="wp-block-paragraph">L&#8217;A/B test divide gli utenti: ognuno vede una delle due versioni della stessa pagina. Lo split test SEO divide le pagine, perché Google deve vedere una sola versione di ciascuna. Per questo serve un sito con molte pagine dello stesso tipo.</p>



<h3 class="wp-block-heading">Quanto deve durare uno split test SEO?</h3>



<p class="wp-block-paragraph">Di solito sei-otto settimane di dati prima della modifica e quattro-sei settimane dopo, escludendo i primi giorni in cui Google sta ancora scansionando le pagine modificate. Allungare il periodo aiuta meno che aumentare il numero di pagine.</p>



<h3 class="wp-block-heading">Si può fare uno split test con poche pagine?</h3>



<p class="wp-block-paragraph">Con poche decine di pagine si scoprono solo effetti grandi, del 15-20% o più. Per effetti tipici del 5-10% servono di norma alcune centinaia di pagine: conviene calcolare la potenza del test prima di partire.</p>



<h3 class="wp-block-heading">Come si misura l&#8217;impatto di una migrazione SEO?</h3>



<p class="wp-block-paragraph">Confrontando il traffico dopo la migrazione con un controfattuale: una previsione di quanto traffico ci sarebbe stato senza migrazione, costruita da serie che la migrazione non tocca (un sito gemello, una sezione non migrata, Google Trends). Il pacchetto R CausalImpact automatizza questo approccio.</p>



<h3 class="wp-block-heading">Cosa si può testare con uno split test SEO?</h3>



<p class="wp-block-paragraph">Qualunque modifica applicabile a un gruppo di pagine dello stesso template: title e meta description, struttura degli heading, dati strutturati, blocchi di contenuto, link interni, velocità di caricamento. Le modifiche che toccano tutto il sito insieme, come una migrazione, non si prestano: manca un gruppo di controllo interno, e serve un controfattuale costruito da fuori.</p>



<script type="application/ld+json">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "Cos'è uno split test SEO?", "acceptedAnswer": {"@type": "Answer", "text": "È un esperimento che misura l'effetto di una modifica sul traffico organico dividendo a caso un insieme di pagine simili in due gruppi: le pagine test ricevono la modifica, quelle di controllo restano invariate. Confrontando come cambiano i due gruppi si separa l'effetto della modifica da stagionalità, aggiornamenti dell'algoritmo e concorrenza."}}, {"@type": "Question", "name": "Che differenza c'è tra split test SEO e A/B test?", "acceptedAnswer": {"@type": "Answer", "text": "L'A/B test divide gli utenti: ognuno vede una delle due versioni della stessa pagina. Lo split test SEO divide le pagine, perché Google deve vedere una sola versione di ciascuna. Per questo serve un sito con molte pagine dello stesso tipo."}}, {"@type": "Question", "name": "Quanto deve durare uno split test SEO?", "acceptedAnswer": {"@type": "Answer", "text": "Di solito sei-otto settimane di dati prima della modifica e quattro-sei settimane dopo, escludendo i primi giorni in cui Google sta ancora scansionando le pagine modificate. Allungare il periodo aiuta meno che aumentare il numero di pagine."}}, {"@type": "Question", "name": "Si può fare uno split test con poche pagine?", "acceptedAnswer": {"@type": "Answer", "text": "Con poche decine di pagine si scoprono solo effetti grandi, del 15-20% o più. Per effetti tipici del 5-10% servono di norma alcune centinaia di pagine: conviene calcolare la potenza del test prima di partire."}}, {"@type": "Question", "name": "Come si misura l'impatto di una migrazione SEO?", "acceptedAnswer": {"@type": "Answer", "text": "Confrontando il traffico dopo la migrazione con un controfattuale: una previsione di quanto traffico ci sarebbe stato senza migrazione, costruita da serie che la migrazione non tocca (un sito gemello, una sezione non migrata, Google Trends). Il pacchetto R CausalImpact automatizza questo approccio."}}, {"@type": "Question", "name": "Cosa si può testare con uno split test SEO?", "acceptedAnswer": {"@type": "Answer", "text": "Qualunque modifica applicabile a un gruppo di pagine dello stesso template: title e meta description, struttura degli heading, dati strutturati, blocchi di contenuto, link interni, velocità di caricamento. Le modifiche che toccano tutto il sito insieme, come una migrazione, non si prestano: manca un gruppo di controllo interno, e serve un controfattuale costruito da fuori."}}]}</script>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Il codice dell&#8217;esempio è completo: basta copiarlo in R e cambiare un parametro alla volta.</p>



<ul class="wp-block-list"><li>Si porta <code>effetto</code> a 1 (la modifica non fa nulla) e si ripete l&#8217;analisi con qualche <code>set.seed()</code> diverso. Quante volte il t-test dichiara, per sbaglio, un effetto significativo? (Dovrebbe succedere circa 5 volte su 100.)</li><li>Si riporta <code>effetto</code> a 1,08 e si raddoppiano le pagine, <code>n_pagine &lt;- 400</code>: come si stringe l&#8217;intervallo di confidenza?</li><li>Si &#8220;rompe&#8221; il sorteggio, mettendo nel gruppo test le 100 pagine con più traffico invece di sorteggiarle, e si dà alle pagine grandi una deriva più favorevole. Cosa dice il test placebo?</li></ul>



<p class="wp-block-paragraph">Per l&#8217;ultimo esercizio, un indizio: è il modo più rapido per capire perché il sorteggio non è un dettaglio burocratico.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">C&#8217;è una domanda che abbiamo sfiorato senza affrontarla. In uno split test guardiamo di solito una metrica principale, ma la tentazione di guardarne dieci è fortissima: clic, impressioni, CTR, posizione, e poi ogni categoria di pagine per conto suo. Ogni sguardo in più è un&#8217;occasione in più per trovare un effetto &#8220;significativo&#8221; che non esiste. Quanto in fretta si accumulano questi falsi allarmi, e come si correggono, è il tema dei <strong><a href="https://www.gironi.it/blog/confronti-multipli/">confronti multipli</a></strong>: il prossimo passo del nostro viaggio tra le trappole dei dati.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Sulla sperimentazione online il riferimento, in inglese, è <a href="https://www.amazon.it/dp/1108724264?tag=consulenzeinf-21&amp;ascsubtag=split-test-seo" rel="nofollow sponsored noopener" target="_blank"><em>Trustworthy Online Controlled Experiments</em></a> di Ron Kohavi, Diane Tang e Ya Xu: dalla scelta delle metriche alle insidie dei gruppi di controllo, è scritto da chi ha guidato gli esperimenti di Microsoft, Google e LinkedIn. Per la differenza delle differenze come strumento econometrico, con le sue ipotesi e le sue estensioni, consiglio <a href="https://www.amazon.it/dp/8891906190?tag=consulenzeinf-21&amp;ascsubtag=split-test-seo" rel="nofollow sponsored noopener" target="_blank"><em>Introduzione all&#8217;econometria</em></a> di Stock e Watson, che le dedica una trattazione chiara e piena di esempi.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/split-test-seo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Clustering delle keyword: raggruppare migliaia di query con K-means e clustering gerarchico</title>
		<link>https://www.gironi.it/blog/clustering-keyword/</link>
					<comments>https://www.gironi.it/blog/clustering-keyword/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Tue, 30 Jun 2026 06:43:17 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3946</guid>

					<description><![CDATA[Capita spesso che si esporti l&#8217;elenco delle keyword da Search Console o da un tool, e ci si ritrovi davanti a migliaia di righe. Tremila, diecimila query. Leggerle una per una è impensabile, e raggrupparle a mano &#8220;a sentimento&#8221; è un lavoro lento, soggettivo e impossibile da rifare.Eppure quel raggruppamento serve: vogliamo capire quali grandi &#8230; <a href="https://www.gironi.it/blog/clustering-keyword/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Clustering delle keyword: raggruppare migliaia di query con K-means e clustering gerarchico"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Capita spesso che si esporti l&#8217;elenco delle keyword da Search Console o da un tool, e ci si ritrovi davanti a migliaia di righe. Tremila, diecimila query. <br>Leggerle una per una è impensabile, e raggrupparle a mano &#8220;a sentimento&#8221; è un lavoro lento, soggettivo e impossibile da rifare.<br>Eppure quel raggruppamento serve: vogliamo capire quali grandi famiglie di ricerche esistono nel nostro mercato, per decidere dove creare contenuti, quali pagine costruire, su cosa puntare.<br>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 <em><strong>clustering delle keyword</strong></em>.</p>



<p class="wp-block-paragraph">Abbiamo già affrontato un problema vicino, classificare l&#8217;<a href="https://www.gironi.it/blog/naive-bayes-intento-ricerca/">intento di una query con il Naive Bayes</a> — ma lì avevamo un ingrediente che oggi ci manca: un insieme di esempi <em>già etichettati</em> da cui imparare. Qui le etichette non ce le ha date nessuno. <br>È il territorio del <em>clustering</em>, uno degli strumenti più usati del <a href="https://www.gironi.it/blog/machine-learning-una-guida-per-i-principianti/">machine learning</a>, e in questo articolo lo costruiamo in R con i suoi due algoritmi classici: il <em><strong>K-means</strong></em> e il <strong>clustering gerarchico</strong>.</p>



<span id="more-3946"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list">
<li><a href="#senza-etichette">Raggruppare senza etichette: l&#8217;idea del clustering</a></li>



<li><a href="#k-means">K-means: i centroidi e il problema di scegliere k</a></li>



<li><a href="#leggere-cluster">Leggere i cluster: chi sono questi gruppi?</a></li>



<li><a href="#gerarchico">Clustering gerarchico: il dendrogramma</a></li>



<li><a href="#trappole">Quale metodo, e le trappole</a></li>



<li><a href="#prova-tu">Prova tu</a></li>



<li><a href="#per-approfondire">Per approfondire</a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="senza-etichette" class="wp-block-heading">Raggruppare senza etichette: l&#8217;idea del clustering</h2>



<p class="wp-block-paragraph">La differenza con il Naive Bayes è di principio, non di dettaglio. Lì infatti facevamo <em>apprendimento supervisionato</em>: avevamo query già marcate come informazionali, navigazionali o transazionali, e insegnavamo all&#8217;algoritmo a riconoscere le nuove. Qui facciamo <em>apprendimento non supervisionato</em>: nessuno ci ha detto quali e quanti gruppi esistano. <strong>Il clustering non verifica un&#8217;etichetta che già conosciamo: cerca una struttura che non sapevamo ci fosse.</strong></p>



<p class="wp-block-paragraph">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 (<em>cpc</em>), la posizione media e il numero di parole.<br>La seconda è una nozione di <em>distanza</em>: due keyword sono &#8220;vicine&#8221; se i loro numeri si somigliano. La <strong>distanza più comune è quella euclidea</strong>, la stessa che useremmo su una mappa, solo calcolata in uno spazio a quattro dimensioni (una per ogni metrica).</p>



<p class="wp-block-paragraph">C&#8217;è 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.<br><strong>Prima di calcolare qualsiasi distanza dobbiamo mettere tutte le variabili sulla stessa scala</strong>, standardizzandole — in R con la funzione <code>scale()</code>, 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 &#8220;pesano&#8221; allo stesso modo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="k-means" class="wp-block-heading">K-means: i centroidi e il problema di scegliere k</h2>



<p class="wp-block-paragraph">L&#8217;idea del K-means è quasi ingenua nella sua semplicità. Decidiamo in quanti gruppi (k) vogliamo dividere i dati; l&#8217;algoritmo piazza k punti-rappresentanti, i <em>centroidi</em>, 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&#8217; più coesi, finché non si muovono più.</p>



<p class="wp-block-paragraph">Ciò che l&#8217;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:</p>



\( \text{WCSS} = \sum_{k=1}^{K} \sum_{x \in C_k} \| x-\mu_k \|^2 \\ \)



<p class="wp-block-paragraph">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 (<em>within-cluster sum of squares</em>) è bassa, più i gruppi sono compatti.</p>



<p class="wp-block-paragraph">Resta il punto dolente: k lo dobbiamo decidere noi, prima di iniziare. Un aiuto viene dal <em>metodo del gomito</em>: proviamo diversi valori di k e guardiamo come cala la WCSS. All&#8217;inizio aggiungere un cluster aiuta molto, poi i miglioramenti si fanno marginali; il &#8220;gomito&#8221; 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:</p>



<pre class="wp-block-code"><code>kw &lt;- 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 -&gt; stesso peso)
X &lt;- scale(kw&#091;, c("volume","cpc","posizione","n_parole")])

# metodo del gomito: WCSS per k da 1 a 6
set.seed(1)
wss &lt;- sapply(1:6, function(k) kmeans(X, centers = k, nstart = 10)$tot.withinss)
round(wss, 1)
# &#091;1] 56.0 34.4 20.7 12.4  8.7  6.9</code></pre>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="975" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-elbow.png" alt="Il metodo del gomito: la dispersione interna (WCSS) crolla fino a tre gruppi e poi si appiattisce. Il gomito della curva suggerisce k = 3." class="wp-image-4024" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-elbow.png 975w, https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-elbow-300x185.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Il metodo del gomito: la dispersione interna (WCSS) crolla fino a tre gruppi e poi si appiattisce. Il gomito della curva suggerisce k = 3.</figcaption></figure>
</div></div>



<p class="wp-block-paragraph">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 <strong>k = 3</strong>. Lancio allora il K-means con tre centroidi:</p>



<pre class="wp-block-code"><code>set.seed(1)
km &lt;- kmeans(X, centers = 3, nstart = 25)
kw$cluster &lt;- km$cluster
table(km$cluster)
# 1 2 3
# 4 7 4</code></pre>



<p class="wp-block-paragraph">n.b. l&#8217;argomento <code>nstart = 25</code> fa ripartire l&#8217;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 <code>set.seed(1)</code> serve solo a rendere l&#8217;esempio riproducibile (compresa la numerazione dei cluster, che di per sé è arbitraria).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="leggere-cluster" class="wp-block-heading">Leggere i cluster: chi sono questi gruppi?</h2>



<p class="wp-block-paragraph">Avere tre gruppi non serve a niente finché non capiamo <em>cosa</em> rappresentano. Il modo più diretto è guardare le metriche medie di ciascun cluster.<br>Le calcolo sulla scala originale (non quella standardizzata, che è illeggibile):</p>



<pre class="wp-block-code"><code>aggregate(kw&#091;, 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</code></pre>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">
<figure class="wp-block-image size-large"><img decoding="async" width="975" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-profilo.png" alt="L'impronta dei tre cluster sulle quattro metriche standardizzate: le teste svettano sul volume, il gruppo commerciale sul cpc, l'informazionale su posizione e numero di parole. È la tabella delle medie, letta a colpo d'occhio." class="wp-image-4025" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-profilo.png 975w, https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-profilo-300x185.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">L&#8217;impronta dei tre cluster sulle quattro metriche standardizzate: le teste svettano sul volume, il gruppo commerciale sul cpc, l&#8217;informazionale su posizione e numero di parole. È la tabella delle medie, letta a colpo d&#8217;occhio.</figcaption></figure>
</div></div>



<p class="wp-block-paragraph">Adesso i gruppi parlano.<br>Il <strong>cluster 2</strong> raccoglie le query a volume altissimo (in media 13.300 ricerche), corte (due-tre parole), ben posizionate e con cpc modesto: sono le <em>teste</em> generiche e di marca — &#8220;scarpe running&#8221;, &#8220;nike pegasus&#8221;, &#8220;asics gel nimbus&#8221;. Il <strong>cluster 1</strong> ha volumi bassi, query lunghe (quasi cinque parole), posizioni arretrate e cpc minimo: è il long-tail <em>informazionale</em> — &#8220;come scegliere scarpe running&#8221;, &#8220;differenza scarpe trail e strada&#8221;, &#8220;recensione scarpe trail&#8221;. Il <strong>cluster 3</strong> si distingue per un cpc altissimo (0,95 €) e contiene le query a chiaro taglio <em>commerciale</em> — &#8220;scarpe running offerta&#8221;, &#8220;comprare scarpe trail online&#8221;, &#8220;scarpe running scontate&#8221;, &#8220;negozio scarpe running milano&#8221;.</p>



<p class="wp-block-paragraph">Vale la pena fermarsi un istante su cosa è appena successo. <strong>Senza dare all&#8217;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.</strong><br>L&#8217;allineamento, attenzione, non è magia: emerge perché le metriche che abbiamo scelto (cpc, lunghezza, posizione) <em>tracciano indirettamente</em> l&#8217;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.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="gerarchico" class="wp-block-heading">Clustering gerarchico: il dendrogramma</h2>



<p class="wp-block-paragraph">Il K-means ci ha costretti a scegliere k in anticipo. Il clustering <em>gerarchico</em> ribalta l&#8217;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 <em>dendrogramma</em>: un albero che mostra a quale &#8220;altezza&#8221; (cioè a quale distanza) ogni fusione avviene.</p>



<p class="wp-block-paragraph">Lo costruisco in R calcolando prima la matrice delle distanze, poi l&#8217;albero:</p>



<pre class="wp-block-code"><code>d  &lt;- dist(X)                       # distanze euclidee fra le keyword standardizzate
hc &lt;- hclust(d, method = "ward.D2") # albero gerarchico (criterio di Ward)
plot(hc, labels = kw$keyword)       # il dendrogramma</code></pre>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">
<figure class="wp-block-image size-large"><img decoding="async" width="1050" height="780" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-dendrogramma.png" alt="Il dendrogramma del clustering gerarchico (criterio di Ward): l'albero delle fusioni fra le keyword. Tagliandolo a k = 3 si ritrovano le tre famiglie — teste, commerciale, informazionale — con confini un po' diversi da quelli del K-means." class="wp-image-4026" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-dendrogramma.png 1050w, https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-dendrogramma-300x223.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/clustering-dendrogramma-1024x761.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Il dendrogramma del clustering gerarchico (criterio di Ward): l&#8217;albero delle fusioni fra le keyword. Tagliandolo a k = 3 si ritrovano le tre famiglie — teste, commerciale, informazionale — con confini un po&#8217; diversi da quelli del K-means.</figcaption></figure>
</div></div>



<p class="wp-block-paragraph">Il bello del dendrogramma è che la decisione su <em>quanti</em> gruppi tenere la prendiamo <em>dopo</em> averlo visto, semplicemente &#8220;tagliandolo&#8221; a un&#8217;altezza scelta: un taglio basso lascia molti gruppetti, un taglio alto pochi gruppi grandi. Taglio a tre gruppi e confronto con il K-means:</p>



<pre class="wp-block-code"><code>kw$cluster_hc &lt;- cutree(hc, k = 3)
table(kw$cluster_hc)
# 1 2 3
# 8 3 4</code></pre>



<p class="wp-block-paragraph">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.<br>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&#8217; di più di quella storia.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="trappole" class="wp-block-heading">Quale metodo, e le trappole</h2>



<p class="wp-block-paragraph">La scelta fra i due, nella pratica, segue poche regole semplici. Il <strong>K-means</strong> è veloce ed efficiente anche su decine di migliaia di keyword, ma vuole che gli si dica k e tende a costruire gruppi di forma &#8220;sferica&#8221; e di dimensioni simili. Il <strong>gerarchico</strong> non chiede k in anticipo e regala il dendrogramma — prezioso per <em>vedere</em> come i gruppi si annidano l&#8217;uno nell&#8217;altro — ma diventa pesante quando le keyword sono troppe. La pratica più comune: esplorare in gerarchico su un campione per farsi un&#8217;idea del numero di gruppi, poi applicare il K-means all&#8217;intero set con il k così individuato.</p>



<p class="has-light-gray-background-color has-background wp-block-paragraph">Un avvertimento, il più importante di tutti: <strong>il clustering trova sempre dei gruppi, anche quando non ce ne sono.</strong> 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 <em>nostre</em>, e ognuna cambia il risultato. Un raggruppamento non è una verità scoperta nei dati, è un&#8217;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.</p>



<p class="wp-block-paragraph">C&#8217;è 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).<br>È esattamente il problema che l&#8217;<a href="https://www.gironi.it/blog/una-introduzione-allanalisi-delle-componenti-principali-pca/">analisi delle componenti principali</a> sa alleggerire, comprimendo molte metriche in poche componenti prima di passare il testimone al clustering.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="prova-tu" class="wp-block-heading">Prova tu</h2>



<p class="wp-block-paragraph">Il modo migliore per capire il clustering è vederlo cambiare risposta al variare delle scelte. Riprendendo il codice qui sopra:</p>



<ol class="wp-block-list">
<li>Salta la standardizzazione: lancia il K-means direttamente su <code>kw[, c("volume","cpc","posizione","n_parole")]</code> senza <code>scale()</code>. I gruppi crollano tutti sul volume? È la dimostrazione pratica di perché si standardizza.</li>



<li>Cambia k: prova con quattro o cinque gruppi e rileggi le medie. Il cluster informazionale si spezza in sotto-temi sensati o stai solo tagliando il rumore?</li>



<li>Cambia il criterio di fusione del gerarchico: <code>method = "complete"</code> o <code>"average"</code> al posto di <code>"ward.D2"</code>. Il dendrogramma cambia forma? E i gruppi tagliati a k=3?</li>
</ol>



<p class="wp-block-paragraph">Suggerimento: tieni sempre d&#8217;occhio le medie per cluster con <code>aggregate()</code>. È lì, e non nel codice, che si decide se un raggruppamento è utile o è solo una partizione elegante di niente.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Abbiamo raggruppato le keyword in base a come si <em>comportano</em> — volume, costo, posizione.<br>Ma resta fuori la cosa che per un SEO conta di più: il loro <em>significato</em>. 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 <em>text mining</em>, dove le parole diventano vettori e la similarità si calcola sul linguaggio. Ed è da lì che ripartiremo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 id="per-approfondire" class="wp-block-heading">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi approfondire il clustering — K-means, metodi gerarchici, la scelta del numero di gruppi e le insidie dell&#8217;interpretazione — <em><a href="https://www.amazon.it/dp/1461471370?tag=consulenzeinf-21&#038;ascsubtag=clustering-keyword" rel="nofollow sponsored noopener" target="_blank">An Introduction to Statistical Learning</a></em> di James, Witten, Hastie e Tibshirani dedica all&#8217;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&#8217;esempio giocattolo al clustering su dati veri.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/clustering-keyword/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CTR atteso vs reale: trovare le pagine che rendono meno della loro posizione</title>
		<link>https://www.gironi.it/blog/ctr-atteso-vs-reale/</link>
					<comments>https://www.gironi.it/blog/ctr-atteso-vs-reale/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 08:22:33 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3941</guid>

					<description><![CDATA[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, &#8230; <a href="https://www.gironi.it/blog/ctr-atteso-vs-reale/" class="more-link">Leggi tutto<span class="screen-reader-text"> "CTR atteso vs reale: trovare le pagine che rendono meno della loro posizione"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">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.<br>La domanda che ci poniamo, di solito, è quella sbagliata: non «quanti clic prende?», ma quella più scomoda — «quanti clic <em>dovrebbe</em> prendere, stando dov&#8217;è?». <br>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.<br>Quello che ci manca, per giudicare, è un CTR atteso: il valore con cui confrontare quello reale.</p>



<p class="wp-block-paragraph">Abbiamo avuto modo di vedere, parlando di <a href="https://www.gironi.it/blog/correlazione/">correlazione</a>, che posizione e CTR si muovono insieme lungo una curva ripida; e che il passo successivo — usare una variabile per prevederne un&#8217;altra — è il mestiere della <a href="https://www.gironi.it/blog/regressione-lineare-semplice/">regressione lineare</a>.<br>Qui i due fili si annodano: trasformiamo quella curva in un <strong>CTR atteso</strong> 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.</p>



<span id="more-3941"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list">
<li><a href="#perche-baseline">Perché un CTR, da solo, non significa niente</a></li>



<li><a href="#modellare-la-curva">Modellare la curva del CTR: tre strade</a></li>



<li><a href="#esempio">Un esempio con dati di Search Console</a></li>



<li><a href="#loess-nls">Le altre due strade: loess e nls al lavoro</a></li>



<li><a href="#residui">I residui: chi rende meno di quanto dovrebbe</a></li>



<li><a href="#interpretare">Leggere gli scostamenti senza farsi ingannare</a></li>



<li><a href="#prova-tu">Prova tu</a></li>



<li><a href="#per-approfondire">Per approfondire</a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="perche-baseline" class="wp-block-heading">Perché un CTR, da solo, non significa niente</h2>



<p class="wp-block-paragraph">Esistono tabelle di settore che dicono quanto &#8220;dovrebbe&#8221; 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 <em>nostre</em> 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 <em>rich snippet</em>.<br>Il CTR medio di &#8220;posizione 3&#8221; su un benchmark americano di e-commerce non ha quasi nulla da dire al nostro blog tecnico in italiano.</p>



<p class="wp-block-paragraph">La via d&#8217;uscita è smettere di confrontarci con una tabella esterna e costruirci la curva di riferimento <em>sui nostri stessi dati</em>.<br>Prendiamo tutte le pagine, le loro posizioni medie e i loro CTR, e tracciamo la curva che descrive l&#8217;andamento tipico del CTR al variare della posizione <em>per come funziona il nostro sito</em>. Quella curva diventa il metro: il CTR atteso di una pagina è il valore che la curva le assegna, data la sua posizione.<br>Lo scostamento fra il CTR reale e quel valore atteso è l&#8217;informazione che cercavamo.</p>



<p class="wp-block-paragraph"><strong>Un CTR ha senso solo accanto alla posizione che l&#8217;ha prodotto: è lo scostamento dalla curva, non il numero assoluto, a dirci se una pagina sta lavorando bene o sta lasciando clic sul tavolo.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="modellare-la-curva" class="wp-block-heading">Modellare la curva del CTR: tre strade</h2>



<p class="wp-block-paragraph">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.</p>



<p class="wp-block-paragraph">C&#8217;è però un dettaglio che cambia tutto il modo di ragionare, ed è il tipo di scostamento che ci interessa.<br>A noi non importa che una pagina prenda &#8220;due punti di CTR in meno&#8221; dell&#8217;atteso: in cima alla SERP due punti sono briciole, in fondo sono un raddoppio. Ci interessa lo scostamento <em>moltiplicativo</em> — &#8220;rende la metà di quanto dovrebbe&#8221;, &#8220;rende il doppio&#8221;.<br>E lo scostamento moltiplicativo si maneggia bene su scala logaritmica, dove un rapporto diventa una differenza.</p>



<p class="wp-block-paragraph">La forma più naturale per una curva di questo tipo è la <strong>legge di potenza</strong>, cioè l&#8217;idea che il CTR sia proporzionale alla posizione elevata a un esponente negativo:</p>



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



<p class="wp-block-paragraph">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:</p>



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



<p class="wp-block-paragraph">In altri termini: il logaritmo del CTR è una funzione <em>lineare</em> del logaritmo della posizione. E stimare una retta è esattamente ciò che sappiamo fare con la regressione. Da qui, tre strade per costruire la curva.</p>



<p class="wp-block-paragraph">La prima, e quella che consiglio come cavallo di battaglia, è una <strong>regressione lineare sui logaritmi</strong> — <code>lm(log(ctr) ~ log(posizione))</code>. È in R base, è interpretabile (la pendenza \( b \) è l&#8217;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 <em>moltiplicativi</em>, proprio come ci serve.<br>Si estrapola anche a posizioni poco osservate, e la si può <strong>pesare per le impression</strong> (<code>weights = impression</code>), 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.</p>



<p class="wp-block-paragraph">La seconda è la <strong>regressione non lineare</strong> con <code>nls</code>, 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.</p>



<p class="wp-block-paragraph">La terza è il <strong>lisciamento locale</strong> con <code>loess</code>, che non impone nessuna forma alla curva e la lascia &#8220;disegnare ai dati&#8221;. È perfetto per <em>vedere</em> l&#8217;andamento a colpo d&#8217;occhio, ma traballa sulle code (poche pagine in prima posizione) e soprattutto non estrapola: fuori dall&#8217;intervallo osservato non sa che dire. È uno strumento esplorativo, non il modello su cui costruire i giudizi.</p>



<p class="wp-block-paragraph">Dunque: partiamo dalla regressione log-log pesata per le impression come modello di lavoro, la confrontiamo a occhio con un <code>loess</code> per controllare che non stiamo forzando una forma sbagliata, e passiamo a <code>nls</code> solo se ci serve l&#8217;esponente esplicito. Vediamola al lavoro.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="esempio" class="wp-block-heading">Un esempio con dati di Search Console</h2>



<p class="wp-block-paragraph">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&#8217;esempio, facciamo il percorso inverso — fissiamo un CTR plausibile e ricostruiamo i clic.<br>Costruisco la tabella in R con dodici pagine di esempio (con dentro, di proposito, un paio di casi anomali):</p>



<pre class="wp-block-code"><code>gsc &lt;- 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   &lt;- 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 &lt;- round(gsc$impression * gsc$ctr)</code></pre>



<p class="wp-block-paragraph">Stimo ora la curva del CTR atteso con la regressione sui logaritmi, pesando ogni pagina per le sue impression:</p>



<pre class="wp-block-code"><code>fit &lt;- lm(log(ctr) ~ log(posizione), data = gsc, weights = impression)
round(coef(fit), 3)
# (Intercept)  log(posizione)
#      -1.240          -1.088</code></pre>



<p class="wp-block-paragraph">La pendenza è <strong>−1,088</strong>: 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&#8217;incirca della metà.<br>È la stessa caduta ripida che avevamo intravisto misurando la correlazione, ora però scritta in una formula che possiamo <em>interrogare</em>: dato un numero di posizione, ci restituisce il CTR tipico che quella posizione comporta sul nostro sito.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="loess-nls" class="wp-block-heading">Le altre due strade: loess e nls al lavoro</h2>



<p class="wp-block-paragraph">Abbiamo scelto la regressione log-log come modello di lavoro, ma le tre strade le avevamo promesse tutte. Vale la pena vederle davvero all&#8217;opera sugli stessi dodici dati, per capire cosa aggiungono le altre due — e soprattutto dove inciampano.</p>



<p class="wp-block-paragraph">La <strong>regressione non lineare</strong> 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&#8217;intercetta riportata in scala naturale con l&#8217;<code>exp</code> è la nostra \( a \), la pendenza è la nostra \( b \). La faccio partire in R:</p>



<pre class="wp-block-code"><code>start &lt;- list(a = exp(coef(fit)&#091;1]), b = coef(fit)&#091;2])  # innesco dalla log-log
fit_nls &lt;- nls(ctr ~ a * posizione^b, data = gsc,
               weights = impression, start = start)
round(coef(fit_nls), 3)
#      a       b
#  0.315  -1.073</code></pre>



<p class="wp-block-paragraph">Converge, e restituisce un esponente <strong>−1,073</strong>, 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.<br>Il prezzo lo paghiamo in fragilità: senza quei valori di partenza, o su dati più rumorosi, <code>nls</code> può non convergere affatto e restituirci soltanto un errore.</p>



<p class="wp-block-paragraph">Il <strong>lisciamento locale</strong> con <code>loess</code> fa l&#8217;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:</p>



<pre class="wp-block-code"><code>fit_lo &lt;- loess(ctr ~ posizione, data = gsc, span = 0.9)
round(predict(fit_lo, data.frame(posizione = c(1, 2, 3, 5))), 3)
# &#091;1]    NA 0.173 0.111 0.029</code></pre>



<p class="wp-block-paragraph">Ed ecco il limite in un solo output: alla posizione 1, <code>loess</code> restituisce <strong><code>NA</code></strong>. Il minimo che i nostri dati osservano è 1,3, e fuori da quell&#8217;intervallo <code>loess</code> si rifiuta di pronunciarsi — <em>non estrapola</em>.<br>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.</p>



<p class="wp-block-paragraph">Messe sullo stesso grafico, le tre strade raccontano tutto in un colpo d&#8217;occhio:</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">
<figure class="wp-block-image size-large"><img decoding="async" width="1020" height="690" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-tre-metodi.png" alt="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." class="wp-image-4074" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-tre-metodi.png 1020w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-tre-metodi-300x203.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Le tre stime della curva del CTR atteso sui dodici dati d&#8217;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.</figcaption></figure>
</div></div>



<p class="wp-block-paragraph">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 — <code>loess</code> ondeggia dietro le poche pagine che trova e si ferma di netto al bordo dei dati, mentre <code>lm</code> e <code>nls</code> proseguono lisce anche dove le osservazioni scarseggiano.</p>



<p class="wp-block-paragraph"><strong>Dunque</strong>: la log-log pesata resta il cavallo di battaglia — interpretabile, estrapolabile, con i residui già in scala moltiplicativa. <code>nls</code> la raffina quando serve un esponente pulito da mettere nero su bianco; <code>loess</code> è l&#8217;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.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="residui" class="wp-block-heading">I residui: chi rende meno di quanto dovrebbe</h2>



<p class="wp-block-paragraph">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: <code>rapporto = ctr_reale / ctr_atteso</code>. 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.<br>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:</p>



<pre class="wp-block-code"><code>gsc$ctr_atteso &lt;- exp(predict(fit))           # torno dalla scala log a quella naturale
gsc$rapporto   &lt;- gsc$ctr / gsc$ctr_atteso

gsc$flag &lt;- ifelse(gsc$rapporto &lt; 0.6 &amp; gsc$impression &gt;= 1000, "SOTTO",
             ifelse(gsc$rapporto &gt; 1.4 &amp; gsc$impression &gt;= 1000, "SOPRA", "ok"))

gsc&#091;order(gsc$rapporto),
    c("pagina","posizione","impression","ctr","ctr_atteso","rapporto","flag")]</code></pre>



<p class="wp-block-paragraph">n.b. il <code>predict</code> ci dà il logaritmo del CTR atteso, perché è su quella scala che abbiamo stimato il modello: l&#8217;<code>exp</code> lo riporta in CTR vero e proprio. A rigore l&#8217;<code>exp</code> ci restituisce la <em>mediana</em> 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.<br>L&#8217;output, ordinato dal rapporto più basso al più alto:</p>



<figure class="wp-block-table"><table><thead><tr><th>pagina</th><th>posizione</th><th>impression</th><th>ctr</th><th>ctr_atteso</th><th>rapporto</th><th>flag</th></tr></thead><tbody><tr><td>/modello-attribuzione</td><td>8,3</td><td>900</td><td>0,012</td><td>0,029</td><td>0,41</td><td>ok</td></tr><tr><td>/guida-keyword-research</td><td>3,4</td><td>12500</td><td>0,034</td><td>0,076</td><td>0,44</td><td><strong>SOTTO</strong></td></tr><tr><td>/guida-seo-tecnica</td><td>1,3</td><td>9800</td><td>0,232</td><td>0,218</td><td>1,07</td><td>ok</td></tr><tr><td>/calcolatore-roi-campagne</td><td>4,0</td><td>2100</td><td>0,071</td><td>0,064</td><td>1,11</td><td>ok</td></tr><tr><td>/checklist-audit-seo</td><td>2,1</td><td>5400</td><td>0,150</td><td>0,129</td><td>1,16</td><td>ok</td></tr><tr><td>/analisi-dei-competitor</td><td>7,0</td><td>6100</td><td>0,041</td><td>0,035</td><td>1,18</td><td>ok</td></tr><tr><td>/report-posizionamento</td><td>9,1</td><td>3300</td><td>0,031</td><td>0,026</td><td>1,18</td><td>ok</td></tr><tr><td>/guida-link-building</td><td>6,1</td><td>4200</td><td>0,048</td><td>0,040</td><td>1,19</td><td>ok</td></tr><tr><td>/tutorial-google-analytics</td><td>4,6</td><td>7600</td><td>0,066</td><td>0,055</td><td>1,20</td><td>ok</td></tr><tr><td>/ottimizzazione-meta-tag</td><td>10,2</td><td>1800</td><td>0,028</td><td>0,023</td><td>1,21</td><td>ok</td></tr><tr><td>/glossario-statistica</td><td>5,2</td><td>1500</td><td>0,060</td><td>0,048</td><td>1,25</td><td>ok</td></tr><tr><td>/recensione-tool-seo</td><td>2,8</td><td>8300</td><td>0,171</td><td>0,094</td><td>1,81</td><td><strong>SOPRA</strong></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Il caso che salta all&#8217;occhio è <strong>/guida-keyword-research</strong>: 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.<br>È un&#8217;ipotesi forte e immediatamente azionabile: con ogni probabilità il <em>title</em> e la <em>meta description</em> non stanno facendo il loro lavoro, e una riscrittura potrebbe sbloccare clic che la posizione avrebbe già meritato.</p>



<p class="wp-block-paragraph">All&#8217;estremo opposto c&#8217;è <strong>/recensione-tool-seo</strong>, che in seconda-terza posizione rende quasi il doppio dell&#8217;atteso. Non è un problema, è una lezione: qualcosa in quello <em>snippet</em> funziona benissimo — un titolo magnetico, una <em>rich card</em>, una corrispondenza perfetta con l&#8217;intento — e vale la pena capire cosa, per provare a replicarlo altrove. <strong>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.</strong></p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">
<figure class="wp-block-image size-large"><img decoding="async" width="1110" height="690" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-curva-attesa.png" alt="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)." class="wp-image-4075" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-curva-attesa.png 1110w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-curva-attesa-300x186.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ctr-curva-attesa-1024x637.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Le dodici pagine d&#8217;esempio: posizione media in SERP contro CTR, con l&#8217;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&#8217;esempio: /guida-keyword-research a 0,44× l&#8217;atteso (rosso, sotto-performa) e /recensione-tool-seo a 1,81× (verde, sovra-performa).</figcaption></figure>
</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="interpretare" class="wp-block-heading">Leggere gli scostamenti senza farsi ingannare</h2>



<p class="wp-block-paragraph">C&#8217;è un dettaglio nella tabella che è il cuore di tutto il discorso, e che è facile mancare. <strong>/modello-attribuzione</strong> ha un rapporto di 0,41 — più basso ancora di /guida-keyword-research — eppure non l&#8217;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.<br>Segnalarlo come &#8220;pagina da ottimizzare&#8221; 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.</p>



<p class="has-light-gray-background-color has-background wp-block-paragraph">Un avvertimento: il CTR atteso è un <strong>valore tipico condizionato</strong> — la mediana che la curva associa a una posizione — non una legge di natura. Una pagina può &#8220;sotto-performare&#8221; per ragioni che con il <em>title</em> non c&#8217;entrano nulla: una query di marca che gonfia il CTR dei concorrenti, un <em>featured snippet</em> 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 <em>snippet</em>. E un CTR costruito su poche impression non misura quasi niente: regredirà verso la sua media da solo, come abbiamo visto parlando di <a href="https://www.gironi.it/blog/regressione-verso-la-media/">regressione verso la media</a>. <strong>Un residuo negativo è un&#8217;ipotesi da verificare — &#8220;forse qui il title rende poco&#8221; — non un verdetto da eseguire.</strong></p>



<p class="wp-block-paragraph">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.<br>Lo si vede già nella nostra tabella: dieci pagine su dodici hanno un rapporto sopra l&#8217;1, ma non è che il sito &#8220;sovra-performi&#8221; quasi ovunque. È che l&#8217;unico grosso scostamento negativo, <code>/guida-keyword-research</code>, pesa moltissimo (dodicimila e cinquecento impression) e tira la curva verso il basso, alzando di riflesso il rapporto di tutte le altre. Il &#8220;centro&#8221; 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&#8217;uno.<br>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.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="prova-tu" class="wp-block-heading">Prova tu</h2>



<p class="wp-block-paragraph">Il modo migliore per fissare il meccanismo è metterci le mani. Riprendendo il codice qui sopra, ci sono tre direzioni interessanti da esplorare:</p>



<ol class="wp-block-list">
<li>Aggrega per <strong>query</strong> 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&#8217;occasione persa. La curva e i residui si costruiscono allo stesso identico modo.</li>



<li>Togli il peso delle impression — <code>lm(log(ctr) ~ log(posizione))</code> senza <code>weights</code> — e confronta esponente e verdetti con la versione pesata: quanto sposta le conclusioni l&#8217;aver dato più voce alle pagine con più dati? È il modo per <em>sentire</em> quanto conta la pesatura, invece di prenderla sulla fiducia.</li>



<li>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.</li>
</ol>



<p class="wp-block-paragraph">Suggerimento: la struttura non cambia mai — si stima la curva, si predice l&#8217;atteso, si guarda il rapporto. È giocando con la soglia e con il livello di aggregazione che si capisce quanto di ciò che chiamiamo &#8220;pagina sotto-performante&#8221; sia segnale e quanto, semplicemente, rumore.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Individuare una pagina che rende meno del previsto <em>per la sua posizione</em> è cugino stretto di un altro problema che ogni analista conosce: individuare un giorno che rende meno del previsto <em>nel tempo</em>, un crollo o un picco nel traffico che non torna con l&#8217;andamento abituale. È lo stesso ragionamento sui residui — valore osservato contro valore atteso — spostato dallo spazio delle posizioni all&#8217;asse del tempo, dove l&#8217;atteso lo fornisce l&#8217;andamento storico della <a href="https://www.gironi.it/blog/analisi-delle-serie-storiche0-e-previsioni-di-serie-temporali-in-r-con-il-metodo-holt-winters/">serie</a>. È esattamente il mestiere dell&#8217;<a href="https://www.gironi.it/blog/anomaly-detection/">anomaly detection</a>: distinguere il segnale dal rumore quando i numeri si muovono nel tempo — il passo naturale da cui proseguire.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 id="per-approfondire" class="wp-block-heading">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi approfondire la regressione, le trasformazioni logaritmiche e la lettura dei residui — l&#8217;ossatura esatta del modello che abbiamo costruito qui — <em><a href="https://www.amazon.it/dp/8891906190?tag=consulenzeinf-21&#038;ascsubtag=ctr-atteso-vs-reale" rel="nofollow sponsored noopener" target="_blank">Introduzione all&#8217;econometria</a></em> di Stock e Watson è il riferimento che consiglio: spiega con cura sia il &#8220;perché&#8221; dei logaritmi sia il &#8220;come&#8221; si interpretano i coefficienti, partendo sempre da problemi applicati. A chi volesse spingersi oltre la legge di potenza, verso i metodi locali come il <code>loess</code>, <em><a href="https://www.amazon.it/dp/1461471370?tag=consulenzeinf-21&#038;ascsubtag=ctr-atteso-vs-reale" rel="nofollow sponsored noopener" target="_blank">An Introduction to Statistical Learning</a></em> di James, Witten, Hastie e Tibshirani offre i capitoli giusti, con i laboratori in R sotto mano.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/ctr-atteso-vs-reale/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Naive Bayes: classificare l&#8217;intento delle query con il teorema di Bayes</title>
		<link>https://www.gironi.it/blog/naive-bayes-intento-ricerca/</link>
					<comments>https://www.gironi.it/blog/naive-bayes-intento-ricerca/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Sun, 28 Jun 2026 08:27:16 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3901</guid>

					<description><![CDATA[Nell&#8217;articolo sul multi-armed bandit abbiamo usato Bayes per decidere fra varianti: spostare il traffico verso quella che converte di più mentre il test è ancora in corso. Adesso facciamo un passo di lato, restando però nello stesso impianto di ragionamento: invece di scegliere fra opzioni, vogliamo classificare, cioè attaccare a ogni nuova osservazione l&#8217;etichetta più &#8230; <a href="https://www.gironi.it/blog/naive-bayes-intento-ricerca/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Naive Bayes: classificare l&#8217;intento delle query con il teorema di Bayes"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Nell&#8217;articolo sul <a href="https://www.gironi.it/blog/multi-armed-bandit-thompson/">multi-armed bandit</a> abbiamo usato Bayes per <em>decidere fra varianti</em>: spostare il traffico verso quella che converte di più mentre il test è ancora in corso. Adesso facciamo un passo di lato, restando però nello stesso impianto di ragionamento: invece di scegliere fra opzioni, vogliamo <em>classificare</em>, cioè attaccare a ogni nuova osservazione l&#8217;etichetta più probabile date le sue caratteristiche.<br> Il caso concreto è uno che chiunque faccia SEO conosce bene: <strong>l&#8217;intento dietro una query di ricerca</strong>. Chi cerca &#8220;come fare un dolce&#8221; vuole imparare qualcosa; chi cerca &#8220;comprare scarpe online&#8221; è pronto a tirare fuori la carta di credito. Sono due mondi diversi, e servono contenuti diversi: una guida, un tutorial, un glossario per il primo; una scheda prodotto, un listino, una <em>call to action</em> ben visibile per il secondo. Sbagliare l&#8217;intento significa rispondere alla domanda giusta nel modo sbagliato.</p>



<p class="wp-block-paragraph">Il problema è che le query sono tante e sempre nuove, e classificarle a mano non scala. Ci serve un metodo che impari da un po&#8217; di esempi etichettati e poi se la cavi da solo sulle query mai viste. L&#8217;algoritmo che fa questo con un&#8217;eleganza quasi disarmante è il <em>Naive Bayes</em>, e — come il nome lascia intuire — parte ancora una volta dal <a href="https://www.gironi.it/blog/statistica-bayesiana/">teorema di Bayes</a> che ci accompagna da tutto questo percorso.</p>



<span id="more-3901"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#dal-teorema">Dal teorema al classificatore</a></li><li><a href="#addestramento">Addestrare su query etichettate</a></li><li><a href="#classificare">Classificare le query nuove</a></li><li><a href="#limiti">I limiti del &#8220;naive&#8221;</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="dal-teorema">Dal teorema al classificatore</h2>



<p class="wp-block-paragraph">Riprendiamo il filo. Il teorema di Bayes ci dice come aggiornare la probabilità di un&#8217;ipotesi alla luce dei dati osservati. Qui l&#8217;ipotesi è &#8220;questa query appartiene alla classe <em>informazionale</em>&#8221; (oppure <em>transazionale</em>), e i dati sono le parole che compongono la query. Vogliamo cioè la probabilità di una classe <em>dato</em> il testo: in simboli, <em>P(classe | parole)</em>.</p>



<p class="wp-block-paragraph">Calcolarla direttamente sarebbe un incubo, perché le combinazioni di parole possibili sono sterminate. Bayes ci permette di girare il problema: invece di chiederci quanto è probabile la classe viste le parole, ci chiediamo quanto sono probabili quelle parole vista la classe — una domanda a cui i dati di addestramento sanno rispondere. La regola, prima a parole e poi in formula, è che la probabilità di una classe data una query è proporzionale alla probabilità a priori della classe moltiplicata per la probabilità di osservare quelle parole se la classe fosse quella:</p>



\( P(\text{classe} \mid \text{parole}) \propto P(\text{classe}) \cdot \prod_i P(\text{parola}_i \mid \text{classe}) \\ \)



<p class="wp-block-paragraph">Sciogliamo i simboli. <em>P(classe)</em> è il <strong>prior</strong>: quanto è frequente quella classe in partenza, prima di guardare il testo (se metà delle query d&#8217;esempio sono informazionali, il prior è 0,5). <em>P(parola | classe)</em> è quanto spesso quella parola compare nelle query di quella classe. Il simbolo ∏ è semplicemente il prodotto: moltiplichiamo i contributi di tutte le parole della query. Il segno ∝ (&#8220;proporzionale a&#8221;) ci ricorda che stiamo trascurando un denominatore costante, identico per tutte le classi: visto che alla fine ci interessa solo <em>quale</em> classe vince, possiamo ignorarlo senza danni.</p>



<p class="wp-block-paragraph">Ed eccolo, il punto in cui si annida il &#8220;naive&#8221;. <strong>Moltiplicare le probabilità delle singole parole come se fossero indipendenti l&#8217;una dall&#8217;altra equivale ad assumere che, nota la classe, la presenza di una parola non dica nulla sulla presenza delle altre.</strong> È un&#8217;assunzione palesemente falsa nel linguaggio reale — &#8220;carta&#8221; e &#8220;credito&#8221; non compaiono certo a caso una indipendentemente dall&#8217;altra — ed è proprio questa ingenuità (in inglese <em>naive</em>) a dare il nome all&#8217;algoritmo. La cosa sorprendente, che vedremo, è che pur partendo da un&#8217;ipotesi così grossolana il metodo funziona benissimo nella pratica.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="addestramento">Addestrare su query etichettate</h2>



<p class="wp-block-paragraph">Addestrare un Naive Bayes vuol dire una cosa sola: contare. Per ogni classe contiamo quante volte compare ciascuna parola nelle query di esempio, e da quei conteggi ricaviamo le <em>P(parola | classe)</em>. Niente ottimizzazione, niente iterazioni: si scorrono i dati una volta e si riempie una tabella di frequenze.</p>



<p class="wp-block-paragraph">Partiamo da un piccolo insieme di query etichettate a mano, cinque informazionali e cinque transazionali. Addestro il classificatore in R così:</p>



<pre class="wp-block-code"><code>train &lt;- list(
  info  = c("come fare un dolce", "cos e la statistica", "guida seo principianti",
            "tutorial r gratis", "come funziona il bayesiano"),
  trans = c("comprare scarpe online", "prezzo iphone offerta", "miglior hosting economico",
            "acquista corso seo", "sconto abbonamento palestra")
)
tok &lt;- function(s) unlist(strsplit(tolower(s), "\\s+"))
vocab &lt;- unique(unlist(lapply(unlist(train), tok)))
counts &lt;- lapply(train, function(docs) {
  w &lt;- table(factor(unlist(lapply(docs, tok)), levels = vocab)); w + 1
})
tots &lt;- sapply(counts, sum); V &lt;- length(vocab)
prior &lt;- sapply(train, length) / length(unlist(train))   # 0.5 / 0.5</code></pre>



<p class="wp-block-paragraph">Vediamo cosa succede riga per riga. La funzione <code>tok</code> spezza ogni query in parole minuscole (una tokenizzazione spartana ma sufficiente). <code>vocab</code> è il vocabolario, l&#8217;elenco di tutte le parole distinte viste in addestramento: qui sono 31. Per ogni classe, <code>table(factor(...))</code> conta le occorrenze di ciascuna parola del vocabolario; <code>tots</code> è il totale dei conteggi per classe, e <code>prior</code> qui vale 0,5 e 0,5 perché le due classi hanno lo stesso numero di esempi.</p>



<p class="wp-block-paragraph">C&#8217;è un dettaglio in quel <code>w + 1</code> che merita una sosta, perché è il trucco che tiene in piedi tutto l&#8217;edificio. <strong>Se una parola non compare mai nelle query di una classe, il suo conteggio è zero, e con esso si azzererebbe l&#8217;intero prodotto delle probabilità: una sola parola sconosciuta basterebbe a mandare a zero la probabilità della classe, cancellando il contributo di tutte le altre.</strong> È il classico caso in cui &#8220;moltiplicare per zero&#8221; rovina la festa. La soluzione si chiama <strong>smoothing di Laplace</strong>: aggiungiamo 1 al conteggio di ogni parola, in ogni classe, prima di calcolare le proporzioni. Nessuna parola ha più probabilità esattamente nulla, solo molto piccola.</p>



<p class="wp-block-paragraph">Il prezzo dello smoothing è che ai totali per classe va aggiunto il numero di parole del vocabolario (le 31 unità aggiunte una per parola): per questo <code>tots</code> non vale 18 e 15 — i token grezzi delle due classi — ma 49 e 46. Con questi numeri, per esempio, la parola &#8220;comprare&#8221; (presente una volta fra le transazionali, mai fra le informazionali) ha <em>P(comprare | trans) = 2/46 ≈ 0,043</em> contro <em>P(comprare | info) = 1/49 ≈ 0,020</em>: più del doppio, ed è proprio questo squilibrio che spinge una query verso l&#8217;intento giusto. Una parola neutra come &#8220;seo&#8221;, che compare una volta per parte, resta invece quasi bilanciata fra le due classi e non sposta l&#8217;ago.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="990" height="630" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-impronta.png" alt="L'impronta lessicale delle due classi: P(parola | classe) per alcuni termini del vocabolario, dopo lo smoothing di Laplace. "comprare" e "sconto" pesano più del doppio verso il transazionale, "come" verso l'informazionale, "seo" resta quasi in equilibrio: è questo squilibrio parola per parola a spingere una query verso l'intento giusto." class="wp-image-4032" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-impronta.png 990w, https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-impronta-300x191.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">L&#8217;impronta lessicale delle due classi: P(parola | classe) per alcuni termini del vocabolario, dopo lo smoothing di Laplace. &#8220;comprare&#8221; e &#8220;sconto&#8221; pesano più del doppio verso il transazionale, &#8220;come&#8221; verso l&#8217;informazionale, &#8220;seo&#8221; resta quasi in equilibrio: è questo squilibrio parola per parola a spingere una query verso l&#8217;intento giusto.</figcaption></figure>

</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="classificare">Classificare le query nuove</h2>



<p class="wp-block-paragraph">Con la tabella delle frequenze pronta, classificare una query nuova è davvero un giochetto da ragazzi: si tokenizza, si sommano i logaritmi delle probabilità di ciascuna parola (sommare logaritmi anziché moltiplicare probabilità evita che il prodotto di tanti numeri piccolissimi vada in <em>underflow</em> numerico, ma il risultato è lo stesso), si aggiunge il logaritmo del prior e si sceglie la classe con il punteggio più alto. Calcolo in R:</p>



<pre class="wp-block-code"><code>classify &lt;- function(query) {
  w &lt;- tok(query)
  logp &lt;- log(prior)
  for (cl in names(train)) {
    p &lt;- counts[[cl]][w]; p[is.na(p)] &lt;- 1            # parola fuori vocab: peso neutro
    logp[cl] &lt;- logp[cl] + sum(log(as.numeric(p) / tots[cl]))
  }
  names(which.max(logp))
}
cat("'comprare corso seo sconto' -&gt;", classify("comprare corso seo sconto"), "\n")
cat("'come imparare la statistica'  -&gt;", classify("come imparare la statistica"), "\n")</code></pre>



<p class="wp-block-paragraph">L&#8217;output è netto:</p>



<pre class="wp-block-code"><code>'comprare corso seo sconto' -&gt; trans
'come imparare la statistica'  -&gt; info</code></pre>



<p class="wp-block-paragraph">Come si vede, il classificatore assegna la prima query all&#8217;intento transazionale e la seconda all&#8217;informazionale, esattamente come avrebbe fatto un essere umano. Vale la pena notare come ci arriva. Nella prima query &#8220;comprare&#8221; e &#8220;sconto&#8221; tirano con decisione verso <em>trans</em>, &#8220;seo&#8221; resta neutra, e nemmeno &#8220;corso&#8221; — che in addestramento compariva nel transazionale &#8220;acquista corso seo&#8221; — rema contro: il verdetto è solido. Nella seconda, &#8220;statistica&#8221; è una parola spiccatamente informazionale, e &#8220;imparare&#8221;, pur essendo fuori vocabolario, non fa danni grazie a quel <code>p[is.na(p)] &lt;- 1</code> che assegna alle parole mai viste un peso neutro, identico per entrambe le classi: non potendo dire nulla, semplicemente non vota.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="1110" height="585" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-tiro-alla-fune.png" alt="Il tiro alla fune delle parole: per ogni query, il log-rapporto di verosimiglianza di ciascuna parola. A destra (arancio) la parola spinge verso il transazionale, a sinistra (blu) verso l'informazionale, vicino allo zero le parole neutre o fuori vocabolario (come "imparare", che non vota). La somma dei contributi decide il verdetto." class="wp-image-4033" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-tiro-alla-fune.png 1110w, https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-tiro-alla-fune-300x158.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/naive-bayes-tiro-alla-fune-1024x540.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Il tiro alla fune delle parole: per ogni query, il log-rapporto di verosimiglianza di ciascuna parola. A destra (arancio) la parola spinge verso il transazionale, a sinistra (blu) verso l&#8217;informazionale, vicino allo zero le parole neutre o fuori vocabolario (come &#8220;imparare&#8221;, che non vota). La somma dei contributi decide il verdetto.</figcaption></figure>

</div></div>



<p class="wp-block-paragraph">Cinque query d&#8217;esempio per parte sono pochissime, eppure il meccanismo è già tutto qui. In un caso reale basta sostituire le manciate di query etichettate con qualche centinaio o migliaio di query estratte dalla Search Console e annotate per intento, e lo stesso codice — invariato nella struttura — diventa un classificatore di intento utilizzabile sul serio.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="limiti">I limiti del &#8220;naive&#8221;</h2>



<p class="wp-block-paragraph">Prima di lanciarsi, però, è bene sapere dove il metodo mostra la corda, perché la sua semplicità ha un rovescio.</p>



<p class="wp-block-paragraph">Il limite più evidente è proprio l&#8217;assunzione di indipendenza da cui prende il nome. Trattando ogni parola come slegata dalle altre, il Naive Bayes ignora del tutto l&#8217;ordine e il contesto: per lui &#8220;scarpe da corsa economiche&#8221; e &#8220;economia delle scarpe da corsa&#8221; sono lo stesso sacchetto di parole (<em>bag of words</em>, come dicono gli anglosassoni). Nella classificazione d&#8217;intento questo conta poco, ma in compiti più sottili può ingannare. C&#8217;è poi la questione delle <strong>parole fuori vocabolario</strong>: una query fatta solo di termini mai visti in addestramento finirebbe decisa dal solo prior, cioè da un puro testa o croce — il segnale che il dataset di esempi è troppo magro e va ampliato.</p>



<p class="has-light-gray-background-color has-background wp-block-paragraph">Un avvertimento che vale per qualunque classificatore, e a maggior ragione per uno addestrato su pochi dati: il modello impara <em>esattamente</em> ciò che gli mostriamo, bias compresi. Se le query d&#8217;esempio transazionali contengono tutte la parola &#8220;comprare&#8221;, il classificatore assocerà l&#8217;intento d&#8217;acquisto a quel termine e arrancherà su un &#8220;aggiungi al carrello&#8221; altrettanto transazionale ma lessicalmente diverso. La qualità e la rappresentatività dei dati etichettati contano più della raffinatezza dell&#8217;algoritmo: un Naive Bayes nutrito di esempi vari e bilanciati batte un modello sofisticato addestrato male.</p>



<p class="wp-block-paragraph">Detto questo, il Naive Bayes resta un <em>preziosissimo strumento</em> da avere nella cassetta degli attrezzi: è velocissimo da addestrare, richiede pochi dati per partire, è interpretabile (possiamo sempre andare a vedere quali parole hanno spinto la decisione) e nella classificazione di testo regge il confronto con modelli ben più complessi. È spesso la <em>baseline</em> da battere prima di tirare in ballo artiglieria più pesante — ed è qui che si apre la porta sul machine learning vero e proprio, dove la classificazione si fa con alberi, regressioni e reti che lasciano cadere l&#8217;ipotesi di indipendenza in cambio di più potenza (e meno trasparenza).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Il codice qui sopra è un terreno di gioco perfetto per costruire l&#8217;intuizione. Le query di ricerca non sono solo informazionali o transazionali: ne manca almeno una terza famiglia, quella <strong>navigazionale</strong> (chi cerca &#8220;facebook login&#8221; o &#8220;gironi blog&#8221; vuole solo arrivare a un sito preciso). Ecco qualche modifica da provare:</p>



<ol class="wp-block-list"><li>Aggiungi una classe <code>nav</code> al <code>train</code> con quattro o cinque query navigazionali (&#8220;facebook login&#8221;, &#8220;youtube&#8221;, &#8220;amazon accedi&#8221;, &#8220;gmail posta&#8221;), poi riaddestra: il <code>prior</code> non sarà più 0,5 ma circa un terzo per classe. Come cambiano le classificazioni delle query precedenti?</li><li>Dài in pasto al classificatore una query ambigua come &#8220;recensione iphone&#8221; (informativa? transazionale?) e guarda da che parte pende. Aveva senso il verdetto, viste le parole in addestramento?</li><li>Togli lo smoothing (sostituisci <code>w + 1</code> con <code>w</code>) e prova a classificare una query con una parola rara: cosa succede al punteggio quando un conteggio è zero? È il modo più rapido per vedere con i propri occhi perché Laplace serve.</li></ol>



<p class="wp-block-paragraph">Il bello è che la struttura del codice non cambia: aggiungere una classe vuol dire solo allungare la lista <code>train</code>, e tutto il resto — vocabolario, conteggi, prior, regola di classificazione — si adatta da sé.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Con questo chiudiamo il filo bayesiano che abbiamo dipanato di articolo in articolo: dalla <a href="https://www.gironi.it/blog/conversion-rate-bayesiano/">stima di un conversion rate</a> al confronto fra varianti, dall&#8217;allocazione adattiva del traffico fino a quest&#8217;ultimo salto, dal decidere al classificare. Lo stesso teorema, riproposto ogni volta in una veste diversa, si è rivelato un filo conduttore sorprendentemente robusto. Da qui in avanti la strada si biforca verso il machine learning in senso pieno — gli alberi decisionali, la regressione logistica, le reti — dove i metodi rinunciano alle ipotesi più comode in cambio di potenza, e dove Bayes resta sullo sfondo come la grammatica con cui, in fondo, si impara sempre dai dati.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi passare dal Naive Bayes al machine learning vero e proprio restando con i piedi per terra (e con R sotto mano), <a href="https://www.amazon.it/dp/1461471370?tag=consulenzeinf-21&#038;ascsubtag=naive-bayes-intento-ricerca" rel="nofollow sponsored noopener" target="_blank"><em>An Introduction to Statistical Learning</em></a> di James, Witten, Hastie e Tibshirani è il testo che consiglio. È la porta d&#8217;ingresso più accessibile al machine learning applicato: spiega classificazione, alberi e regressione con il rigore giusto ma senza matematica intimidatoria, e ogni capitolo ha laboratori in R che si possono rifare passo passo. La seconda edizione dedica spazio proprio al Naive Bayes, così il salto da questo articolo al resto del percorso è naturale.</p>



<p class="wp-block-paragraph">Questo articolo fa parte del percorso <a href="https://www.gironi.it/blog/approccio-bayesiano/">«L&#8217;approccio bayesiano»</a>, la guida ragionata agli articoli su statistica e inferenza bayesiana applicate alla SEO.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/naive-bayes-intento-ricerca/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Multi-armed bandit: ottimizzare le varianti mentre il test è ancora in corso</title>
		<link>https://www.gironi.it/blog/multi-armed-bandit-thompson/</link>
					<comments>https://www.gironi.it/blog/multi-armed-bandit-thompson/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 17:04:59 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3888</guid>

					<description><![CDATA[Nell&#8217;articolo sull&#8217;A/B test bayesiano abbiamo confrontato due varianti a campione fisso: si raccolgono i dati per tutta la durata pianificata, si calcola la probabilità che B batta A, si decide. È un metodo solido, ma porta con sé un costo che di solito passa sotto silenzio. Quel costo è il traffico che, per tutta la &#8230; <a href="https://www.gironi.it/blog/multi-armed-bandit-thompson/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Multi-armed bandit: ottimizzare le varianti mentre il test è ancora in corso"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Nell&#8217;articolo sull&#8217;<a href="https://www.gironi.it/blog/ab-test-bayesiano/">A/B test bayesiano</a> abbiamo confrontato due varianti a campione fisso: si raccolgono i dati per tutta la durata pianificata, si calcola la probabilità che B batta A, si decide. È un metodo solido, ma porta con sé un costo che di solito passa sotto silenzio.<br> Quel costo è il traffico che, per tutta la durata del test, continuiamo a mandare sulla variante peggiore. Se a metà esperimento B sta già stravincendo, ogni visitatore assegnato ad A è una conversione che probabilmente stiamo buttando via. <strong>Il test a campione fisso ci fa pagare l&#8217;informazione che raccogliamo: per sapere quale variante è migliore, dobbiamo continuare a mostrare anche quella che sospettiamo essere la peggiore.</strong></p>



<p class="wp-block-paragraph">C&#8217;è un modo per ridurre questo conto, e si chiama <em>multi-armed bandit</em>. L&#8217;idea è spostare il traffico in modo adattivo verso la variante che sta vincendo <em>mentre il test è ancora in corso</em>, invece di aspettare il verdetto finale. In questo articolo lo costruiamo con uno degli algoritmi più eleganti e pratici, il <em>Thompson sampling</em>, che è la naturale prosecuzione del ragionamento <a href="https://www.gironi.it/blog/statistica-bayesiana/">bayesiano</a> che abbiamo seguito finora.</p>



<span id="more-3888"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#esplorazione-sfruttamento">Il dilemma fra esplorazione e sfruttamento</a></li><li><a href="#thompson-sampling">Thompson sampling: lasciar decidere il posterior</a></li><li><a href="#regret-evitato">Quanto si guadagna davvero: il regret evitato</a></li><li><a href="#quando-ha-senso">Quando un bandit ha senso, e quando no</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="esplorazione-sfruttamento">Esplorazione contro sfruttamento</h2>



<p class="wp-block-paragraph">Il nome viene dalle slot machine: un <em>bandito a un braccio solo</em> (<em>one-armed bandit</em>) è la macchinetta del casinò, e immaginiamo di averne davanti diverse, ciascuna con una probabilità di vincita sconosciuta e diversa dalle altre. Abbiamo un numero limitato di gettoni. A ogni giocata dobbiamo scegliere quale braccio tirare. Qual è la strategia che massimizza la vincita totale?</p>



<p class="wp-block-paragraph">La traduzione per chi fa SEO o marketing è immediata: i bracci sono le varianti (tre versioni di un <em>title tag</em>, di una <em>call to action</em>, di una landing page), i gettoni sono i visitatori, la vincita è la conversione. Ogni visitatore va assegnato a una variante, e vogliamo massimizzare le conversioni totali lungo tutto l&#8217;esperimento.</p>



<p class="wp-block-paragraph">Qui nasce la tensione di fondo, quella che rende il problema interessante. Da un lato vorremmo <strong>sfruttare</strong> (<em>exploit</em>) la variante che finora sembra la migliore, per incassare più conversioni possibili. Dall&#8217;altro dobbiamo continuare a <strong>esplorare</strong> (<em>explore</em>) anche le altre, perché &#8220;finora sembra la migliore&#8221; si basa su pochi dati e potrebbe essere un&#8217;impressione sbagliata.<br> È un equilibrio delicato: troppa esplorazione e sprechiamo traffico su varianti mediocri; troppo sfruttamento e rischiamo di incoronare un vincitore sbagliato sulla base di un colpo di fortuna iniziale. <strong>Il dilemma esplorazione-sfruttamento è il cuore di ogni problema di multi-armed bandit: ogni scelta è insieme un&#8217;occasione di guadagno e un&#8217;occasione di apprendimento, e le due cose tirano in direzioni opposte.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="thompson-sampling">Thompson sampling: lasciar decidere il posterior</h2>



<p class="wp-block-paragraph">L&#8217;A/B test classico risolve il dilemma nel modo più rozzo possibile: esplora e basta, in parti uguali, fino alla fine. Metà traffico ad A, metà a B, nessun adattamento. Thompson sampling lo risolve in modo molto più astuto, e quasi sorprendente per quanto è semplice.</p>



<p class="wp-block-paragraph">Riprendiamo il filo bayesiano. Per ogni variante manteniamo un posterior sul suo tasso di conversione: come abbiamo visto stimando il conversion rate, partendo da un prior <a href="https://www.gironi.it/blog/la-distribuzione-beta-spiegata-semplice/">Beta</a> e osservando esiti binari (conversione sì/no), il posterior di ogni braccio è ancora una distribuzione Beta, che si aggiorna a ogni visitatore. All&#8217;inizio, quando non sappiamo nulla, ogni braccio parte da un prior non informativo Beta(1, 1).</p>



<p class="wp-block-paragraph">La regola di Thompson, a parole prima che in formule, è questa: invece di chiederci &#8220;qual è la variante con la media più alta finora?&#8221;, a ogni visitatore <strong>campioniamo un tasso a caso dal posterior di ciascun braccio, e giochiamo il braccio che ha prodotto il campione più alto</strong>. È un modo di scegliere &#8220;in proporzione alla probabilità di essere il migliore&#8221;: una variante di cui siamo molto incerti può ancora vincere l&#8217;estrazione ogni tanto (ed è così che continua a essere esplorata), ma man mano che i dati si accumulano i suoi campioni si concentrano e, se è davvero peggiore, smette di essere scelta quasi da sola.</p>



<p class="wp-block-paragraph">L&#8217;eleganza sta proprio qui: non c&#8217;è un parametro di esplorazione da regolare a mano. L&#8217;incertezza del posterior <em>è</em> il motore dell&#8217;esplorazione. Più un braccio è incerto, più i suoi campioni sono dispersi, più spesso capita che vinca l&#8217;estrazione e venga provato; più diventa certo, più i campioni si stringono attorno al valore vero e il braccio viene giocato (o evitato) con decisione.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="1080" height="555" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-beta-prima-dopo.png" alt="I tre posterior Beta, uno per variante. Dopo 300 visitatori (sinistra) sono larghi e sovrapposti: l'incertezza tiene viva l'esplorazione. A fine esperimento (destra) la variante migliore è stretta e ben separata, mentre le altre due, poco esplorate, restano larghe." class="wp-image-4016" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-beta-prima-dopo.png 1080w, https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-beta-prima-dopo-300x154.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-beta-prima-dopo-1024x526.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">I tre posterior Beta, uno per variante. Dopo 300 visitatori (sinistra) sono larghi e sovrapposti: l&#8217;incertezza tiene viva l&#8217;esplorazione. A fine esperimento (destra) la variante migliore è stretta e ben separata, mentre le altre due, poco esplorate, restano larghe.</figcaption></figure>

</div></div>



<p class="wp-block-paragraph">Simulo in R l&#8217;intero processo su tre varianti con tassi reali del 5%, 7% e 9% (che ovviamente l&#8217;algoritmo non conosce), su 5000 visitatori:</p>



<pre class="wp-block-code"><code>set.seed(7)
true_rates &lt;- c(0.05, 0.07, 0.09)   # 3 varianti, la terza e' la migliore
K &lt;- length(true_rates); N &lt;- 5000
alpha &lt;- rep(1, K); beta_ &lt;- rep(1, K)   # prior Beta(1,1) per braccio
pulls &lt;- rep(0, K); rewards &lt;- rep(0, K)
for (t in 1:N) {
  theta &lt;- rbeta(K, alpha, beta_)        # campiona un tasso per braccio
  arm &lt;- which.max(theta)                 # gioca il braccio campionato migliore
  r &lt;- rbinom(1, 1, true_rates[arm])      # esito (conv si/no)
  alpha[arm] &lt;- alpha[arm] + r; beta_[arm] &lt;- beta_[arm] + (1 - r)
  pulls[arm] &lt;- pulls[arm] + 1; rewards[arm] &lt;- rewards[arm] + r
}</code></pre>



<p class="wp-block-paragraph">Il ciclo è tutto qui. A ogni iterazione campioniamo un <code>theta</code> per braccio (<code>rbeta</code>), scegliamo il massimo (<code>which.max</code>), simuliamo l&#8217;esito (<code>rbinom</code>) e aggiorniamo i parametri del braccio giocato: una conversione aumenta di uno il suo <code>alpha</code>, una mancata conversione il suo <code>beta_</code>. Nessun&#8217;altra logica, nessuna soglia da tarare.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="regret-evitato">Quanto si guadagna: il regret evitato</h2>



<p class="wp-block-paragraph">Vediamo dove ci ha portato la simulazione. La prima cosa da guardare è come si sono distribuiti i 5000 visitatori fra i tre bracci:</p>



<pre class="wp-block-code"><code>cat("pull per braccio:", pulls, "\n")
cat("conv totali bandit:", sum(rewards), "\n")</code></pre>



<p class="wp-block-paragraph">Output: pull per braccio = 359, 278, 4363; conv totali bandit = 424.</p>



<p class="wp-block-paragraph"><strong>Dei 5000 visitatori, ben 4363 sono finiti sulla variante migliore, e solo poco più di 600 in totale sulle due peggiori.</strong> L&#8217;algoritmo, senza che gli avessimo detto nulla sui tassi veri, ha capito da solo quale braccio premiare e ci ha dirottato la stragrande maggioranza del traffico. È esattamente lo sfruttamento che volevamo, raggiunto attraverso un&#8217;esplorazione iniziale appena sufficiente a distinguere i bracci.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="975" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-allocazione-tempo.png" alt="La quota di traffico assegnata a ciascuna variante mentre il test procede: senza alcun intervento, il bandit dirotta la gran parte dei visitatori (4363 su 5000) verso la variante migliore, lasciando le altre due a poco più di 300 ciascuna." class="wp-image-4017" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-allocazione-tempo.png 975w, https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-allocazione-tempo-300x185.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">La quota di traffico assegnata a ciascuna variante mentre il test procede: senza alcun intervento, il bandit dirotta la gran parte dei visitatori (4363 su 5000) verso la variante migliore, lasciando le altre due a poco più di 300 ciascuna.</figcaption></figure>

</div></div>



<p class="wp-block-paragraph">Ora il confronto che dà la misura del vantaggio. Un A/B test classico avrebbe diviso i 5000 visitatori in parti uguali fra le tre varianti, circa 1667 ciascuna, per tutta la durata. Le conversioni attese in quello scenario sono semplicemente la media dei tre tassi moltiplicata per il numero di visitatori:</p>



<pre class="wp-block-code"><code>exp_ab &lt;- sum(true_rates) / K * N
cat("conv attese A/B equipartito:", round(exp_ab), "\n")
cat("regret evitato (circa):", round(sum(rewards) - exp_ab), "conv\n")</code></pre>



<p class="wp-block-paragraph">Output: conv attese A/B equipartito = 350; regret evitato (circa) = 74 conv.</p>



<p class="wp-block-paragraph">Un A/B equipartito si sarebbe portato a casa circa 350 conversioni; il bandit ne ha raccolte 424. La differenza, circa <strong>74 conversioni in più sullo stesso identico traffico</strong>, è ciò che in gergo si chiama <em>regret</em> evitato: il rimpianto, cioè le conversioni perse per aver giocato i bracci sbagliati, che l&#8217;allocazione adattiva ci ha risparmiato. In termini più chiari e diretti, a parità di visitatori il bandit ha convertito oltre il 20% in più, semplicemente non insistendo sulle varianti deboli.<br> Si noti che non abbiamo dovuto sacrificare nulla per ottenerlo: alla fine dell&#8217;esperimento conosciamo comunque il vincitore (anzi, lo conosciamo con grande sicurezza, visti i 4363 dati raccolti su di esso), e nel frattempo abbiamo convertito di più. È il vantaggio strutturale del bandit sul test a campione fisso.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="975" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-cumulate-vs-ab.png" alt="Conversioni cumulate del bandit contro un A/B equipartito: a parità di traffico le due curve divergono, e il divario finale — 424 contro 350, cioè le 74 conversioni in più — è il regret evitato." class="wp-image-4018" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-cumulate-vs-ab.png 975w, https://www.gironi.it/blog/wp-content/uploads/2026/07/bandit-cumulate-vs-ab-300x185.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Conversioni cumulate del bandit contro un A/B equipartito: a parità di traffico le due curve divergono, e il divario finale — 424 contro 350, cioè le 74 conversioni in più — è il regret evitato.</figcaption></figure>

</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="quando-ha-senso">Quando ha senso (e quando no)</h2>



<p class="wp-block-paragraph">Detto questo, un bandit non è la risposta giusta a ogni domanda, ed è importante capire dove brilla e dove invece un A/B test tradizionale resta preferibile.</p>



<p class="wp-block-paragraph">Il bandit dà il meglio di sé quando il traffico è <strong>continuo e di lungo corso</strong> (una pagina sempre attiva, una campagna che gira per mesi) e quando le varianti da confrontare sono <strong>molte</strong>: lì l&#8217;allocazione adattiva ripaga, perché c&#8217;è tempo e volume per spostare il traffico e tante varianti deboli da abbandonare in fretta. È ideale per ottimizzazioni continue in cui l&#8217;obiettivo è massimizzare le conversioni lungo tutto il percorso, non scattare una fotografia statistica pulita in un dato momento.</p>



<p class="wp-block-paragraph">Quando invece l&#8217;obiettivo è proprio quella fotografia — una stima precisa e imparziale di <em>quanto</em> una variante è migliore, magari da difendere davanti a un cliente o da usare per una decisione strategica — il test a campione fisso resta più adatto: proprio perché esplora in parti uguali, raccoglie dati bilanciati su tutte le varianti e produce stime più nette dell&#8217;effetto. Il bandit, concentrandosi presto sul vincitore, raccoglie pochi dati sui perdenti e quindi stima peggio <em>di quanto</em> siano peggiori.</p>



<p class="has-light-gray-background-color has-background wp-block-paragraph">Un avvertimento: il bandit dà per scontato che i tassi di conversione restino stabili nel tempo. Ma il mondo reale è spesso non stazionario — stagionalità, cambi di pubblico, una promozione che parte. Se la variante migliore cambia <em>dopo</em> che l&#8217;algoritmo si è già concentrato su un&#8217;altra, un Thompson sampling ingenuo come quello visto qui fatica ad accorgersene, perché ha smesso di esplorare le alternative. In contesti che cambiano servono varianti che &#8220;dimenticano&#8221; i dati vecchi (per esempio scontando gradualmente le osservazioni passate), altrimenti si rischia di restare ancorati a un vincitore che non lo è più.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">La simulazione è un terreno di gioco perfetto per costruire l&#8217;intuizione. Riprendendo il codice qui sopra, prova a cambiare i numeri di partenza e osserva come si ridistribuiscono i <code>pulls</code>:</p>



<ol class="wp-block-list"><li>Avvicina i tassi veri, per esempio <code>true_rates &lt;- c(0.07, 0.08, 0.09)</code>: con varianti più simili, quanto più traffico serve prima che il braccio migliore si stacchi? La concentrazione dei pull resta così netta?</li><li>Riduci drasticamente il traffico (<code>N &lt;- 500</code>): con pochi visitatori il bandit fa ancora in tempo a individuare il vincitore, o l&#8217;esplorazione si mangia tutto il budget?</li><li>Aggiungi varianti: passa a quattro o cinque bracci. Con più alternative da scartare, il vantaggio sul <code>regret evitato</code> rispetto all&#8217;A/B equipartito cresce o si assottiglia?</li></ol>



<p class="wp-block-paragraph">Suggerimento: la struttura del ciclo non cambia mai, basta modificare <code>true_rates</code> e <code>N</code>. È proprio osservando come il vettore <code>pulls</code> si sbilancia (o non si sbilancia) in funzione della distanza fra i tassi che si capisce davvero cosa fa, sotto il cofano, il Thompson sampling.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Finora abbiamo usato Bayes per <em>decidere fra varianti</em>: stimare un tasso, confrontarne due, allocare il traffico in modo adattivo. Ma lo stesso impianto — un prior, dei dati, un posterior — serve anche per un compito diverso e molto comune: <em>classificare</em>, cioè assegnare a ogni nuova osservazione l&#8217;etichetta più probabile date le sue caratteristiche. È il salto dal decidere al classificare, e l&#8217;algoritmo che lo fa con eleganza disarmante, ancora una volta partendo dal teorema di Bayes, è il <em>Naive Bayes</em>: l&#8217;argomento del prossimo articolo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi approfondire i bandit applicati all&#8217;ottimizzazione dei siti web, <a href="https://www.amazon.it/dp/1449341330?tag=consulenzeinf-21&#038;ascsubtag=multi-armed-bandit-thompson" rel="nofollow sponsored noopener" target="_blank"><em>Bandit Algorithms for Website Optimization</em></a> di John Myles White è il libro che consiglio. È un volumetto agile e dichiaratamente pratico, che mette a confronto A/B test e algoritmi bandit (epsilon-greedy, softmax, UCB) proprio nell&#8217;ottica di chi ottimizza pagine e conversioni, con il codice sotto mano. È il punto di partenza ideale per chi vuole passare dalla simulazione che abbiamo visto a un bandit che gira davvero sul proprio sito.</p>



<p class="wp-block-paragraph">Questo articolo fa parte del percorso <a href="https://www.gironi.it/blog/approccio-bayesiano/">«L&#8217;approccio bayesiano»</a>, la guida ragionata agli articoli su statistica e inferenza bayesiana applicate alla SEO.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/multi-armed-bandit-thompson/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>A/B test bayesiano: non solo &#8220;se&#8221; B è meglio di A, ma &#8220;di quanto&#8221;</title>
		<link>https://www.gironi.it/blog/ab-test-bayesiano/</link>
					<comments>https://www.gironi.it/blog/ab-test-bayesiano/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 07:58:45 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3880</guid>

					<description><![CDATA[Abbiamo avuto modo di esaminare, nell&#8217;articolo sull&#8217;A/B testing classico, come confrontare due varianti con il test per due proporzioni: calcoliamo una statistica, otteniamo un p-value, decidiamo se rifiutare l&#8217;ipotesi nulla. Funziona, ed è il pane quotidiano di chi fa esperimenti online. Ma c&#8217;è uno scarto sottile tra quello che il p-value ci dice e quello &#8230; <a href="https://www.gironi.it/blog/ab-test-bayesiano/" class="more-link">Leggi tutto<span class="screen-reader-text"> "A/B test bayesiano: non solo &#8220;se&#8221; B è meglio di A, ma &#8220;di quanto&#8221;"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Abbiamo avuto modo di esaminare, nell&#8217;articolo sull&#8217;<a href="https://www.gironi.it/blog/ab-testing/">A/B testing classico</a>, come confrontare due varianti con il test per due proporzioni: calcoliamo una statistica, otteniamo un p-value, decidiamo se rifiutare l&#8217;ipotesi nulla. Funziona, ed è il pane quotidiano di chi fa esperimenti online. Ma c&#8217;è uno scarto sottile tra quello che il p-value ci dice e quello che vorremmo davvero sapere.<br> Il p-value risponde a una domanda contorta: &#8220;se A e B fossero identiche, quanto sarebbe improbabile osservare una differenza grande come questa?&#8221;. La domanda che ci interessa nella realtà operativa è un&#8217;altra, molto più diretta: <strong>qual è la probabilità che B sia meglio di A?</strong> E, subito dopo: di quanto è meglio, e quanto possiamo fidarci di quel &#8220;di quanto&#8221;?</p>



<p class="wp-block-paragraph">L&#8217;approccio <a href="https://www.gironi.it/blog/statistica-bayesiana/">bayesiano</a> risponde a queste due domande in modo nativo. In questo articolo lo applichiamo al confronto tra due varianti, riprendendo il filo lasciato in sospeso quando abbiamo stimato <a href="https://www.gironi.it/blog/conversion-rate-bayesiano/">il conversion rate di una singola variante</a>.</p>



<p class="wp-block-paragraph">È la domanda di ogni test di conversione con un risvolto SEO: due versioni della stessa landing page che si contendono lo stesso traffico organico, oppure due formulazioni di title e meta description giudicate dal loro <a href="https://www.gironi.it/blog/ctr-atteso-vs-reale/">CTR reale rispetto a quello atteso</a> nella SERP. Ovunque ci siano <em>successi su tentativi</em> — clic su impression, iscrizioni su visite, conversioni su sessioni — il ragionamento che segue è lo stesso.</p>



<span id="more-3880"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#due-posterior">Due posterior invece di uno: una distribuzione per ciascuna variante</a></li><li><a href="#probabilita-b-vince">Qual è la probabilità che B vinca davvero</a></li><li><a href="#distribuzione-differenza">Di quanto è meglio: la distribuzione della differenza</a></li><li><a href="#quando-fermarsi">Quando fermarsi: expected loss e il problema del peeking</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="due-posterior">Due posterior invece di uno</h2>



<p class="wp-block-paragraph">Quando abbiamo stimato il conversion rate di una singola variante, abbiamo visto che — partendo da un prior <a href="https://www.gironi.it/blog/la-distribuzione-beta-spiegata-semplice/">Beta</a> e osservando dati binomiali — il posterior è ancora una distribuzione Beta. La regola di aggiornamento era una semplice aritmetica: se il prior è Beta(α, β) e osserviamo \( k \) conversioni su \( n \) sessioni, il posterior è:</p>



\( Beta(\alpha + k,\ \beta + (n &#8211; k)) \\ \)



<p class="wp-block-paragraph">Detto a parole: aggiungiamo le conversioni osservate al primo parametro e le mancate conversioni al secondo, e non serve altro.</p>



<p class="wp-block-paragraph">In un A/B test non abbiamo una proporzione sola, ne abbiamo due: una per il controllo (A) e una per il trattamento (B). Il meccanismo, però, è identico: costruiamo <strong>un posterior per ciascuna variante</strong>, in modo indipendente, applicando la stessa regola due volte.</p>



<p class="wp-block-paragraph">Facciamo un esempio al volo. Abbiamo testato due versioni di una landing page che riceve traffico organico, assegnando i visitatori a caso:</p>



<ul class="wp-block-list"><li><strong>Variante A</strong> (controllo): 90 conversioni su 1000 sessioni → tasso grezzo 9,0%</li><li><strong>Variante B</strong> (trattamento): 120 conversioni su 1000 sessioni → tasso grezzo 12,0%</li></ul>



<p class="wp-block-paragraph">Partiamo da un prior non informativo Beta(1, 1) per entrambe — &#8220;non sappiamo nulla, prima dei dati ogni tasso è ugualmente plausibile&#8221;. Applicando la regola, il posterior di A è Beta(91, 911) e quello di B è Beta(121, 881).</p>



<p class="wp-block-paragraph">Costruisco i due posterior in R, campionandoli per simulazione:</p>



<pre class="wp-block-code"><code>set.seed(42)
# Variante A: 90 conv / 1000 ; Variante B: 120 conv / 1000
cA &lt;- 90; nA &lt;- 1000; cB &lt;- 120; nB &lt;- 1000

# Posterior con prior uniforme Beta(1,1)
postA &lt;- rbeta(1e5, 1 + cA, 1 + nA - cA)   # Beta(91, 911)
postB &lt;- rbeta(1e5, 1 + cB, 1 + nB - cB)   # Beta(121, 881)</code></pre>



<p class="wp-block-paragraph">Adesso abbiamo in mano due distribuzioni, non due numeri. Ed è proprio questo il punto: invece di confrontare 9,0% contro 12,0% come fossero due valori fissi, confrontiamo l&#8217;intera incertezza che li circonda. Le domande operative diventano operazioni su queste distribuzioni.</p>



<p class="wp-block-paragraph">Vale la pena tenere a mente l&#8217;immagine: due rilievi di probabilità affiancati sull&#8217;asse dei tassi di conversione. Più quello di B è spostato a destra rispetto a quello di A, e meno i due si sovrappongono, più diventa probabile che B sia davvero la variante migliore.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="1050" height="630" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-due-posterior-1.png" alt="Le due posterior Beta messe a confronto: non due numeri (9% e 12%) ma due distribuzioni, che ancora si sovrappongono un po' — ed è in quella sovrapposizione che vive il dubbio residuo." class="wp-image-4213" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-due-posterior-1.png 1050w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-due-posterior-1-300x180.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-due-posterior-1-1024x614.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Le due posterior Beta messe a confronto: non due numeri (9% e 12%) ma due distribuzioni, che ancora si sovrappongono un po&#8217; — ed è in quella sovrapposizione che vive il dubbio residuo.</figcaption></figure>

</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="probabilita-b-vince">Qual è la probabilità che B vinca?</h2>



<p class="wp-block-paragraph">La prima domanda — quella che il p-value non risponde mai direttamente — è la probabilità che B sia davvero migliore di A.<br> Con i posterior in mano, il calcolo è disarmante: confrontiamo i campioni di B con quelli di A, coppia per coppia, e contiamo in quale frazione dei casi B supera A. Quella frazione <em>è</em> la probabilità che cerchiamo.</p>



<p class="wp-block-paragraph">Calcolo in R la probabilità che B batta A:</p>



<pre class="wp-block-code"><code>cat("P(B&gt;A) =", round(mean(postB &gt; postA), 3), "\n")</code></pre>



<p class="wp-block-paragraph">Output: P(B&gt;A) = 0.985.</p>



<p class="wp-block-paragraph"><strong>C&#8217;è il 98,5% di probabilità che la variante B converta meglio della variante A.</strong><br> Si noti la differenza di registro rispetto al frequentista. Non stiamo dicendo &#8220;la differenza osservata è improbabile sotto l&#8217;ipotesi nulla&#8221;: stiamo dicendo, in modo diretto, che date le evidenze raccolte è quasi certo che B sia la variante migliore. È esattamente l&#8217;affermazione su cui vorremmo basare una decisione — e l&#8217;approccio bayesiano ce la consegna senza giri di parole.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="distribuzione-differenza">Di quanto è meglio: la distribuzione della differenza</h2>



<p class="wp-block-paragraph">Sapere che B vince con probabilità del 98,5% non basta a decidere. Un miglioramento c&#8217;è quasi di sicuro, ma se fosse di due decimi di punto percentuale, forse non varrebbe la pena di mandare in produzione la nuova pagina. La domanda successiva è quindi: <em>di quanto</em> è meglio?</p>



<p class="wp-block-paragraph">Finora abbiamo tenuto A e B separati, un posterior ciascuno. Ma l&#8217;oggetto che ci interessa davvero non è nessuno dei due preso da solo: è la loro distanza. La risposta vive perciò nella <strong>distribuzione della differenza</strong> tra i due posterior. Sottraiamo, campione per campione, il tasso di A da quello di B: otteniamo una nuova distribuzione, quella dell&#8217;uplift. Da lì leggiamo sia il valore tipico (la media) sia un intervallo di credibilità che ne quantifica l&#8217;incertezza.</p>



<p class="wp-block-paragraph">Calcolo in R la differenza e il suo intervallo al 95%:</p>



<pre class="wp-block-code"><code>diff &lt;- postB - postA
cat("uplift medio (punti %) =", round(mean(diff)*100, 2), "\n")
cat("CI 95% differenza =", round(quantile(diff, c(.025,.975))*100, 2), "\n")</code></pre>



<p class="wp-block-paragraph">Output: uplift medio = 3.00 punti %, CI 95% = [0.31, 5.68].</p>



<p class="wp-block-paragraph"><strong>Il guadagno atteso è di circa 3 punti percentuali di conversione, con un intervallo di credibilità al 95% che va da 0,31 a 5,68 punti.</strong><br> Anche qui il significato è diretto, non una proprietà astratta della procedura: c&#8217;è il 95% di probabilità che il vero miglioramento di B su A stia tra 0,3 e 5,7 punti percentuali. L&#8217;intervallo non tocca lo zero, il che conferma — coerentemente con il 98,5% di prima — che B è quasi certamente superiore. Ma il dato prezioso è l&#8217;ampiezza: il miglioramento potrebbe essere modesto (mezzo punto) o robusto (oltre cinque punti), e questa forbice è un&#8217;informazione che la decisione operativa deve tenere presente.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="1080" height="645" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-differenza-1.png" alt="La distribuzione dell'uplift B − A in punti percentuali: l'area a destra dello zero è la probabilità che B vinca (98,5%), la barra azzurra l'intervallo di credibilità al 95%, da 0,31 a 5,68 punti. Un solo grafico risponde a entrambe le domande — «se» e «di quanto»." class="wp-image-4214" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-differenza-1.png 1080w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-differenza-1-300x179.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-differenza-1-1024x612.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">La distribuzione dell&#8217;uplift B − A in punti percentuali: l&#8217;area a destra dello zero è la probabilità che B vinca (98,5%), la barra azzurra l&#8217;intervallo di credibilità al 95%, da 0,31 a 5,68 punti. Un solo grafico risponde a entrambe le domande — «se» e «di quanto».</figcaption></figure>

</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="quando-fermarsi">Quando fermarsi: expected loss e il problema del peeking</h2>



<p class="wp-block-paragraph">Nell&#8217;articolo sull&#8217;A/B testing classico abbiamo dedicato spazio a uno degli errori più insidiosi: il <a href="https://www.gironi.it/blog/peeking-problem/">peeking</a>, cioè sbirciare i dati intermedi e fermarsi appena la differenza sembra significativa. Nell&#8217;impianto frequentista questo gonfia il tasso di falsi positivi, perché ogni occhiata è di fatto un nuovo test sulla stessa ipotesi nulla, e i test ripetuti moltiplicano le occasioni di sbagliare.</p>



<p class="wp-block-paragraph">L&#8217;approccio bayesiano cambia la natura del problema. Qui non ripetiamo un test su un&#8217;ipotesi nulla: aggiorniamo una credenza. Il posterior di oggi diventa il prior di domani, e guardare i dati man mano non &#8220;consuma&#8221; un budget di errore nello stesso modo. Questo non vuol dire che possiamo fermarci a capriccio: ci serve comunque una <strong>regola di arresto</strong> dichiarata in anticipo. E la regola bayesiana naturale non è &#8220;fermati quando P(B&gt;A) è alta&#8221;, ma si basa sull&#8217;<strong>expected loss</strong> — la perdita attesa.</p>



<p class="wp-block-paragraph">L&#8217;idea è questa: se scegliamo B ma in realtà fosse A la variante migliore, sbagliamo, e l&#8217;entità dell&#8217;errore è quanto A batte B in quei casi. La perdita attesa di scegliere B è la media di questo &#8220;rimpianto&#8221; su tutta l&#8217;incertezza residua. A parole: di quanto, in media, ci pentiremmo di aver scelto B se ci sbagliassimo.</p>



<p class="wp-block-paragraph">Calcolo in R la perdita attesa scegliendo B:</p>



<pre class="wp-block-code"><code># Expected loss scegliendo B: perdita attesa se in realta' A fosse meglio
loss_B &lt;- mean(pmax(postA - postB, 0))
cat("expected loss scegliendo B =", round(loss_B*100, 3), "punti %\n")</code></pre>



<p class="wp-block-paragraph">Output: expected loss scegliendo B = 0.007 punti %.</p>



<p class="wp-block-paragraph">La perdita attesa scegliendo B è di appena 0,007 punti percentuali: trascurabile. In termini più chiari e diretti, anche nello scenario sfortunato in cui ci sbagliassimo, il danno medio sarebbe minuscolo. Si fissa allora una soglia di tolleranza <em>prima</em> di iniziare — per esempio &#8220;mi fermo quando la perdita attesa scende sotto 0,01 punti&#8221; — e si lascia correre il test finché non la si raggiunge.</p>



<p class="wp-block-paragraph">Ed è qui che si vede perché la perdita attesa, e non &#8220;P(B&gt;A) è alta&#8221;, è il criterio giusto. Se rifacciamo i conti a ogni tappa del test, man mano che il traffico arriva, le due grandezze non scattano nello stesso momento: la probabilità che B vinca supera il 95% molto prima che la perdita attesa diventi davvero trascurabile.</p>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-eed7543b wp-block-group-is-layout-constrained">

<figure class="wp-block-image size-large"><img decoding="async" width="1140" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-expected-loss-1.png" alt="Al crescere dei dati P(B&gt;A) supera il 95% già intorno ai 570 visitatori per variante, ma la perdita attesa scende sotto la soglia di 0,01 punti solo verso gli 855: fermarsi appena la probabilità «sembra alta» è precisamente la trappola del peeking." class="wp-image-4215" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-expected-loss-1.png 1140w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-expected-loss-1-300x158.png 300w, https://www.gironi.it/blog/wp-content/uploads/2026/07/ab-test-expected-loss-1-1024x539.png 1024w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Al crescere dei dati P(B&gt;A) supera il 95% già intorno ai 570 visitatori per variante, ma la perdita attesa scende sotto la soglia di 0,01 punti solo verso gli 855: fermarsi appena la probabilità «sembra alta» è precisamente la trappola del peeking.</figcaption></figure>

</div></div>



<div class="wp-block-group has-background" style="background-color:#f5f7f9;margin-top:2.5rem;margin-bottom:2.5rem;padding-top:1.5rem;padding-right:1.5rem;padding-bottom:1.5rem;padding-left:1.5rem"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow">
<p>Un avvertimento: la libertà di guardare i dati in corsa non è un permesso di fermarsi quando il risultato ci aggrada. La regola di arresto — la soglia di expected loss, o un livello minimo di P(B&gt;A) — va fissata prima di raccogliere i dati, esattamente come nel frequentista si fissa la dimensione campionaria. Il rigore non sta nel metodo che usiamo, ma nel decidere il criterio prima di vedere i numeri.</p>
</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Un sito di lead generation testa due varianti del modulo di contatto. Sulla variante A si osservano 45 conversioni su 600 sessioni; sulla B, 52 conversioni su 600 sessioni.</p>



<ol class="wp-block-list"><li>Costruisci i due posterior con prior non informativo Beta(1, 1): <code>postA &lt;- rbeta(1e5, 1 + 45, 1 + 555)</code> e l&#8217;analogo per B.</li><li>Calcola <strong>P(B&gt;A)</strong>: B è migliore con probabilità abbastanza alta da convincerti?</li><li>Calcola <strong>uplift medio e intervallo al 95%</strong> della differenza: l&#8217;intervallo tocca lo zero?</li><li>Calcola la <strong>expected loss</strong> scegliendo B. Con questi numeri (più vicini tra loro del caso visto sopra), come cambia rispetto all&#8217;esempio dell&#8217;articolo?</li></ol>



<p class="wp-block-paragraph">Suggerimento: la struttura del codice è identica a quella che abbiamo usato. Cambiano solo i conteggi di partenza — e il risultato, molto meno netto, è proprio il motivo per cui l&#8217;intervallo e la perdita attesa contano più di un semplice &#8220;ha vinto B&#8221;.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Finora abbiamo confrontato due varianti a campione fisso: raccogliamo i dati, calcoliamo, decidiamo. Eppure la probabilità che B sia la migliore l&#8217;abbiamo appena vista ricalcolarsi a ogni tappa, mentre il traffico arriva — e se una variante si sta rivelando nettamente migliore, perché continuare a mandare metà dei visitatori su quella peggiore? Si può fare di meglio: usare quella probabilità in tempo reale per allocare il traffico in modo adattivo, spostandolo verso la variante che sta vincendo mentre il test è ancora in corso. È il salto dal test al <em>bandit</em>, l&#8217;argomento del prossimo articolo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi approfondire l&#8217;A/B testing bayesiano con un taglio pratico e orientato al codice, <a href="https://www.amazon.it/dp/0133902838?tag=consulenzeinf-21&#038;ascsubtag=ab-test-bayesiano" rel="nofollow sponsored noopener" target="_blank"><em>Bayesian Methods for Hackers</em></a> di Cameron Davidson-Pilon è il libro che consiglio. Affronta il ragionamento bayesiano partendo dalla programmazione invece che dalla matematica formale, e dedica un capitolo esplicito proprio al confronto bayesiano tra varianti — probabilità che B vinca, distribuzione della differenza, perdita attesa. È pensato per chi impara meglio leggendo codice che dimostrazioni.</p>



<p class="wp-block-paragraph">Questo articolo fa parte del percorso <a href="https://www.gironi.it/blog/approccio-bayesiano/">«L&#8217;approccio bayesiano»</a>, la guida ragionata agli articoli su statistica e inferenza bayesiana applicate alla SEO.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/ab-test-bayesiano/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Stima bayesiana di un conversion rate: quanto possiamo fidarci dei pochi dati che abbiamo</title>
		<link>https://www.gironi.it/blog/conversion-rate-bayesiano/</link>
					<comments>https://www.gironi.it/blog/conversion-rate-bayesiano/#respond</comments>
		
		<dc:creator><![CDATA[Paolo Gironi]]></dc:creator>
		<pubDate>Thu, 25 Jun 2026 08:34:36 +0000</pubDate>
				<category><![CDATA[statistica]]></category>
		<guid isPermaLink="false">https://www.gironi.it/blog/?p=3871</guid>

					<description><![CDATA[Abbiamo avuto modo di esaminare, nell&#8217;articolo sulle fondamenta della statistica bayesiana, come l&#8217;aggiornamento bayesiano funzioni con la simulazione: generiamo campioni dal prior, simuliamo i dati, filtriamo. Un metodo intuitivo, ma che si scontra con un limite pratico non appena i dati diventano minimamente numerosi. In questo articolo passiamo alla soluzione analitica elegante che l&#8217;approccio bayesiano &#8230; <a href="https://www.gironi.it/blog/conversion-rate-bayesiano/" class="more-link">Leggi tutto<span class="screen-reader-text"> "Stima bayesiana di un conversion rate: quanto possiamo fidarci dei pochi dati che abbiamo"</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Abbiamo avuto modo di esaminare, nell&#8217;articolo sulle <a href="https://www.gironi.it/blog/statistica-bayesiana/">fondamenta della statistica bayesiana</a>, come l&#8217;aggiornamento bayesiano funzioni con la simulazione: generiamo campioni dal prior, simuliamo i dati, filtriamo. Un metodo intuitivo, ma che si scontra con un limite pratico non appena i dati diventano minimamente numerosi.<br> In questo articolo passiamo alla soluzione analitica elegante che l&#8217;approccio bayesiano mette a disposizione per uno dei problemi più comuni nell&#8217;analisi di marketing: stimare un conversion rate con pochi dati.</p>



<p class="wp-block-paragraph">Il problema nasce sempre allo stesso modo. Un piccolo e-commerce ha raccolto 23 conversioni su 412 sessioni. Il tasso grezzo è 23/412 ≈ 5,6%. Un numero preciso, apparentemente. Ma quanta fiducia gli diamo? Potremmo trovarci davanti al 3% vero o al 9% vero — con quel campione, non lo sappiamo. La stima puntuale &#8220;5,6%&#8221; non dice niente sulla propria incertezza.</p>



<span id="more-3871"></span>



<p class="wp-block-paragraph"><strong>Di cosa parleremo</strong>:</p>



<ul class="wp-block-list"><li><a href="#modello-beta-binomiale">Il modello Beta-Binomiale: perché la Beta è la distribuzione naturale per un conversion rate</a></li><li><a href="#prior-non-informativo">Prior non informativo: lasciar parlare i dati</a></li><li><a href="#prior-informativo">Prior informativo: usare lo storico senza barare</a></li><li><a href="#posterior-prior-domani">Il posterior di oggi è il prior di domani</a></li><li><a href="#prova-tu">Prova tu</a></li><li><a href="#per-approfondire">Per approfondire</a></li></ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="modello-beta-binomiale">Dal click singolo al tasso: il modello Beta-Binomiale</h2>



<p class="wp-block-paragraph">Ogni sessione è un evento binario: l&#8217;utente converte oppure no.<br> Con \( n \) sessioni e \( k \) conversioni, il meccanismo generativo è binomiale. Il parametro che vogliamo stimare — il vero tasso di conversione \( \theta \) — è una proporzione: un valore tra 0 e 1.</p>



<p class="wp-block-paragraph">Quando il prior su \( \theta \) è una <a href="https://www.gironi.it/blog/la-distribuzione-beta-spiegata-semplice/">distribuzione Beta</a> e i dati sono binomiali, accade qualcosa di molto comodo: <strong>il posterior è ancora una distribuzione Beta</strong>. Questo si chiama prior <em>coniugato</em>, e significa che l&#8217;aggiornamento bayesiano si riduce a una semplice operazione aritmetica sui parametri.</p>



<p class="wp-block-paragraph">La regola di aggiornamento è questa: se il prior è Beta(α, β), dopo aver osservato \( k \) conversioni su \( n \) sessioni il posterior è:</p>



\( Beta(\alpha + k,\ \beta + (n &#8211; k)) \\ \)



<p class="wp-block-paragraph">In parole: aggiungiamo le conversioni osservate ad α e i fallimenti osservati a β. Il prior Beta(α, β) raccoglie in α le &#8220;conversioni già viste&#8221; (o una credenza equivalente) e in β i &#8220;non-click&#8221;. Ogni nuovo dato aggiorna entrambi i contatori.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prior-non-informativo">Prior non informativo: lasciar parlare i dati</h2>



<p class="wp-block-paragraph">Il prior più neutro possibile su una proporzione è la distribuzione uniforme su [0, 1], che corrisponde a Beta(1, 1): tutti i valori del tasso sono considerati ugualmente plausibili prima di vedere i dati.</p>



<p class="wp-block-paragraph">Il nostro caso: 23 conversioni su 412 sessioni (389 non-conversioni).</p>



<p class="wp-block-paragraph">Calcoliamo il posterior in R:</p>



<pre class="wp-block-code"><code># Dati osservati
conv &lt;- 23; sess &lt;- 412; nonconv &lt;- sess - conv

# Prior NON informativo: uniforme Beta(1,1)
a0 &lt;- 1; b0 &lt;- 1
a1 &lt;- a0 + conv; b1 &lt;- b0 + nonconv      # posterior Beta(24, 390)

cat("Non-informativo: media =", round(a1/(a1+b1), 4), "\n")
cat("  CI 95% =", round(qbeta(c(.025,.975), a1, b1), 4), "\n")</code></pre>



<p class="wp-block-paragraph">Output: media = 0.058, CI 95% = [0.0376, 0.0824].</p>



<p class="wp-block-paragraph"><strong>Il posterior Beta(24, 390) ha media 5,8% e credible interval al 95% compreso tra 3,8% e 8,2%.</strong><br> Il credible interval bayesiano non è un esercizio statistico astratto: significa direttamente che c&#8217;è il 95% di probabilità che il vero tasso di conversione sia tra il 3,8% e l&#8217;8,2%. Non una frequenza su ripetizioni infinite — una probabilità diretta sul parametro.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="975" height="600" src="https://www.gironi.it/blog/wp-content/uploads/2026/07/conversion-rate-posterior.png" alt="Prior non informativo Beta(1,1) e posterior Beta(24,390) dopo 23 conversioni su 412 sessioni: i dati concentrano la stima attorno al 5,8%." class="wp-image-3950" srcset="https://www.gironi.it/blog/wp-content/uploads/2026/07/conversion-rate-posterior.png 975w, https://www.gironi.it/blog/wp-content/uploads/2026/07/conversion-rate-posterior-300x185.png 300w" sizes="(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px" /><figcaption class="wp-element-caption">Prior non informativo Beta(1,1) e posterior Beta(24,390) dopo 23 conversioni su 412 sessioni: i dati concentrano la stima attorno al 5,8%.</figcaption></figure>



<p class="wp-block-paragraph">Con 412 sessioni, l&#8217;incertezza è ancora apprezzabile: quasi 5 punti percentuali di ampiezza. La stima puntuale 5,6% era fuorviante nella sua precisione.</p>



<p class="has-light-gray-background-color has-background wp-block-paragraph">Un avvertimento: il credible interval bayesiano e l&#8217;intervallo di confidenza frequentista hanno numeri simili ma significati profondamente diversi. Il 95% frequentista è una proprietà della procedura (&#8220;se ripetessi l&#8217;esperimento 100 volte, 95 intervalli conterrebbero il vero parametro&#8221;); il 95% bayesiano è un&#8217;affermazione diretta sul parametro nel caso specifico che stiamo analizzando.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prior-informativo">Prior informativo: usare lo storico senza barare</h2>



<p class="wp-block-paragraph">Il prior non informativo è il punto di partenza onesto quando non sappiamo nulla. Ma spesso sappiamo qualcosa: anni di campagne, storico del settore, benchmark di categoria.<br> Il nostro e-commerce ha uno storico di quattro stagioni con un conversion rate medio intorno al 4%. Come traduciamo questa conoscenza in un prior?</p>



<p class="wp-block-paragraph">La distribuzione Beta(8, 192) ha media esattamente 8/(8+192) ≈ 4% e — grazie alla somma α+β = 200 — ha una concentrazione che equivale a &#8220;credere&#8221; ai nostri dati quanto si crede a 200 sessioni fittizie di storico. Non è un numero arbitrario: è una scelta dichiarata e verificabile.</p>



<p class="wp-block-paragraph">Calcoliamo il posterior informativo in R:</p>



<pre class="wp-block-code"><code># Prior INFORMATIVO da storico: media ~4% -&gt; Beta(8, 192)
a0i &lt;- 8; b0i &lt;- 192
a1i &lt;- a0i + conv; b1i &lt;- b0i + nonconv  # posterior Beta(31, 581)

cat("Informativo: media =", round(a1i/(a1i+b1i), 4), "\n")
cat("  CI 95% =", round(qbeta(c(.025,.975), a1i, b1i), 4), "\n")</code></pre>



<p class="wp-block-paragraph">Output: media = 0.0507, CI 95% = [0.0347, 0.0694].</p>



<p class="wp-block-paragraph"><strong>Il posterior informativo Beta(31, 581) dà media 5,1% e credible interval al 95% tra 3,5% e 6,9%.</strong><br> Due cose da notare. Prima: la media scende leggermente da 5,8% a 5,1% — il prior &#8220;tira&#8221; la stima verso il 4% dello storico. Seconda: l&#8217;intervallo si stringe (da 4,4 a 3,4 punti percentuali) — lo storico vale come informazione aggiuntiva, quindi l&#8217;incertezza cala.</p>



<p class="wp-block-paragraph">Con pochi dati, il prior informativo aiuta: aggiunge informazione dove i dati non bastano. Con molti dati — migliaia di sessioni — il prior viene <em>sommerso</em> dai dati e la differenza tra prior informativo e non informativo diventa trascurabile. Questa è una caratteristica fondamentale dell&#8217;inferenza bayesiana: <strong>il prior conta quando i dati sono pochi; i dati vincono sempre, alla lunga</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="posterior-prior-domani">Il posterior di oggi è il prior di domani</h2>



<p class="wp-block-paragraph">L&#8217;eleganza più pratica dell&#8217;approccio bayesiano è l&#8217;aggiornamento sequenziale. Dopo un mese arrivano nuovi dati: 15 conversioni su 300 sessioni aggiuntive. Non dobbiamo ricominciare da zero — il posterior che abbiamo calcolato <em>diventa</em> il nuovo prior.</p>



<p class="wp-block-paragraph">Aggiorniamo in R:</p>



<pre class="wp-block-code"><code># Nuovi dati: 15 conversioni su 300 sessioni aggiuntive
conv2 &lt;- 15; sess2 &lt;- 300
a2 &lt;- a1i + conv2; b2 &lt;- b1i + (sess2 - conv2)   # Beta(46, 866)

cat("Dopo update: media =", round(a2/(a2+b2), 4), "\n")
cat("  CI 95% =", round(qbeta(c(.025,.975), a2, b2), 4), "\n")</code></pre>



<p class="wp-block-paragraph">Output: media = 0.0504, CI 95% = [0.0372, 0.0655].</p>



<p class="wp-block-paragraph">Tre stadi a confronto:</p>



<figure class="wp-block-table"><table><thead><tr><th>Stadio</th><th>Dati accumulati</th><th>Media</th><th>CI 95%</th></tr></thead><tbody><tr><td>Prior puro (prima dei dati)</td><td>—</td><td>4,0%</td><td>[1,8%, 7,1%]</td></tr><tr><td>Dopo primo mese</td><td>23/412</td><td>5,1%</td><td>[3,5%, 6,9%]</td></tr><tr><td>Dopo secondo mese</td><td>38/712</td><td>5,0%</td><td>[3,7%, 6,6%]</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>Il credible interval si è ristretto</strong> ad ogni stadio: 5,3 punti percentuali nel prior puro, 3,4 dopo il primo mese, 2,8 dopo l&#8217;aggiornamento. La media è rimasta stabile: i nuovi dati confermano la stima precedente invece di spostarla, e l&#8217;incertezza cala come atteso.<br> Questo è il vero vantaggio operativo: non occorre aspettare che si accumuli un campione &#8220;abbastanza grande&#8221; prima di fare qualsiasi stima. Si parte da una stima incerta e la si affina progressivamente, con ogni nuovo dato.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="prova-tu">Prova tu</h2>



<p class="wp-block-paragraph">Un sito di lead generation ha un tasso di conversione storico intorno al 2%. Dopo una campagna di ottimizzazione, su 150 sessioni si osservano 8 conversioni.</p>



<ol class="wp-block-list"><li>Costruisci un <strong>prior informativo</strong> che rifletta il 2% storico: prova Beta(4, 196) — ha media esattamente 2%.</li><li>Calcola il <strong>posterior</strong> dopo le 8 conversioni su 150 sessioni.</li><li>Calcola il <strong>credible interval al 95%</strong>.</li><li>Calcola poi con <strong>prior non informativo</strong> Beta(1, 1): il posterior cambia molto? Perché?</li></ol>



<p class="wp-block-paragraph">Suggerimento: la formula è sempre la stessa — <code>qbeta(c(.025, .975), a0 + conv, b0 + nonconv)</code>. L&#8217;unica cosa che cambia è il punto di partenza.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Quello che abbiamo costruito finora è la stima di un tasso su una singola variante. Il passo successivo è confrontare due varianti — una pagina di controllo e una pagina modificata — e calcolare la probabilità bayesiana che una superi l&#8217;altra. È esattamente quello che faremo nel prossimo articolo: l&#8217;A/B test bayesiano.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading" id="per-approfondire">Per approfondire</h3>



<p class="wp-block-paragraph">Se vuoi capire come il ragionamento bayesiano entra nelle decisioni pratiche — dalla previsione dei mercati alla stima dell&#8217;incertezza nei dati reali — <a href="https://www.amazon.it/dp/8860443865?tag=consulenzeinf-21&#038;ascsubtag=conversion-rate-bayesiano" rel="nofollow sponsored noopener" target="_blank"><em>Il segnale e il rumore</em></a> di Nate Silver è il libro che consiglio. Silver dedica capitoli espliciti all&#8217;aggiornamento bayesiano, mostrandolo in contesti concreti (previsioni meteorologiche, politica, sport) che rendono immediato il concetto di &#8220;aggiornare le credenze con nuovi dati&#8221;. È divulgativo ma rigoroso, e si legge d&#8217;un fiato.</p>



<p class="wp-block-paragraph">Questo articolo fa parte del percorso <a href="https://www.gironi.it/blog/approccio-bayesiano/">«L&#8217;approccio bayesiano»</a>, la guida ragionata agli articoli su statistica e inferenza bayesiana applicate alla SEO.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.gironi.it/blog/conversion-rate-bayesiano/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
