Creatività, saturazione e CAC: come misurare con Python l’interazione nell’advertising
Quando una campagna pubblicitaria diventa più costosa, aumentare il budget non è sempre la soluzione migliore. Attraverso simulazioni Python e modelli statistici, analizziamo come la creatività possa influenzare CTR, conversioni e Customer Acquisition Cost, e come il suo effetto cambi al variare della saturazione del pubblico.
👉 Il vero costo della pubblicità su Meta: CPM, algoritmo e reach marginale
Quando aumentare il budget smette di essere la soluzione
Immaginiamo una campagna pubblicitaria che sta funzionando bene.
Il pubblico è stato identificato, il messaggio è coerente con l’offerta e il costo di acquisizione è sostenibile. Il responsabile marketing decide quindi di aumentare il budget.
All’inizio la crescita della spesa produce più impression, più clic e più conversioni.
Poi qualcosa cambia.
Il pubblico potenzialmente interessato non è infinito. Aumentando la pressione pubblicitaria, le stesse persone iniziano a vedere più volte lo stesso annuncio. La frequency, cioè il rapporto tra impression e reach, aumenta:
[math]\displaystyle \text{Frequency} = \frac{\text{Impressions}}{\text{Reach}}[/math]
Il problema non è necessariamente che il prodotto sia diventato meno interessante. È possibile che sia semplicemente aumentata la pressione esercitata sullo stesso pubblico.
A quel punto possono comparire alcuni segnali:
- il CTR diminuisce;
- il costo per clic aumenta;
- il CPM aumenta;
- il tasso di conversione peggiora;
- il CAC marginale cresce;
- le nuove impression producono progressivamente meno conversioni.
Il comportamento più intuitivo è aumentare ancora il budget per cercare di mantenere il volume.
È però proprio qui che può comparire un errore strategico.
Se il problema è la saturazione del pubblico, acquistare ancora più impression sullo stesso pubblico potrebbe non risolvere il problema.
Potrebbe essere necessario intervenire sulla creatività.
Non significa semplicemente produrre un’immagine più bella.
Significa modificare il modo in cui l’annuncio riesce a catturare attenzione, comunicare rilevanza e generare una risposta.
Un nuovo formato, un diverso hook iniziale, un video UGC, una diversa struttura narrativa o una promessa più pertinente possono modificare la risposta dello stesso segmento di utenti.
Nasce quindi una domanda quantitativa:
Quanto può cambiare l’efficienza economica di una campagna quando miglioriamo la creatività, mantenendo invariati pubblico e pressione competitiva?
Questa domanda è interessante perché mette in relazione due variabili che nel marketing vengono spesso analizzate separatamente:
la saturazione del pubblico e la qualità della creatività.
Ed è proprio qui che la Data Science può essere utile.
Dal budget alla funzione di risposta
Un modo semplice per rappresentare il problema è pensare alla campagna come a una funzione di risposta.
Indichiamo con:
- [math]T[/math] = livello di saturazione o, più concretamente, un proxy osservabile come la frequency;
- [math]C[/math] = indice di efficacia della creatività;
- [math]P[/math] = pressione competitiva;
- [math]R[/math] = probabilità di conversione per impression.
Possiamo ipotizzare che:
[math]\displaystyle R = f(T, C, P)[/math]
Una prima intuizione è:
[math]\displaystyle \frac{\partial R}{\partial T} < 0[/math]
quando la saturazione aumenta.
La risposta del pubblico tende a diminuire.
Contemporaneamente possiamo ipotizzare:
[math]\displaystyle \frac{\partial R}{\partial C} > 0[/math]
quando una creatività più efficace aumenta la probabilità di risposta.
Ma la domanda veramente interessante è un’altra.
L’effetto della creatività è uguale quando il pubblico è fresco e quando è saturo?
Probabilmente no.
Ed è qui che entra in gioco l’interazione.
Interazione tra creatività e saturazione
In statistica e machine learning, quando l’effetto di una variabile dipende dal valore di un’altra variabile, introduciamo un interaction term.
Possiamo quindi scrivere un modello logistico:
[math]\displaystyle \begin{aligned}
\text{logit}(R) = \alpha_0 + \alpha_T T + \beta_C C + \gamma_{TC} (T \times C) + \beta_P P
\end{aligned}[/math]
dove:
[math]\displaystyle \text{logit}(R) = \ln\left(\frac{R}{1 – R}\right)[/math]
Il termine fondamentale è:
[math]\displaystyle \gamma_{TC} (T \times C)[/math]
Questo coefficiente non deve essere interpretato automaticamente come una “elasticità incrociata” nel significato microeconomico tradizionale.
È più corretto chiamarlo coefficiente di interazione tra saturazione e creatività.
Se:
[math]\displaystyle \gamma_{TC} > 0[/math]
significa che, nel modello ipotizzato, l’effetto della creatività diventa più positivo all’aumentare della saturazione.
Se invece:
[math]\displaystyle \gamma_{TC} < 0[/math]
la capacità della creatività di migliorare la risposta diminuisce quando il pubblico diventa più saturo.
Questa distinzione è importante.
Elasticità: una lettura più rigorosa
In microeconomia, l’elasticità incrociata misura la variazione percentuale della domanda di un bene rispetto alla variazione percentuale del prezzo di un altro bene.
Nel nostro problema non abbiamo due beni e non stiamo osservando il prezzo di un bene sostituto.
Per questo è più corretto parlare di elasticità della risposta rispetto alla creatività, condizionata dal livello di saturazione.
Supponiamo, per esempio, un modello log-log:
[math]\displaystyle \begin{aligned}
\ln(Y) = \beta_0 + \beta_T \ln(T) + \beta_C \ln(C) + \gamma [\ln(T) \ln(C)] + \beta_P \ln(P) + \epsilon
\end{aligned}[/math]
L’effetto della creatività è:
[math]\displaystyle \frac{\partial \ln(Y)}{\partial \ln(C)} = \beta_C + \gamma \ln(T)[/math]
Quindi non è costante.
Dipende da [math]T[/math].
Questa è una delle conseguenze più interessanti dell’interazione: la stessa variazione percentuale della creatività può avere effetti diversi su pubblici con livelli diversi di saturazione.
Dalla risposta al costo di acquisizione
Per trasformare il modello statistico in una metrica economica dobbiamo collegare impression, clic e conversioni.
Supponiamo di avere:
- [math]\text{CPM}[/math]: costo per mille impression;
- [math]\text{CTR}[/math]: probabilità di clic dato che l’utente ha visto l’impression;
- [math]\text{CVR}[/math]: probabilità di conversione dato che l’utente ha cliccato.
La probabilità di ottenere una conversione da una singola impression è:
[math]\displaystyle P(\text{conversione}) = \text{CTR} \times \text{CVR}[/math]
Il numero atteso di conversioni per 1.000 impression è quindi:
[math]\displaystyle 1000 \times \text{CTR} \times \text{CVR}[/math]
Il costo di 1.000 impression è:
[math]\displaystyle \text{CPM}[/math]
Di conseguenza, il costo di acquisizione atteso è:
[math]\displaystyle \text{CAC} = \frac{\text{CPM}}{1000 \times \text{CTR} \times \text{CVR}}[/math]
Questa formula è fondamentale perché mostra un fatto spesso sottovalutato.
Il CAC non dipende soltanto dal prezzo dell’asta.
Dipende anche dalla capacità dell’annuncio di trasformare le impression in clic e i clic in conversioni.
Per questo un aumento del CPM non implica necessariamente un aumento proporzionale del CAC.
Se contemporaneamente aumentano CTR e CVR, l’efficienza complessiva può migliorare.
Un modello simulativo
Possiamo costruire una funzione logistica:
[math]\displaystyle R = \frac{1}{1 + e^{-z}}[/math]
dove:
[math]\displaystyle z = \alpha_0 + \alpha_T T + \beta_C C + \gamma_{TC} T C + \beta_P P[/math]
I parametri non sono osservazioni di Meta o di un’altra piattaforma.
Sono parametri sintetici utilizzati per costruire uno scenario.
Questo punto è essenziale.
La simulazione non dimostra che un certo incremento della qualità creativa produca realmente un determinato incremento del CTR o del CAC.
Ci permette invece di studiare:
come cambia il sistema quando ipotizziamo diversi livelli di interazione tra creatività e saturazione.
Simulazione Python in Google Colab
Il seguente codice è autosufficiente e può essere eseguito direttamente in Google Colab.
Non richiede file esterni.
# ============================================================
# CREATIVITÀ, SATURAZIONE E CAC
# Simulazione dell'interazione tra creatività e advertising
#
# Compatibile con Google Colab
# ============================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# ------------------------------------------------------------
# 1. Funzione logistica
# ------------------------------------------------------------
def sigmoid(x):
"""
Trasforma il logit in una probabilità compresa tra 0 e 1.
"""
return 1 / (1 + np.exp(-x))
# ------------------------------------------------------------
# 2. Modello di advertising
# ------------------------------------------------------------
def simula_campagna(
creative_score=50,
saturation=0.40,
competition=1.20,
interaction=5.50,
max_creative_discount=0.35
):
"""
Simula una campagna pubblicitaria al variare della
qualità creativa.
Parametri
---------
creative_score : float
Qualità creativa selezionata, da 1 a 100.
saturation : float
Proxy normalizzato della saturazione del pubblico,
compreso tra 0 e 1.
competition : float
Pressione competitiva sull'asta.
interaction : float
Intensità del termine di interazione T * C.
max_creative_discount : float
Parametro puramente simulativo che rappresenta
la riduzione ipotetica del CPM associata alla qualità
creativa.
NON rappresenta un valore osservato di Meta.
Returns
-------
pandas.DataFrame
Risultati della simulazione per tutti i livelli
di qualità creativa.
"""
# --------------------------------------------------------
# Spazio della qualità creativa
# --------------------------------------------------------
C = np.linspace(0.01, 1.00, 100)
# --------------------------------------------------------
# Parametri del modello
#
# Sono PARAMETRI SINTETICI.
# Servono esclusivamente per costruire uno scenario.
# --------------------------------------------------------
alpha_0 = -4.5
alpha_T = -2.5
beta_C = 4.0
beta_P = -0.8
# --------------------------------------------------------
# CPM di riferimento
# --------------------------------------------------------
cpm_base = 10.50
# --------------------------------------------------------
# Modello logistico
#
# La parte T * C rappresenta l'interazione tra
# saturazione e qualità creativa.
# --------------------------------------------------------
logit = (
alpha_0
+ alpha_T * saturation
+ beta_C * C
+ interaction * saturation * C
+ beta_P * competition
)
# Probabilità di conversione per impression.
#
# In questo modello rappresentiamo direttamente
# CTR * CVR.
conversion_per_impression = sigmoid(logit)
# --------------------------------------------------------
# CPM simulato
#
# Il termine "discount" è un'ASSUNZIONE del modello.
# Non descrive l'algoritmo reale di una piattaforma.
# --------------------------------------------------------
creative_discount = max_creative_discount * C
cpm = (
cpm_base
* competition
* (1 - creative_discount)
)
# --------------------------------------------------------
# CAC atteso
#
# CAC = CPM / (1000 * conversioni per impression)
# --------------------------------------------------------
cac = (
cpm
/ (1000 * conversion_per_impression)
)
# --------------------------------------------------------
# DataFrame finale
# --------------------------------------------------------
results = pd.DataFrame({
"Creative_Score": C * 100,
"Creative_Normalized": C,
"Conversion_per_Impression": conversion_per_impression,
"CPM": cpm,
"CAC": cac
})
return results
# ------------------------------------------------------------
# 3. Eseguiamo una simulazione di riferimento
# ------------------------------------------------------------
df = simula_campagna(
creative_score=50,
saturation=0.40,
competition=1.20,
interaction=5.50
)
df.head()
Visualizzare il comportamento del modello
Possiamo ora osservare tre grandezze:
- probabilità di conversione per impression;
- CPM;
- CAC.
# ============================================================
# VISUALIZZAZIONE DELLA SIMULAZIONE
# ============================================================
fig, axes = plt.subplots(
3,
1,
figsize=(10, 14)
)
# ------------------------------------------------------------
# Grafico 1: conversione
# ------------------------------------------------------------
axes[0].plot(
df["Creative_Score"],
df["Conversion_per_Impression"] * 100,
linewidth=2
)
axes[0].set_title(
"Risposta del pubblico al variare della qualità creativa"
)
axes[0].set_xlabel(
"Creative Score"
)
axes[0].set_ylabel(
"Conversione per impression (%)"
)
axes[0].grid(True, alpha=0.3)
# ------------------------------------------------------------
# Grafico 2: CPM
# ------------------------------------------------------------
axes[1].plot(
df["Creative_Score"],
df["CPM"],
linewidth=2
)
axes[1].set_title(
"CPM simulato"
)
axes[1].set_xlabel(
"Creative Score"
)
axes[1].set_ylabel(
"CPM (€)"
)
axes[1].grid(True, alpha=0.3)
# ------------------------------------------------------------
# Grafico 3: CAC
# ------------------------------------------------------------
axes[2].plot(
df["Creative_Score"],
df["CAC"],
linewidth=2
)
axes[2].set_title(
"CAC atteso"
)
axes[2].set_xlabel(
"Creative Score"
)
axes[2].set_ylabel(
"CAC (€)"
)
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Il punto importante non è il valore assoluto prodotto dalla simulazione.
È la forma delle curve.
Il modello consente di osservare come un miglioramento della creatività possa aumentare la probabilità di conversione e, contemporaneamente, modificare il costo economico dell’acquisizione.
Interazione: cosa succede quando aumenta la saturazione?
Adesso facciamo qualcosa di più interessante.
Manteniamo costanti:
- pressione competitiva;
- parametri creativi;
- struttura del modello.
Cambiamo soltanto la saturazione.
# ============================================================
# CONFRONTO TRA DIVERSI LIVELLI DI SATURAZIONE
# ============================================================
saturation_levels = [0.10, 0.40, 0.70, 0.90]
plt.figure(figsize=(10, 6))
for saturation in saturation_levels:
scenario = simula_campagna(
saturation=saturation,
competition=1.20,
interaction=5.50
)
plt.plot(
scenario["Creative_Score"],
scenario["CAC"],
linewidth=2,
label=f"Saturazione = {saturation:.2f}"
)
plt.title(
"CAC al variare della creatività e della saturazione"
)
plt.xlabel(
"Creative Score"
)
plt.ylabel(
"CAC (€)"
)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Se il termine di interazione è positivo, l’effetto della creatività cambia con il livello di saturazione.
La creatività non è quindi necessariamente una leva con effetto costante.
Il suo valore marginale può dipendere dal contesto nel quale viene utilizzata.
La simulazione non deve suggerire la risposta
Una simulazione è utile quando ci permette di esplorare le conseguenze di ipotesi diverse.
Diventa invece problematica quando i parametri vengono scelti in modo tale da produrre fin dall’inizio il risultato che vogliamo raccontare.
Nel nostro caso il parametro più delicato è il coefficiente di interazione:
[math]\gamma_{TC}[/math]
che compare nel termine:
[math]\gamma_{TC}(T \times C)[/math]
Nel primo esempio della simulazione avevamo impostato:
interaction = 5.50
Si tratta di un valore positivo.
Questa scelta implica già che, nel modello, l’effetto della creatività diventi più favorevole all’aumentare della saturazione.
Il risultato della prima simulazione è quindi coerente con l’ipotesi incorporata nel modello.
Non è una scoperta empirica.
È una conseguenza dell’ipotesi.
Questa distinzione è fondamentale quando si utilizza Python per costruire modelli quantitativi.
Prima il modello neutrale
Per evitare un bias narrativo, conviene partire dal caso più semplice:
[math]\gamma_{TC} = 0[/math]
In questo scenario non esiste interazione tra creatività e saturazione.
Il modello diventa:
[math]\displaystyle \text{logit}(R) = \alpha_0 + \alpha_T T + \beta_C C + \beta_P P[/math]
La creatività ha quindi un effetto proprio, ma questo effetto non dipende dal livello di saturazione.
Questo è il nostro punto di riferimento.
Non stiamo ancora dicendo che la creatività protegga il CAC dalla saturazione.
Stiamo semplicemente chiedendo:
Cosa succederebbe se creatività e saturazione agissero come variabili indipendenti?
Possiamo verificarlo con Python.
# ============================================================
# CASO BASE: NESSUNA INTERAZIONE
#
# gamma = 0
#
# In questo scenario l'effetto della creatività non dipende
# dalla saturazione del pubblico.
# ============================================================
scenario_neutro = simula_campagna(
saturation=0.70,
competition=1.20,
interaction=0.0
)
plt.figure(figsize=(10, 6))
plt.plot(
scenario_neutro["Creative_Score"],
scenario_neutro["CAC"],
linewidth=2
)
plt.title(
"Caso neutro: interazione tra creatività e saturazione = 0"
)
plt.xlabel(
"Creative Score"
)
plt.ylabel(
"CAC (€)"
)
plt.grid(True, alpha=0.3)
plt.show()

