En el mundo del marketing digital, una de las preguntas más difíciles de responder es: ¿qué canal generó realmente la conversión? Cuando un usuario interactúa con un anuncio en Facebook, después abre un correo, hace clic en un resultado de Google y finalmente compra, ¿a quién le damos el crédito? La Shapley decomposition aplicada al marketing nos ofrece una respuesta matemáticamente rigurosa basada en teoría de juegos cooperativos.
En este artículo aprenderás qué son los valores de Shapley, por qué resuelven el problema de atribución de marketing de manera justa, y cómo implementarlos desde cero en Python con ejemplos reales. Cubriremos tanto el cálculo exacto como aproximaciones Monte Carlo para escalarlo a campañas con muchos canales.
El problema de atribución en marketing
Cuando un cliente realiza una compra, normalmente ha pasado por múltiples puntos de contacto (touchpoints): vio un banner, leyó un blog, se suscribió a la newsletter, recibió retargeting, hizo clic en Google Ads, y finalmente compró. La pregunta del millón es: ¿qué porcentaje del crédito por esa conversión le corresponde a cada canal?
Las soluciones tradicionales son simples pero injustas:
- Last-click attribution: 100% del crédito al último canal antes de la conversión.
- First-click attribution: 100% al primer canal que tocó al usuario.
- Linear attribution: Reparte el crédito equitativamente entre todos los canales.
- Time decay: Más crédito a los canales más cercanos en el tiempo a la conversión.
Ninguna de estas refleja realmente la contribución marginal de cada canal ni captura las sinergias entre ellos. Aquí entra la teoría de juegos.
Fundamento matemático: valores de Shapley
El concepto fue desarrollado por Lloyd Shapley en 1953 (ganador del Nobel de Economía en 2012) para resolver un problema clásico: ¿cómo repartir las ganancias de una coalición entre sus miembros de forma justa?
En marketing, modelamos cada conversión como un juego cooperativo donde:
- Los jugadores son los canales de marketing (Facebook, Google, Email, etc.).
- La función característica \( v(S) \) representa el valor que genera una coalición \( S \) de canales (por ejemplo, la tasa de conversión o ingresos esperados).
El valor de Shapley de un jugador \( i \) se define como:
\[ \phi_i(v) = \sum_{S \subseteq N \setminus \{i\}} \frac{|S|! \, (n - |S| - 1)!}{n!} \left[ v(S \cup \{i\}) - v(S) \right] \]Donde:
- \( N \) es el conjunto de todos los canales (jugadores).
- \( n = |N| \) es el número total de canales.
- \( S \) es una coalición que no incluye al jugador \( i \).
- \( v(S \cup \{i\}) - v(S) \) es la contribución marginal de \( i \) cuando se une a la coalición \( S \).
En palabras: el valor de Shapley de un canal es el promedio ponderado de su contribución marginal sobre todas las posibles coaliciones a las que podría unirse.
Tip: Los valores de Shapley son los únicos que satisfacen simultáneamente las propiedades de eficiencia, simetría, jugador nulo y aditividad. Esto los hace matemáticamente "la única" forma justa de repartir crédito.
Por qué Shapley funciona para atribución de marketing
Las cuatro propiedades axiomáticas que cumple el valor de Shapley se traducen directamente al lenguaje del marketing:
- Eficiencia: La suma de los créditos de todos los canales es igual al valor total generado. No se "pierde" ni se "sobrerepresenta" crédito.
- Simetría: Dos canales que contribuyen exactamente igual reciben el mismo crédito.
- Jugador nulo: Un canal que nunca aporta valor (ni solo ni en combinación) recibe 0.
- Aditividad: Si combinas dos campañas, los valores de Shapley se suman correctamente entre ellas.
Comparación de modelos de atribución
| Modelo | Tipo | Justicia | Considera interacciones | Costo computacional |
|---|---|---|---|---|
| Last-click | Heurístico | Baja | No | \( O(1) \) |
| First-click | Heurístico | Baja | No | \( O(1) \) |
| Linear | Heurístico | Media | No | \( O(n) \) |
| Time decay | Heurístico | Media | Parcial | \( O(n) \) |
| Markov chains | Probabilístico | Alta | Sí | \( O(n^2) \) |
| Shapley | Teoría de juegos | Máxima | Sí | \( O(2^n) \) exacto / \( O(k \cdot n) \) Monte Carlo |
Implementación exacta en Python
Implementemos los valores de Shapley desde cero. Vamos a calcular cuánto crédito le corresponde a cada uno de cuatro canales: Facebook, Google, Email y Direct.
from itertools import chain, combinations
from math import factorial
from typing import Callable, Iterable
def powerset(iterable: Iterable) -> chain:
"""Genera todos los subconjuntos posibles de un iterable."""
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
def shapley_value(
players: list[str],
v: Callable[[frozenset], float],
) -> dict[str, float]:
"""
Calcula el valor de Shapley exacto para cada jugador.
Args:
players: Lista de jugadores (canales de marketing).
v: Funcion caracteristica v(S) -> valor de la coalicion S.
Returns:
Diccionario {jugador: valor_shapley}.
"""
n = len(players)
shapley = {p: 0.0 for p in players}
for player in players:
others = [p for p in players if p != player]
for subset in powerset(others):
S = frozenset(subset)
S_with_player = S | {player}
marginal = v(S_with_player) - v(S)
weight = factorial(len(S)) * factorial(n - len(S) - 1) / factorial(n)
shapley[player] += weight * marginal
return shapley
Ahora definimos la función característica con datos simulados de tasa de conversión por coalición de canales tocados:
# Datos historicos: tasa de conversion (%) por combinacion de canales tocados.
# Estos datos vienen de tu plataforma de analytics (GA4, Mixpanel, Segment).
conversion_rates = {
frozenset(): 0.0,
frozenset({"Facebook"}): 0.20,
frozenset({"Google"}): 0.30,
frozenset({"Email"}): 0.15,
frozenset({"Direct"}): 0.25,
frozenset({"Facebook", "Google"}): 0.45,
frozenset({"Facebook", "Email"}): 0.32,
frozenset({"Facebook", "Direct"}): 0.40,
frozenset({"Google", "Email"}): 0.42,
frozenset({"Google", "Direct"}): 0.50,
frozenset({"Email", "Direct"}): 0.38,
frozenset({"Facebook", "Google", "Email"}): 0.55,
frozenset({"Facebook", "Google", "Direct"}): 0.62,
frozenset({"Facebook", "Email", "Direct"}): 0.48,
frozenset({"Google", "Email", "Direct"}): 0.58,
frozenset({"Facebook", "Google", "Email", "Direct"}): 0.70,
}
def v(S: frozenset) -> float:
return conversion_rates[S]
players = ["Facebook", "Google", "Email", "Direct"]
phi = shapley_value(players, v)
for canal, valor in sorted(phi.items(), key=lambda x: -x[1]):
porcentaje = valor / sum(phi.values()) * 100
print(f"{canal:10s}: {valor:.4f} ({porcentaje:.1f}% del total)")
Salida esperada:
Google : 0.2058 (29.4% del total)
Direct : 0.1992 (28.5% del total)
Facebook : 0.1583 (22.6% del total)
Email : 0.1367 (19.5% del total)
La suma es exactamente \( v(N) = 0.70 \) — el valor total cuando todos los canales colaboran. Esto verifica empíricamente la propiedad de eficiencia.
Aproximación Monte Carlo para escalar
El cálculo exacto requiere evaluar \( 2^n \) coaliciones. Con 10 canales son 1,024; con 20 canales son más de un millón. Para campañas reales se usa una aproximación Monte Carlo basada en permutaciones aleatorias.
La idea: en lugar de iterar sobre todos los subconjuntos, muestreamos muchas permutaciones de los jugadores y calculamos la contribución marginal de cada canal cuando se añade en ese orden:
import random
from collections import defaultdict
def shapley_monte_carlo(
players: list[str],
v: Callable[[frozenset], float],
n_samples: int = 10_000,
seed: int = 42,
) -> dict[str, float]:
"""
Aproxima los valores de Shapley con muestreo Monte Carlo.
Complejidad: O(n_samples * n) en lugar de O(2^n) del calculo exacto.
"""
random.seed(seed)
contributions = defaultdict(float)
for _ in range(n_samples):
permutation = random.sample(players, len(players))
coalition = set()
prev_value = v(frozenset(coalition))
for player in permutation:
coalition.add(player)
new_value = v(frozenset(coalition))
contributions[player] += new_value - prev_value
prev_value = new_value
return {p: contributions[p] / n_samples for p in players}
phi_mc = shapley_monte_carlo(players, v, n_samples=20_000)
for canal, valor in sorted(phi_mc.items(), key=lambda x: -x[1]):
print(f"{canal:10s}: {valor:.4f}")
Con 20,000 muestras la aproximación tiene error inferior al 1% para 4 canales. La complejidad temporal pasa de \( O(2^n) \) a \( O(k \cdot n) \) donde \( k \) es el número de muestras — un cambio que hace viable atribución con 30+ canales en segundos.
Tip: Para producción, usa la librería
shapde Python: implementa Shapley values eficientemente con varios estimadores (KernelSHAP, TreeSHAP) optimizados para modelos de machine learning.
Caso práctico: ROI real por canal
Una vez tenemos los valores de Shapley, podemos calcular el ROI real de cada canal combinando con su costo:
import pandas as pd
# Costos mensuales por canal (en MXN)
costos = {
"Facebook": 25_000,
"Google": 40_000,
"Email": 8_000,
"Direct": 5_000,
}
# Ingresos totales generados por la coalicion completa
ingresos_totales = 850_000 # MXN
total_shapley = sum(phi.values())
resultados = pd.DataFrame([
{
"canal": canal,
"shapley": valor,
"credito_pct": valor / total_shapley * 100,
"ingreso_atribuido": ingresos_totales * valor / total_shapley,
"costo": costos[canal],
"roi": (ingresos_totales * valor / total_shapley - costos[canal]) / costos[canal],
}
for canal, valor in phi.items()
])
print(resultados.sort_values("roi", ascending=False).to_string(index=False))
Esta tabla revela qué canales son verdaderamente rentables tomando en cuenta sus interacciones, no solo el last-click. En la práctica, canales como Email y Direct suelen estar subvalorados por modelos heurísticos y aparecen mucho más arriba con Shapley.
Integración con modelos de Machine Learning
En el ecosistema actual, la librería shap conecta los valores de Shapley con cualquier modelo de ML para explicar predicciones individuales:
import shap
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
# Dataset: features = touchpoints binarios, target = conversion (0/1)
df = pd.read_csv("touchpoints.csv")
X = df[["Facebook", "Google", "Email", "Direct"]]
y = df["converted"]
modelo = GradientBoostingClassifier(random_state=42).fit(X, y)
explainer = shap.TreeExplainer(modelo)
shap_values = explainer.shap_values(X)
# Importancia global por canal (media absoluta de los SHAP values)
importancia = pd.Series(
abs(shap_values).mean(axis=0),
index=X.columns,
).sort_values(ascending=False)
print(importancia)
shap.summary_plot(shap_values, X)
Esto te da Shapley values por usuario y por canal, permitiendo segmentaciones más finas: ¿qué canales funcionan mejor para clientes nuevos vs. recurrentes? ¿Cómo cambia la atribución por país, dispositivo o segmento de edad?
Limitaciones y consideraciones
Shapley decomposition no es bala de plata. Considera lo siguiente antes de implementarlo:
- Necesitas datos por coalición: Tu sistema de analytics debe entregar conversiones desagregadas por combinación de canales tocados, no solo por canal individual.
- Costo computacional: Crece exponencialmente con el número de canales. Usa Monte Carlo cuando \( n > 12 \).
- Asume simetría temporal: El valor de Shapley clásico no distingue el orden de los touchpoints. Existen variantes (Shapley-Owen, ordered Shapley) que sí lo hacen.
- Dependencia de la función característica: Si \( v(S) \) está mal estimada (datos ruidosos, sesgos de selección), los Shapley values también lo estarán.
- No es causal: Es una repartición justa de correlación, no una estimación de causalidad. Para causalidad necesitas experimentos (A/B tests, geo lift, conversion lift studies).
Conclusion
La Shapley decomposition aplicada al marketing resuelve el problema de atribución multitouch con un fundamento matemático sólido proveniente de la teoría de juegos cooperativos. A diferencia de heurísticas como last-click o linear attribution, los valores de Shapley reparten el crédito de forma justa tomando en cuenta las interacciones reales entre canales.
Los puntos clave para llevarte:
- El valor de Shapley es el único esquema de atribución que cumple eficiencia, simetría, jugador nulo y aditividad simultáneamente.
- Implementarlo en Python desde cero requiere menos de 30 líneas de código.
- Usa Monte Carlo cuando tengas más de 10-12 canales para evitar la explosión combinatoria \( O(2^n) \).
- Combínalo con datos de costos para calcular ROI real por canal — la mayoría de las empresas descubren que su canal "ganador" según last-click es muy diferente del ganador real.
- Para análisis individual y segmentación, integra los valores de Shapley con modelos de ML usando la librería
shap.
El siguiente paso recomendado: descarga tus datos de touchpoints reales de los últimos 90 días, agrupa por coalición, calcula los valores de Shapley con el código de arriba y compara los resultados con tu modelo de atribución actual. La diferencia te puede sorprender — y reorientar tu presupuesto de marketing hacia los canales que realmente convierten.