BigQuery ML su GA4: prevedere la churn dai dati grezzi

In questo articolo
Su un SaaS B2B con cui ho lavorato, il marketing spendeva 42% del budget retention su utenti che sarebbero rimasti comunque. Lo abbiamo scoperto solo dopo aver costruito un modello di churn prediction in BigQuery ML alimentato dai dati grezzi di GA4. Il modello segmentava gli utenti in quattro fasce: chi era sicuramente perso (non disturbare), chi era già fedele (non spendere), chi era a rischio (target attivo), chi era incerto (test A/B). Riallocare la spesa sulla fascia 3 ha alzato il retention rate del 9% in un trimestre.
Non serve un team di data scientist per replicare questo. BigQuery ML permette di scrivere modelli SQL che girano nella stessa piattaforma dove vivono già i dati GA4. Vediamo come.
Perché BigQuery ML invece di Python/sklearn
Tre motivi pratici, non religiosi:
- I dati GA4 sono già in BigQuery, quindi salti l'export, il pre-processing in pandas e i problemi di campionamento
- BigQuery ML scala automaticamente: tabelle da centinaia di milioni di righe vengono trainate in minuti, senza setup di cluster
- Il modello vive vicino alla activation: una volta trainato, lo riusi via SQL per scorare nuovi utenti e mandarli direttamente nelle audience di Google Ads
Lo svantaggio: meno controllo fine sull'hyperparameter tuning rispetto a XGBoost in Python. Per i casi d'uso marketing tipici (churn binaria, valore previsto, propensity score) BQML basta e avanza.
Definire la churn
Prima di scrivere SQL, decidi cosa significa "churn" nel tuo business. Non è la stessa cosa per tutti:
| Tipo di business | Definizione tipica |
|---|---|
| SaaS B2B | Nessun login per 30 giorni dopo la prova gratuita |
| Ecommerce ad alto frequenza | Nessun acquisto per 60 giorni dopo l'ultimo |
| Editoria/contenuto | Nessuna visita per 21 giorni dopo l'iscrizione newsletter |
| Marketplace | Nessuna ricerca per 14 giorni |
Sbagliare la definizione di churn vanifica il modello. Concorda con il business prima di scrivere una riga di SQL.
Feature engineering su GA4 raw
Il dataset GA4 in BigQuery ha una riga per evento. Per addestrare un modello servono feature aggregate per utente. Tipico set per un SaaS:
CREATE OR REPLACE TABLE `prog.ml.user_features_202604` AS
WITH base AS (
SELECT
user_pseudo_id,
DATE(TIMESTAMP_MICROS(event_timestamp)) AS event_date,
event_name,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'session_duration') AS session_duration
FROM `prog.analytics_XXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260301' AND '20260430'
)
SELECT
user_pseudo_id,
COUNT(DISTINCT event_date) AS active_days_60d,
COUNTIF(event_name = 'login') AS login_count_60d,
COUNTIF(event_name = 'feature_used') AS feature_used_count_60d,
COUNT(DISTINCT page) AS unique_pages_60d,
DATE_DIFF(CURRENT_DATE(), MAX(event_date), DAY) AS days_since_last_event,
AVG(session_duration) AS avg_session_duration,
COUNTIF(event_name = 'error') AS error_count_60d
FROM base
GROUP BY user_pseudo_id;
Le feature più predittive nei modelli di churn marketing che ho visto sono quasi sempre le stesse:
- Recency: giorni dall'ultimo evento (la più forte, spesso da sola spiega il 60% della predizione)
- Frequency: numero di sessioni / login nelle ultime N settimane
- Engagement depth: pagine uniche, feature uniche, eventi distinti
- Friction signals: errori, sessioni brevi (sotto i 10 secondi), bounce diretti
- Lifecycle stage: giorni dalla registrazione (la churn al day 7 è diversa dalla churn al day 90)
Evita feature derivate da rapporto/divisione: spesso introducono outlier che destabilizzano il modello.
Etichettare il target
Prima di trainare serve una colonna churned (1 = sì, 0 = no). Su un SaaS con definizione "30 giorni senza login":
CREATE OR REPLACE TABLE `prog.ml.user_features_labeled` AS
WITH features AS (
SELECT * FROM `prog.ml.user_features_202604`
),
labels AS (
SELECT
user_pseudo_id,
-- L'utente è "churned" se non ha login nei 30 giorni successivi alla snapshot
CASE
WHEN MAX(DATE(TIMESTAMP_MICROS(event_timestamp))) <= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
THEN 1 ELSE 0
END AS churned
FROM `prog.analytics_XXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260501' AND '20260530'
AND event_name = 'login'
GROUP BY user_pseudo_id
)
SELECT
f.*,
COALESCE(l.churned, 1) AS churned -- chi non compare nel periodo successivo è churned
FROM features f
LEFT JOIN labels l USING (user_pseudo_id);
Importante: la finestra di feature (60 giorni passati) e la finestra di label (30 giorni futuri) non si sovrappongono. Sovrapporle è il bug numero uno che inquina i modelli di churn nei tutorial online.
Trainare il modello
BigQuery ML supporta diversi modelli per classificazione binaria. Per partire uso quasi sempre BOOSTED_TREE_CLASSIFIER: pratico, robusto sui dati sparsi, accetta nativamente sia feature numeriche che categoriche.
CREATE OR REPLACE MODEL `prog.ml.churn_model_v1`
OPTIONS(
model_type = 'BOOSTED_TREE_CLASSIFIER',
input_label_cols = ['churned'],
max_iterations = 50,
early_stop = TRUE,
min_rel_progress = 0.01,
data_split_method = 'AUTO_SPLIT',
data_split_eval_fraction = 0.2
) AS
SELECT
active_days_60d,
login_count_60d,
feature_used_count_60d,
unique_pages_60d,
days_since_last_event,
avg_session_duration,
error_count_60d,
churned
FROM `prog.ml.user_features_labeled`
WHERE user_pseudo_id IS NOT NULL;
Tempo di training su 1.2M righe: ~4 minuti su BigQuery standard. Costo: pochi euro.
Valutare il modello
Sempre prima di mettere un modello in produzione:
SELECT *
FROM ML.EVALUATE(MODEL `prog.ml.churn_model_v1`);
Cosa guardare:
- AUC ROC sopra 0.75: il modello discrimina bene
- Precision alta sul positivo (churned=1) se ti interessa non sprecare budget retention su falsi positivi
- Recall alta se preferisci non lasciarti sfuggire utenti a rischio
Su un modello B2B SaaS reale, AUC = 0.83, precision = 0.71, recall = 0.79: numeri da modello utile per il marketing, non da paper accademico.
Activation: dal modello all'audience Google Ads
Qui il modello produce valore. Scoriamo gli utenti attivi oggi e li mandiamo come audience.
CREATE OR REPLACE TABLE `prog.ml.churn_scores_today` AS
WITH today_features AS (
-- Stesso CTE di feature engineering ma sui 60 giorni che terminano oggi
SELECT ...
),
predictions AS (
SELECT
user_pseudo_id,
predicted_churned_probs[OFFSET(0)].prob AS churn_probability
FROM ML.PREDICT(MODEL `prog.ml.churn_model_v1`, TABLE today_features)
)
SELECT
user_pseudo_id,
churn_probability,
CASE
WHEN churn_probability > 0.80 THEN 'lost_cause'
WHEN churn_probability BETWEEN 0.50 AND 0.80 THEN 'at_risk'
WHEN churn_probability BETWEEN 0.20 AND 0.50 THEN 'uncertain'
ELSE 'loyal'
END AS segment
FROM predictions;
Da qui due strade:
- Audience GA4 personalizzate via
user_pseudo_id: importi la tabella come Custom Dimensions o usi il Measurement Protocol per inviare un eventosegment_assignedcon il valore. Le audience GA4 si possono attivare in Google Ads. - Activation diretta via Google Ads Customer Match: se hai email associate al
user_pseudo_idnel CRM, esporti gli hash e li carichi come lista Customer Match.
La seconda è più granulare ma richiede consenso ad_user_data granted (vedi Enhanced Conversions per il pattern di hashing).
Schedulare il refresh
Una volta che il modello esiste, schedula un refresh giornaliero o settimanale via Scheduled Queries di BigQuery:
-- Query schedulata ogni 24h alle 06:00 Europe/Rome
CREATE OR REPLACE TABLE `prog.ml.churn_scores_today` AS
WITH today_features AS (...)
SELECT ... FROM ML.PREDICT(MODEL `prog.ml.churn_model_v1`, TABLE today_features);
Il modello stesso si retrenara mensile: i pattern di churn cambiano con il prodotto, e un modello fresco performa meglio di uno vecchio anche se ha la stessa architettura.
Limiti che valgono come avvertenza
Il modello impara dal passato, non legge il futuro. Se il prodotto cambia (release importante, nuovo onboarding, prezzo modificato) il modello smette di essere predittivo. Va monitorato e ritarato.
Correlazione, non causazione. Il modello dice "questo utente ha l'80% di probabilità di churn", non "questo utente sta per fare churn perché X". Se vuoi capire il "perché", servono analisi separate sui drop point del funnel, non il modello di score.
ML non sostituisce la qualità del dato GA4. Se il tuo tracking è rotto, il modello impara da spazzatura. Prima della modellazione, sempre un audit tracking GA4. Sempre.
BigQuery costa. Il free tier GA4 export copre solo i siti piccoli. Su volumi enterprise, ogni query ML.PREDICT su 5M righe costa decine di euro: vale la pena materializzare le tabelle intermediate e schedulare con parsimonia.
Prossimi passi
Se hai già GA4 collegato a BigQuery e vuoi partire, ti serve:
- Almeno 90 giorni di dati storici puliti (l'audit del tracking è un prerequisito)
- Una definizione di churn allineata con il business
- Un'idea di cosa farai con i segment, prima di trainare (l'activation guida le feature)
Per il setup BigQuery + GA4 di base, il pattern parte da User journey analytics con GA4 e BigQuery SQL. Per visualizzare i segment ottenuti in una vista business, la copertura è in Dashboard Looker Studio (sezione blended data). E se ti serve aiuto operativo per costruire il modello sul tuo dataset, partiamo dalla consulenza GA4 o, su casi più ampi, dagli agenti AI per l'analytics.
Correlati

Iubenda + GTM: integrare il consent banner senza buchi
Integrare Iubenda Cookie Solution con GTM e Consent Mode v2 senza buchi: mappatura categorie, gestione race condition, segnali ad_user_data e ad_personalization.

Tag governance: ordine in un container GTM con 200 tag
Come fare ordine in un container GTM con 200+ tag naming convention, folder, version control e processo di review per team marketing e dev.

Attribution data-driven GA4: come confrontare i modelli senza farsi ingannare
Confronto onesto tra modelli di attribution su GA4 data-driven, paid + organic, ultima interazione. Come leggere il rapporto Path Exploration senza innamorarsi del numero.