Questo è il nostro benchmark.
La curva può comunque mostrare che una creatività migliore riduce il CAC.
Ma questo non significa che la creatività sia particolarmente efficace sui pubblici saturi.
Significa soltanto che, nel modello, la creatività ha un effetto positivo sulla risposta.
Che cosa cambia quando γ diventa positivo?
Ora introduciamo:
[math]\gamma_{TC} > 0[/math]
Per esempio:
python
interaction = 5.50
L’equazione diventa:
[math]\displaystyle \text{logit}(R) = \alpha_0 + \alpha_T T + \beta_C C + 5.5 (T \times C) + \beta_P P[/math]
La creatività e la saturazione non sono più indipendenti.
L’effetto della creatività dipende dal livello di [math]T[/math].
Possiamo confrontare direttamente i due scenari.
# ============================================================
# CONFRONTO:
# 1. gamma = 0
# 2. gamma > 0
# ============================================================
scenario_gamma_0 = simula_campagna(
saturation=0.70,
competition=1.20,
interaction=0.0
)
scenario_gamma_pos = simula_campagna(
saturation=0.70,
competition=1.20,
interaction=5.50
)
plt.figure(figsize=(10, 6))
plt.plot(
scenario_gamma_0["Creative_Score"],
scenario_gamma_0["CAC"],
linewidth=2,
label="γ = 0"
)
plt.plot(
scenario_gamma_pos["Creative_Score"],
scenario_gamma_pos["CAC"],
linewidth=2,
label="γ = +5.5"
)
plt.title(
"Effetto dell'interazione positiva sul CAC"
)
plt.xlabel(
"Creative Score"
)
plt.ylabel(
"CAC (€)"
)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Ora possiamo formulare una domanda più interessante:
La maggiore efficacia della creatività sui pubblici saturi è realmente presente nei dati oppure è soltanto un effetto della nostra ipotesi?
La simulazione, da sola, non può rispondere.
Può però mostrarci cosa implicherebbe quell’ipotesi.
Il caso che non dobbiamo ignorare: γ < 0
Esiste però un terzo scenario.
Possiamo avere:
[math]\gamma_{TC} < 0[/math]
In questo caso l’interazione tra creatività e saturazione è negativa.
È un risultato particolarmente interessante perché ribalta la narrativa.
Significa che l’efficacia marginale della creatività diminuisce quando il pubblico diventa più saturo.
In altre parole:
una creatività che funziona bene su un pubblico fresco potrebbe perdere parte della propria efficacia proprio quando aumenta la frequency.
È perfettamente plausibile come ipotesi di modello.
Un pubblico esposto ripetutamente allo stesso tipo di messaggio può diventare progressivamente meno reattivo. Una nuova creatività potrebbe migliorare la situazione, ma potrebbe anche non essere sufficiente a compensare l’effetto della saturazione.
Peggio ancora, se il nuovo asset è percepito come poco rilevante dal pubblico già esposto, l’interazione potrebbe risultare negativa.
Non dobbiamo quindi assumere a priori che:
[math]\gamma > 0[/math]
La domanda empirica deve essere:
[math]\displaystyle \gamma \begin{cases} > 0 \\ = 0 \\ < 0 \end{cases}[/math]
Quale dei tre scenari è compatibile con i dati?
Simuliamo anche l’interazione negativa
Possiamo modificare il codice precedente:
# ============================================================
# CONFRONTO TRA TRE REGIMI
#
# gamma < 0 -> interazione negativa
# gamma = 0 -> nessuna interazione
# gamma > 0 -> interazione positiva
# ============================================================
interaction_values = [
-5.50,
0.00,
5.50
]
plt.figure(figsize=(10, 6))
for gamma in interaction_values:
scenario = simula_campagna(
saturation=0.70,
competition=1.20,
interaction=gamma
)
plt.plot(
scenario["Creative_Score"],
scenario["CAC"],
linewidth=2,
label=f"γ = {gamma:+.2f}"
)
plt.title(
"Tre possibili regimi di interazione"
)
plt.xlabel(
"Creative Score"
)
plt.ylabel(
"CAC (€)"
)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Il grafico permette di confrontare tre ipotesi:
| Interazione | Interpretazione |
|---|---|
| [math]\gamma < 0[/math] | la creatività perde efficacia relativa quando aumenta la saturazione |
| [math]\gamma = 0[/math] | l’effetto della creatività non dipende dalla saturazione |
| [math]\gamma > 0[/math] | la creatività diventa relativamente più efficace quando aumenta la saturazione |
Questa è una rappresentazione molto più corretta del problema.
Non abbiamo più una simulazione costruita per raccontare una sola storia.
Abbiamo un modello con tre possibili storie.
Il caso negativo è particolarmente interessante dal punto di vista manageriale
Consideriamo il caso:
[math]\gamma < 0[/math]
Il significato non è semplicemente:
“la creatività funziona male.”
L’interpretazione è più sottile.
La creatività può continuare ad avere un effetto positivo:
[math]\displaystyle \frac{\partial R}{\partial C} > 0[/math]
ma questo effetto può diventare progressivamente più debole quando aumenta [math]T[/math].
In altre parole, possiamo avere contemporaneamente:
[math]\displaystyle \frac{\partial R}{\partial C} > 0[/math]
e:
[math]\displaystyle \frac{\partial^2 R}{\partial C \partial T} < 0[/math]
La prima derivata dice che migliorare la creatività aumenta la risposta.
La seconda dice che l’effetto di questo miglioramento diminuisce quando aumenta la saturazione.
È proprio questo che un semplice confronto tra “creatività alta” e “creatività bassa” rischia di non catturare.
Un esempio intuitivo
Supponiamo di avere due pubblici.
Pubblico A: fresco
[math]T = 0.20[/math]
Pubblico B: saturo
[math]T = 0.80[/math]
Supponiamo di aumentare il Creative Score da 40 a 80.
Se:
[math]\gamma > 0[/math]
potremmo osservare un miglioramento relativamente maggiore sul pubblico B.
La creatività sembra quindi una leva particolarmente utile proprio quando aumenta la pressione sul pubblico.
Se invece:
[math]\gamma < 0[/math]
il risultato può essere opposto.
Il passaggio da 40 a 80 potrebbe produrre un miglioramento importante sul pubblico A, ma un miglioramento molto più piccolo sul pubblico B.
Questo cambia completamente la strategia.
Nel primo caso potremmo pensare:
“Il pubblico è saturo: investiamo nel testing creativo.”
Nel secondo:
“Il pubblico è saturo e la creatività non riesce più a compensare il deterioramento: dobbiamo intervenire anche sul targeting, sull’audience o sulla struttura della campagna.”
Questa è una distinzione molto più utile di una semplice raccomandazione del tipo “fai creatività migliori”.
La derivata mista rende il concetto ancora più chiaro
Nel modello lineare sul logit:
[math]\displaystyle z = \alpha_0 + \alpha_T T + \beta_C C + \gamma T C + \beta_P P[/math]
la derivata dell’effetto della creatività rispetto alla saturazione è:
[math]\displaystyle \frac{\partial^2 z}{\partial C \partial T} = \gamma[/math]
Quindi:
- se [math]\gamma > 0[/math], l’effetto marginale della creatività aumenta con la saturazione;
- se [math]\gamma = 0[/math], non cambia;
- se [math]\gamma < 0[/math], diminuisce.
È una delle ragioni per cui il termine di interazione è così utile.
Non ci limitiamo a chiedere:
“La creatività funziona?”
Chiediamo:
“In quali condizioni la creatività funziona meglio o peggio?”
Questa seconda domanda è molto più vicina alla logica della Data Science applicata.
Un quarto grafico: l’effetto marginale della creatività
Possiamo rendere ancora più esplicito il fenomeno.
Invece di rappresentare soltanto il CAC, calcoliamo la variazione della risposta ottenuta aumentando leggermente il Creative Score.
# ============================================================
# EFFETTO MARGINALE DELLA CREATIVITÀ
#
# Confrontiamo l'effetto di un piccolo aumento del Creative
# Score a diversi livelli di saturazione.
# ============================================================
def effetto_marginale_creativita(
saturation,
competition,
interaction,
delta=1
):
"""
Calcola la variazione approssimata della probabilità
di conversione per impression ottenuta aumentando
il Creative Score di 'delta' punti.
"""
base = simula_campagna(
saturation=saturation,
competition=competition,
interaction=interaction
)
# Troviamo il punto più vicino a Creative Score = 50
base_index = (
np.abs(
base["Creative_Score"] - 50
)
).argmin()
base_response = (
base.iloc[base_index]
["Conversion_per_Impression"]
)
# Ricostruiamo un modello con un Creative Score
# leggermente superiore.
#
# Per semplicità utilizziamo la formula logistica
# direttamente.
C1 = 0.50
C2 = (50 + delta) / 100
alpha_0 = -4.5
alpha_T = -2.5
beta_C = 4.0
beta_P = -0.8
z1 = (
alpha_0
+ alpha_T * saturation
+ beta_C * C1
+ interaction * saturation * C1
+ beta_P * competition
)
z2 = (
alpha_0
+ alpha_T * saturation
+ beta_C * C2
+ interaction * saturation * C2
+ beta_P * competition
)
r1 = sigmoid(z1)
r2 = sigmoid(z2)
return r2 - r1
# ------------------------------------------------------------
# Confrontiamo tre valori di gamma
# ------------------------------------------------------------
saturation_grid = np.linspace(
0.05,
0.95,
50
)
gamma_values = [
-5.5,
0.0,
5.5
]
plt.figure(figsize=(10, 6))
for gamma in gamma_values:
marginal_effect = [
effetto_marginale_creativita(
saturation=t,
competition=1.20,
interaction=gamma
)
for t in saturation_grid
]
plt.plot(
saturation_grid,
marginal_effect,
linewidth=2,
label=f"γ = {gamma:+.1f}"
)
plt.axhline(
0,
linewidth=1
)
plt.title(
"Effetto marginale della creatività al variare della saturazione"
)
plt.xlabel(
"Saturazione"
)
plt.ylabel(
"Δ conversione per impression"
)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Questo grafico è concettualmente ancora più importante del grafico del CAC.
Mostra direttamente quanto cambia la risposta quando aumentiamo marginalmente la creatività.
Con:
[math]\gamma > 0[/math]
l’effetto marginale cresce con la saturazione.
Con:
[math]\gamma < 0[/math]
l’effetto marginale diminuisce.
Perché il caso negativo rende il testing A/B ancora più importante
Se non conosciamo il segno di [math]\gamma[/math], una decisione basata soltanto sull’intuizione è rischiosa.
Supponiamo che una campagna abbia frequency elevata.
Potremmo pensare:
“Il pubblico è saturo, quindi dobbiamo cambiare creatività.”
Ma il modello ci ricorda che questa non è una conclusione automatica.
Potremmo trovarci in tre situazioni.
Scenario 1 — γ positivo
La nuova creatività è particolarmente efficace proprio sul pubblico saturo.
Il testing creativo è una leva prioritaria.
Scenario 2 — γ vicino a zero
La creatività funziona, ma il suo effetto non dipende significativamente dalla saturazione.
Potrebbe essere necessario intervenire su altre variabili.
Scenario 3 — γ negativo
La nuova creatività migliora la risposta, ma meno di quanto farebbe su un pubblico fresco.
In questo caso cambiare soltanto l’annuncio potrebbe non essere sufficiente.
Potrebbero essere necessari:
- nuovi segmenti;
- audience expansion;
- esclusione degli utenti già esposti;
- variazione del frequency cap, quando disponibile;
- modifica del mix di placement;
- riallocazione del budget;
- revisione dell’offerta.
La Data Science non fornisce quindi una regola universale.
Permette di capire quale regime descrive meglio il comportamento osservato.
Dalla simulazione all’ipotesi statistica
A questo punto il problema può essere formulato in modo molto preciso.
Vogliamo sapere se esiste un’interazione tra frequency e creatività.
Possiamo quindi definire:
[math]H_0: \gamma = 0[/math]
contro:
[math]H_1: \gamma \neq 0[/math]
Se il modello restituisce un coefficiente significativamente positivo, abbiamo evidenza di un’interazione positiva.
Se è significativamente negativo, abbiamo evidenza di un’interazione negativa.
Se non è statisticamente distinguibile da zero, non abbiamo evidenza sufficiente per affermare che l’effetto della creatività dipenda dalla saturazione.
Notiamo la formulazione:
non stiamo cercando di dimostrare che la creatività funziona.
Stiamo cercando di stimare come il suo effetto cambia al variare della saturazione.
È una domanda molto più precisa.
Il valore del controfattuale
La vera utilità della sensitivity analysis è quindi costruire un piccolo spazio di scenari:
[math]\gamma < 0[/math]
[math]\gamma = 0[/math]
[math]\gamma > 0[/math]
Questo evita un errore frequente nella modellazione:
scegliere un parametro favorevole, osservare il risultato e poi raccontare il risultato come se fosse stato scoperto dai dati.
Una simulazione non scopre un parametro.
Una simulazione mostra le conseguenze di un parametro ipotizzato.
La stima del parametro arriva soltanto quando utilizziamo dati osservati.
E se vogliamo interpretarlo causalmente, dobbiamo andare oltre la semplice regressione osservazionale.
Una regola metodologica da ricordare
Quando costruisci una simulazione per spiegare un fenomeno, chiediti sempre:
Quale risultato otterrei se la mia ipotesi fosse falsa?
Nel nostro caso la risposta è:
- se γ > 0, la creatività diventa relativamente più efficace al crescere della saturazione;
- se γ = 0, la saturazione non modifica l’effetto della creatività;
- se γ < 0, la creatività diventa relativamente meno efficace al crescere della saturazione.
Mostrare tutti e tre gli scenari rende il modello più onesto e, paradossalmente, anche più interessante.
Non stiamo più raccontando che la creatività è la soluzione.
Stiamo costruendo uno strumento per capire quando potrebbe esserlo e quando potrebbe non esserlo.
Questo è esattamente il tipo di domanda che una buona analisi quantitativa dovrebbe aiutare a formulare.
Sensitivity analysis: il ruolo del coefficiente di interazione
Questo passaggio è particolarmente importante perché evita di presentare la simulazione come una previsione.
Proviamo diversi valori di [math]\gamma[/math].
# ============================================================
# SENSITIVITY ANALYSIS
#
# Studiamo cosa accade quando cambia l'intensità
# dell'interazione tra saturazione e creatività.
# ============================================================
interaction_values = [
0.0,
2.0,
5.5,
8.0
]
plt.figure(figsize=(10, 6))
for gamma in interaction_values:
scenario = simula_campagna(
saturation=0.70,
competition=1.20,
interaction=gamma
)
plt.plot(
scenario["Creative_Score"],
scenario["CAC"],
linewidth=2,
label=f"γ = {gamma}"
)
plt.title(
"Sensitivity analysis: effetto del coefficiente di interazione"
)
plt.xlabel(
"Creative Score"
)
plt.ylabel(
"CAC (€)"
)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Questo grafico ha un significato metodologico importante.
Se:
[math]\displaystyle \gamma = 0[/math]
non c’è interazione.
La creatività ha un effetto indipendente dalla saturazione.
Quando invece:
[math]\displaystyle \gamma > 0[/math]
l’effetto della creatività dipende dal livello di saturazione.
La simulazione quindi non “dimostra” che [math]\gamma[/math] sia positivo.
Mostra quali conseguenze avrebbe un’interazione positiva.
Per stabilire il valore di [math]\gamma[/math] servono dati.
Dal modello simulato ai dati reali
Supponiamo ora di avere un dataset storico delle campagne.
Una struttura minima potrebbe essere:
Date
Campaign
Impressions
Reach
Clicks
Conversions
CPM
Creative_Score
Da questi dati possiamo costruire:
[math]\displaystyle \text{Frequency} = \frac{\text{Impressions}}{\text{Reach}}[/math]
e:
[math]\displaystyle \text{CTR} = \frac{\text{Clicks}}{\text{Impressions}}[/math]
[math]\displaystyle \text{CVR} = \frac{\text{Conversions}}{\text{Clicks}}[/math]
[math]\displaystyle \text{CAC} = \frac{\text{Spend}}{\text{Conversions}}[/math]
La frequency diventa quindi un proxy osservabile della saturazione.
Questo è preferibile rispetto a chiamare direttamente Impressions / Reach “saturazione”.
Costruire un modello empirico
Possiamo utilizzare una regressione log-log con interazione:
[math]\displaystyle \begin{aligned}
\ln(\text{CTR}) = \beta_0 + \beta_T \ln(\text{Frequency}) + \beta_C \ln(\text{CreativeScore}) \\
+ \gamma [\ln(\text{Frequency}) \times \ln(\text{CreativeScore})] + \beta_P \ln(\text{CPM}) + \epsilon
\end{aligned}[/math]
Il termine:
[math]\displaystyle \gamma [\ln(\text{Frequency}) \times \ln(\text{CreativeScore})][/math]
ci permette di studiare se l’effetto della creatività cambia al variare della frequency.
Codice Python per dati storici
Il codice seguente può essere utilizzato in Colab con un CSV reale.
# ============================================================
# REGRESSIONE LOG-LOG CON INTERAZIONE
# ============================================================
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
def stima_interazione_creativita(
file_path,
robust_se=True
):
"""
Stima l'interazione tra frequency e Creative Score
utilizzando una regressione log-log.
Il dataset deve contenere almeno:
Impressions
Reach
Clicks
CPM
Creative_Score
Opzionalmente può contenere anche Conversions.
Parameters
----------
file_path : str
Percorso del CSV.
robust_se : bool
Se True, utilizza errori standard HC3 robusti
all'eteroschedasticità.
Returns
-------
model
Modello statsmodels stimato.
"""
# --------------------------------------------------------
# 1. Caricamento
# --------------------------------------------------------
df = pd.read_csv(file_path)
# --------------------------------------------------------
# 2. Controllo colonne necessarie
# --------------------------------------------------------
required_columns = [
"Impressions",
"Reach",
"Clicks",
"CPM",
"Creative_Score"
]
missing = [
col
for col in required_columns
if col not in df.columns
]
if missing:
raise ValueError(
f"Colonne mancanti nel dataset: {missing}"
)
# --------------------------------------------------------
# 3. Feature engineering
# --------------------------------------------------------
df["Frequency"] = (
df["Impressions"]
/ df["Reach"]
)
df["CTR"] = (
df["Clicks"]
/ df["Impressions"]
)
# --------------------------------------------------------
# 4. Se esistono le conversioni,
# calcoliamo anche il CVR e il CAC.
# --------------------------------------------------------
if "Conversions" in df.columns:
df["CVR"] = np.where(
df["Clicks"] > 0,
df["Conversions"] / df["Clicks"],
np.nan
)
# --------------------------------------------------------
# 5. Se esiste la spesa, calcoliamo il CAC.
# --------------------------------------------------------
if "Spend" in df.columns:
df["CAC"] = np.where(
df["Conversions"] > 0,
df["Spend"] / df["Conversions"],
np.nan
)
# --------------------------------------------------------
# 6. Variabili utilizzate dal modello
# --------------------------------------------------------
model_columns = [
"CTR",
"Frequency",
"Creative_Score",
"CPM"
]
df_model = df[
model_columns
].copy()
# --------------------------------------------------------
# 7. I logaritmi richiedono valori > 0
# --------------------------------------------------------
for col in model_columns:
df_model = df_model[
df_model[col] > 0
]
# --------------------------------------------------------
# 8. Trasformazione logaritmica
# --------------------------------------------------------
for col in model_columns:
df_model[f"log_{col}"] = np.log(
df_model[col]
)
# --------------------------------------------------------
# 9. Modello log-log con interazione
# --------------------------------------------------------
formula = (
"log_CTR ~ "
"log_Frequency * log_Creative_Score "
"+ log_CPM"
)
# --------------------------------------------------------
# 10. Stima OLS
# --------------------------------------------------------
if robust_se:
model = smf.ols(
formula=formula,
data=df_model
).fit(
cov_type="HC3"
)
else:
model = smf.ols(
formula=formula,
data=df_model
).fit()
# --------------------------------------------------------
# 11. Risultati
# --------------------------------------------------------
print(model.summary())
# --------------------------------------------------------
# 12. Recuperiamo il coefficiente di interazione
# --------------------------------------------------------
interaction_name = (
"log_Frequency:log_Creative_Score"
)
gamma = model.params.get(
interaction_name,
np.nan
)
p_value = model.pvalues.get(
interaction_name,
np.nan
)
confidence_interval = model.conf_int().loc[
interaction_name
]
print("\n" + "=" * 60)
print("ANALISI DELL'INTERAZIONE")
print("=" * 60)
print(
f"Coefficiente γ: {gamma:.4f}"
)
print(
f"p-value: {p_value:.4f}"
)
print(
"IC 95%: "
f"[{confidence_interval[0]:.4f}, "
f"{confidence_interval[1]:.4f}]"
)
if p_value < 0.05:
print(
"\nL'interazione è statisticamente "
"distinguibile da zero al livello del 5%."
)
else:
print(
"\nNon abbiamo evidenza statistica sufficiente "
"per distinguere l'interazione da zero al 5%."
)
return model
Come interpretare il risultato
Supponiamo che il modello produca:
gamma = 0.18
p-value = 0.012
Non dovremmo dire:
“La creatività riduce il CAC del 18%.”
Sarebbe sbagliato.
Dovremmo dire:
Il coefficiente positivo dell’interazione indica che, nel modello stimato, l’effetto della creatività sul CTR tende a diventare più positivo all’aumentare della frequency. Il p-value suggerisce che questa interazione è statisticamente distinguibile da zero al livello del 5%.
È una differenza sostanziale.
Il modello ci parla di associazione condizionata, non automaticamente di causalità.
Correlazione non significa causalità
Questo è uno dei punti più importanti dell’intera analisi.
Immaginiamo di osservare:
[math]\displaystyle \text{CreativeScore} \uparrow[/math]
insieme a:
[math]\displaystyle \text{CTR} \uparrow[/math]
Non possiamo automaticamente concludere:
[math]\displaystyle \text{CreativeScore} \rightarrow \text{CTR}[/math]
Potrebbero esistere variabili confondenti.
Per esempio:
- budget;
- audience;
- placement;
- stagionalità;
- obiettivo della campagna;
- brand awareness;
- formato;
- dispositivo;
- durata della campagna;
- bidding strategy.
Esiste inoltre un problema di reverse causality.
Un advertiser potrebbe cambiare creatività proprio perché il CTR è diminuito.
In questo caso osserviamo:
[math]\displaystyle \text{CTR} \downarrow \rightarrow \text{Creative revision}[/math]
e non necessariamente:
[math]\displaystyle \text{Creative revision} \rightarrow \text{CTR} \uparrow[/math]
Per stimare un effetto causale servirebbero strategie più robuste, come:
- A/B test randomizzati;
- esperimenti controllati;
- panel con effetti fissi;
- difference-in-differences, quando appropriato;
- modelli causali con adeguate assunzioni.
La regressione proposta in questo articolo deve quindi essere considerata principalmente uno strumento esplorativo.
Un Creative Score non è una variabile naturale
Un altro problema interessante riguarda la costruzione del Creative_Score.
Non esiste necessariamente un unico punteggio universale di qualità creativa.
Possiamo però costruire un indice composito a partire da metriche osservabili.
Per esempio:
[math]\displaystyle \text{CreativeScore} = w_1 Z(\text{Retention}_{3s}) + w_2 Z(\text{ViewThroughRate}) + w_3 Z(\text{EngagementRate})[/math]
dove [math]Z[/math] indica una standardizzazione.
In Python:
# ============================================================
# ESEMPIO DI CREATIVE SCORE
# ============================================================
from sklearn.preprocessing import StandardScaler
features = [
"Retention_3s",
"Video_Completion_Rate",
"Engagement_Rate"
]
scaler = StandardScaler()
df[features] = scaler.fit_transform(
df[features]
)
df["Creative_Score"] = (
0.40 * df["Retention_3s"]
+ 0.35 * df["Video_Completion_Rate"]
+ 0.25 * df["Engagement_Rate"]
)
I pesi sono naturalmente un’altra assunzione.
In un progetto reale potrebbero essere determinati attraverso:
- regressione;
- PCA;
- modelli predittivi;
- ottimizzazione rispetto al KPI finale;
- oppure mantenuti uguali per evitare di introdurre una struttura arbitraria.
Il punto fondamentale è documentare come è stato costruito l’indice.
Dalla simulazione alla decisione manageriale
Possiamo ora tornare alla domanda iniziale.
Supponiamo che una campagna sia diventata costosa.
Abbiamo almeno due strategie.
Strategia A: aumentare il budget
Aumentiamo la spesa mantenendo:
- stesso pubblico;
- stessa creatività;
- stessa struttura della campagna.
Il rischio è aumentare ulteriormente la frequency e spostarsi verso impression marginali meno produttive.
Strategia B: migliorare la creatività
Manteniamo il pubblico ma introduciamo nuove varianti creative.
L’obiettivo non è necessariamente ridurre il CPM.
È aumentare la probabilità che una nuova impression produca una risposta.
Questa distinzione è importante.
Il problema economico non è minimizzare il CPM. È minimizzare il costo dell’acquisizione mantenendo il valore della conversione.
Il modello non dice che bisogna sempre investire nella creatività
Questo è un altro punto fondamentale.
Se:
[math]\displaystyle \gamma \approx 0[/math]
l’interazione tra creatività e saturazione potrebbe essere trascurabile.
In questo caso migliorare la creatività potrebbe non compensare la pressione crescente.
Se:
[math]\displaystyle \gamma > 0[/math]
e l’effetto è sufficientemente grande, la creatività può diventare una leva interessante.
Ma anche qui serve considerare il costo della produzione creativa.
La vera decisione economica dovrebbe quindi confrontare:
[math]\displaystyle \text{beneficio marginale della nuova creatività}[/math]
con:
[math]\displaystyle \text{costo marginale della produzione e del testing}[/math]
La creatività non è una risorsa gratuita.
Il vero concetto: costo marginale dell’attenzione
Possiamo quindi interpretare il problema in termini di economia marginale dell’attenzione.
Quando aumentiamo la spesa, non stiamo semplicemente comprando “più utenti”.
Stiamo comprando impression successive.
Le prime impression possono intercettare utenti altamente reattivi.
Le successive possono raggiungere utenti meno propensi all’azione oppure utenti già esposti molte volte.
Il valore marginale può quindi diminuire.
La creatività interviene proprio su questa curva.
Una nuova creatività può aumentare la probabilità che una nuova esposizione venga percepita come rilevante.
Non significa necessariamente creare nuova domanda.
Può significare estrarre più valore dall’attenzione già acquistata.
9. Limiti del modello
Un modello quantitativo serio deve dichiarare anche ciò che non può spiegare.
Il modello presentato ha almeno sette limiti.
1. Parametri sintetici
La simulazione non utilizza parametri osservati da una piattaforma pubblicitaria.
2. Algoritmi proprietari
La funzione d’asta utilizzata è una rappresentazione semplificata e non pretende di ricostruire un algoritmo proprietario.
3. Saturazione
La frequency è un proxy osservabile, non una misura perfetta della saturazione.
4. Creative Score
La qualità creativa deve essere definita e misurata con criteri espliciti.
5. Endogeneità
Creatività, targeting e budget possono essere modificati contemporaneamente.
6. Causalità
Una regressione osservazionale non dimostra automaticamente un effetto causale.
7. Non linearità
La relazione reale tra creatività, frequency, CTR, CVR e CAC potrebbe essere molto più complessa di quella rappresentata dal modello.
Questi limiti non indeboliscono l’articolo.
Al contrario, spiegano come utilizzare correttamente il modello.
La lezione per la Data Science applicata al marketing
La conclusione più interessante non è:
“La creatività è importante.”
Lo sapevamo già.
La domanda quantitativa è diversa:
L’effetto della creatività dipende dal livello di saturazione del pubblico?
Questa è una domanda testabile.
Possiamo formulare un’ipotesi:
[math]\displaystyle H_0: \gamma = 0[/math]
contro:
[math]\displaystyle H_1: \gamma \neq 0[/math]
e utilizzare dati storici, esperimenti o modelli causali per verificare quale delle due descriva meglio il fenomeno.
A quel punto la creatività smette di essere soltanto una questione qualitativa.
Diventa una variabile osservabile all’interno di un modello.
Conclusione
Quando una campagna pubblicitaria diventa più costosa, la risposta non è necessariamente aumentare il budget.
Il problema potrebbe essere la diminuzione del rendimento marginale dell’attenzione acquistata.
La frequency ci permette di costruire un proxy della pressione esercitata sul pubblico. La creatività rappresenta invece una possibile leva per modificare la risposta dell’utente.
Il punto centrale è l’interazione:
[math]\displaystyle T \times C[/math]
dove [math]T[/math] rappresenta la saturazione e [math]C[/math] la qualità creativa.
Se l’interazione è positiva, l’effetto della creatività cambia proprio quando il pubblico diventa più saturo. Questo non significa che la creatività “neutralizzi” automaticamente i costi d’asta: significa che il suo valore marginale può dipendere dal contesto competitivo e dalla pressione già esercitata sul pubblico.
La simulazione Python permette di esplorare questo meccanismo.
La regressione sui dati storici permette invece di formulare una domanda empirica.
E il passaggio successivo, se l’obiettivo è stabilire un rapporto causale, è l’esperimento.
È questa la differenza tra una dashboard che descrive ciò che è successo e un approccio di Data Science che prova a capire perché è successo e quale decisione conviene prendere dopo.
📚 Per approfondire: Data Science, modelli predittivi e simulazione
Se vuoi andare oltre l’analisi descrittiva e utilizzare la Data Science per prendere decisioni basate sui dati, questi articoli approfondiscono inferenza bayesiana, causal inference, customer lifetime value, pricing e simulazione Monte Carlo con Python:
👉 Distribuzione Beta e inferenza bayesiana: come ottimizzare campagne e A/B test con la Data Science
👉 Disintermediazione del GTV: come modellare in Python la fuga di volume dalla piattaforma
👉 Pricing ibrido B2B con Python: simulare churn, GTV e profitto con Monte Carlo
👉 Comprendere il modello Pareto/NBD per la valutazione della Customer Lifetime Value (CLV)
👉 Simulatore e-commerce in Python: modello matematico per prevedere profitto e break-even
👉 Tutorial CausalML – Parte 1: dalla teoria all’Uplift Modeling con Python





