Introducción al Modelo de Difusión de Bass#

¿Qué es el Modelo de Bass?#

El modelo de difusión de Bass, desarrollado por Frank Bass en 1969, es un modelo matemático que describe cómo los nuevos productos son adoptados en una población a lo largo del tiempo. Se utiliza ampliamente en marketing para prever las ventas de nuevos productos, especialmente cuando los datos históricos son limitados o inexistentes.

El modelo captura todo el ciclo de vida de la adopción del producto, desde la introducción hasta la saturación, convirtiéndolo en una herramienta poderosa para la planificación de productos y el desarrollo de estrategias de marketing.

La motivación detrás del modelo Bass#

Antes del modelo de Bass, las empresas luchaban por predecir los patrones de adopción de nuevos productos. Los métodos de pronóstico tradicionales a menudo fallaban porque no podían tener en cuenta las dinámicas sociales que impulsan la adopción de productos. Frank Bass reconoció que la adopción de productos sigue un patrón distinto:

  • Crecimiento inicial lento: Cuando un producto se lanza por primera vez, la adopción comienza lentamente.

  • Aceleración rápida: A medida que más personas adoptan, el boca a boca se difunde y la adopción se acelera.

  • Saturación eventual: Eventualmente, el mercado se satura y la adopción se ralentiza.

El modelo de Bass proporciona un marco matemático para capturar estos patrones, lo que permite a las empresas tomar decisiones más informadas sobre la planificación de la producción, la gestión de inventarios y la asignación de recursos de marketing.

Formulación Matemática#

El modelo de Bass se basa en una ecuación diferencial que describe la tasa de adopción:

\[\frac{f(t)}{1-F(t)} = p + q F(t)\]

Dónde:

  • \(F(t)\) es la fracción de base instalada (proporción acumulativa de adoptantes)

  • \(f(t)\) es la tasa de cambio de la fracción de la base instalada (\(f(t) = F'(t)\))

  • \(p\) es el coeficiente de innovación o influencia externa

  • \(q\) es el coeficiente de imitación o influencia interna

La solución a esta ecuación proporciona la curva de adopción:

\[F(t) = \frac{1 - e^{-(p+q)t}}{1 + (\frac{q}{p})e^{-(p+q)t}}\]

La tasa de adopción en el tiempo \(t\) se da por:

\[f(t) = (p + q F(t))(1 - F(t))\]

Alternativamente, esto se puede escribir como:

\[f(t) = \frac{(p+q)^2 \cdot e^{-(p+q)t}}{p \cdot (1+\frac{q}{p}e^{-(p+q)t})^2}\]

Componentes Clave de la Implementación del Modelo Bass#

La implementación del modelo Bass en PyMC-Marketing consiste en varios componentes clave:

  1. Adoptantes - El número de nuevas adopciones en el momento \(t\):

\[\text{adopters}(t) = m \cdot f(p, q, t)\]
  1. Innovadores - Adopciones impulsadas por influencia externa (publicidad, etc.):

\[\text{innovators}(t) = m \cdot p \cdot (1 - F(p, q, t))\]
  1. Imitadores - Adopciones impulsadas por influencia interna (boca a boca):

\[\text{imitators}(t) = m \cdot q \cdot F(p, q, t) \cdot (1 - F(p, q, t))\]
  1. Hora de Adopción Máxima - Cuando la tasa de adopción alcanza su máximo:

\[\text{peak} = \frac{\ln(q) - \ln(p)}{p + q}\]

El número total de adoptantes a lo largo del tiempo es la suma de innovadores e imitadores, lo que equivale a \(\text{adopters}(t)\). Todos estos componentes están implementados directamente en el modelo de PyMC, lo que nos permite analizar cada aspecto del proceso de difusión por separado.

Comprendiendo la Relación Entre los Componentes#

Una idea clave del modelo de Bass es cómo descompone la adopción en dos fuentes:

\[\text{adopters}(t) = \text{innovators}(t) + \text{imitators}(t)\]

En cada punto temporal:

  • Innovadores (\(m \cdot p \cdot (1 - F(t))\)) representa nuevas adopciones provenientes de personas que están influenciadas por factores externos como la publicidad.

  • Imitadores (\(m \cdot q \cdot F(t) \cdot (1 - F(t))\)) representa las nuevas adopciones que provienen de personas que están influenciadas por los adoptantes anteriores.

A medida que avanza el tiempo:

  • Inicialmente, los innovadores dominan el proceso de adopción cuando pocas personas han adoptado (\(F(t)\) es pequeño)

  • Más tarde, los imitadores se convierten en la fuente principal de nuevas adopciones a medida que crece el efecto de boca a boca.

  • Eventualmente, ambos disminuyen a medida que el mercado se acerca a la saturación (\(F(t)\) se aproxima a 1)

La adopción acumulativa en cualquier momento es:

\[\text{Cumulative Adoption}(t) = m \cdot F(t)\]

Esto significa que a medida que \(t \to \infty\), la adopción acumulativa se aproxima al potencial total del mercado \(m\):

\[\lim_{t \to \infty} \text{Cumulative Adoption}(t) = m\]

Por lo tanto, el modelo de Bass proporciona un análisis completo del mercado:

  • En cada momento, los nuevos adoptantes son innovadores o imitadores.

  • A lo largo de todo el ciclo de vida del producto, todos los posibles adoptantes (m) eventualmente adoptan el producto.

  • El modelo rastrea tanto la tasa de adopción (nuevos adoptantes por período de tiempo) como la adopción acumulativa (total de adoptantes hasta la fecha).

Esta estructura permite a los comercializadores comprender no solo cuántas personas adoptarán a lo largo del tiempo, sino también las fuerzas impulsoras detrás de la adopción en diferentes etapas del ciclo de vida del producto.

Comprendiendo los Parámetros Clave#

El modelo tiene tres parámetros principales:

  • Potencial de mercado (m): Número total de adoptantes eventuales (el tamaño final del mercado)

  • Coeficiente de innovación (p): Mide la influencia externa como la publicidad y los medios - típicamente \(0.01-0.03\)

  • Coeficiente de imitación (q): Mide la influencia interna como el boca a boca - típicamente \(0.3-0.5\)

Interpretación de Parámetros#

  • Un valor p más alto indica una influencia externa más fuerte (publicidad, marketing)

  • Un valor q más alto indica una influencia interna más fuerte (boca a boca, interacciones sociales)

  • La relación q/p indica la fuerza relativa de las influencias internas frente a las externas.

  • El pico de adopción ocurre en el momento {like_this}.

\[t^* = \frac{\ln(q / p)}{p + q}\]

Innovadores vs. Imitadores#

El modelo de Bass distingue entre dos tipos de adoptantes:

  1. Innovadores: Personas que adoptan independientemente de las decisiones de otros, influenciadas principalmente por los medios de comunicación masivos y las comunicaciones externas.

    • Representado matemáticamente como: \(\text{innovators}(t) = m \cdot p \cdot (1 - F(p, q, t))\)

  2. Imitadores: Personas que adoptan debido a la influencia social y el boca a boca de adoptantes anteriores.

    • Representado matemáticamente como: \(\text{imitators}(t) = m \cdot q \cdot F(p, q, t) \cdot (1 - F(p, q, t))\)

Aplicaciones del mundo real#

El modelo Bass se ha aplicado con éxito para predecir la adopción de diversos productos y tecnologías:

  • Bienes de consumo duraderos: televisores, refrigeradores, lavadoras

  • Productos tecnológicos: Smartphones, computadoras, software

  • Productos farmacéuticos: Nuevos medicamentos y tratamientos

  • Productos de entretenimiento: Películas, juegos, servicios de streaming

  • Servicios y suscripciones: Servicios bancarios, planes de suscripción

Valor Empresarial: Por qué el Modelo Bass es Importante para Ejecutivos y Marketers#

Desde una perspectiva empresarial, el modelo de difusión de Bass proporciona ventajas competitivas sustanciales y beneficios de ROI:

1. Resource Optimization and Cash Flow Management#

  • Planificación de la Producción: Evite la costosa sobreproducción o falta de stock al prever con precisión las curvas de demanda.

  • Asignación del Presupuesto de Marketing: Optimizar el gasto a lo largo del ciclo de vida del producto, invirtiendo más durante los puntos de inflexión clave.

  • Eficiencia de la Cadena de Suministro: Coordinar con proveedores y distribuidores en función de las tasas de adopción previstas.

  • Optimización del Flujo de Efectivo: Predecir mejor los flujos de ingresos, mejorando la planificación financiera y las relaciones con los inversores.

2. Strategic Decision Making#

  • Tiempo de Lanzamiento: Determine el momento óptimo para ingresar a un mercado basado en patrones de difusión.

  • Estrategia de Precios: Implementar estrategias de precios dinámicos alineadas con la curva de adopción

  • Análisis Competitivo: Compare los parámetros de adopción de su producto con los de los competidores para identificar fortalezas y debilidades.

  • Gestión del Portafolio de Productos: Tome decisiones informadas sobre cuándo retirar productos antiguos e introducir nuevos.

3. Risk Mitigation#

  • Planificación de Escenarios: Pruebe diferentes supuestos de mercado y factores externos a través de variaciones de parámetros

  • Sistema de Alerta Temprana: Identificar desviaciones de las curvas de adopción esperadas de manera temprana, lo que permite una intervención más rápida.

  • Justificación de la Inversión: Proporcione pronósticos basados en datos para justificar las inversiones en I+D y marketing ante los interesados.

4. Performance Measurement#

  • Efectividad del Marketing: Mida el impacto de las campañas de marketing en el coeficiente de innovación (p)

  • Fuerza del Boca a Boca: Cuantifique el poder de la influencia social de su marca a través del coeficiente de imitación (q)

  • Potencial Total del Mercado: Valide o ajuste sus estimaciones del mercado total direccionable (m)

En el entorno empresarial actual impulsado por datos, las empresas que utilizan eficazmente modelos como la difusión de Bass obtienen una ventaja competitiva significativa a través de pronósticos más precisos, una mejor asignación de recursos y un momento estratégico en el mercado.

Extensiones Bayesianas#

En este cuaderno, mostramos cómo generar datos simulados a partir del modelo de Bass y ajustar un modelo bayesiano a estos. La formulación bayesiana ofrece varias ventajas:

  • Cuantificación de la incertidumbre a través de distribuciones a priori sobre parámetros

  • Modelado jerárquico para múltiples productos o mercados

  • Incorporación de conocimiento experto a través de priors informativos

  • Distribuciones de probabilidad completas para pronósticos de adopción futuros

Lo que haremos en este cuaderno#

En este cuaderno, vamos a:

  1. Configurar parámetros para una simulación del modelo Bass

  2. Generar datos de adopción simulados para múltiples productos

  3. Ajuste el modelo Bass a nuestros datos simulados utilizando PyMC.

  4. Visualizar las curvas de adopción

Preparar el cuaderno#

from typing import Any

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import numpy.typing as npt
import pandas as pd
import pymc as pm
import xarray as xr
from pymc_extras.prior import Prior, Scaled

from pymc_marketing.bass.model import create_bass_model
from pymc_marketing.plot import plot_curve

az.style.use("arviz-darkgrid")
plt.rcParams["figure.figsize"] = [12, 7]
plt.rcParams["figure.dpi"] = 100

%load_ext autoreload
%autoreload 2
%config InlineBackend.figure_format = "retina"
seed: int = sum(map(ord, "bass"))
rng: np.random.Generator = np.random.default_rng(seed=seed)

Configuración de los parámetros de simulación#

Primero, configuraremos los parámetros para nuestra simulación. Esto incluye:

  • El período de tiempo para nuestra simulación (en semanas)

  • El número de productos a simular

  • Fechas de inicio para el período de simulación

def setup_simulation_parameters(
    n_weeks: int = 52,
    n_products: int = 9,
    start_date: str = "2023-01-01",
    cutoff_start_date: str = "2023-12-01",
) -> tuple[
    npt.NDArray[np.int_],
    pd.DatetimeIndex,
    pd.DatetimeIndex,
    list[str],
    pd.Series,
    dict[str, Any],
]:
    """Set up initial parameters for the Bass diffusion model simulation.

    Parameters
    ----------
    n_weeks : int
        Number of weeks to simulate
    n_products : int
        Number of products to include in the simulation
    start_date : str
        Starting date for the simulation period
    cutoff_start_date : str
        Latest possible start date for products

    Returns
    -------
    T : numpy.ndarray
        Time array (weeks)
    possible_dates : pandas.DatetimeIndex
        All dates in the simulation period
    possible_start_dates : pandas.DatetimeIndex
        Possible start dates for products
    products : list
        List of product names
    product_start : pandas.Series
        Start date for each product
    coords : dict
        Coordinates for PyMC model
    """
    # Set a seed for reproducibility
    seed = sum(map(ord, "bass"))
    rng = np.random.default_rng(seed)

    # Create time array and date range
    T = np.arange(n_weeks)
    possible_dates = pd.date_range(start_date, freq="W-MON", periods=n_weeks)
    cutoff_start_date = pd.to_datetime(cutoff_start_date)
    cutoff_start_date = cutoff_start_date + pd.DateOffset(weeks=1)
    possible_start_dates = possible_dates[possible_dates < cutoff_start_date]

    # Generate product names and random start dates
    products = [f"P{i}" for i in range(n_products)]
    product_start = pd.Series(
        rng.choice(possible_start_dates, size=len(products)),
        index=pd.Index(products, name="product"),
    )

    coords = {"T": T, "product": products}
    return T, possible_dates, possible_start_dates, products, product_start, coords

Creando Distribuciones Previas#

Para nuestro modelo de Bass bayesiano, necesitamos especificar distribuciones a priori para los parámetros clave:

  • m (potencial de mercado): Cuántas unidades se pueden vender potencialmente en total

  • p (coeficiente de innovación): Tasa de adopción de influencias externas

  • q (coeficiente de imitación): Tasa de adopción a partir de influencias internas/sociales

  • probabilidad: La distribución de probabilidad que modela los datos de adopción observados

Para el potencial de mercado m, utilizamos un truco de escalado para especificar un prior sin escala y luego añadimos un factor global:

def create_bass_priors(factor: float) -> dict[str, Prior | Scaled]:
    """Define prior distributions for the Bass model parameters.

    Returns
    -------
    dict
        Dictionary of prior distributions for m, p, q, and likelihood

    Notes
    -----
    - m: Market potential (scaled Gamma distribution)
    - p: Innovation coefficient (Beta distribution)
    - q: Imitation coefficient (Beta distribution)
    - likelihood: Observation model (Negative Binomial)
    """
    return {
        # We use a scaled Gamma distribution for the market potential.
        "m": Scaled(Prior("Gamma", mu=1, sigma=0.1, dims="product"), factor=factor),
        "p": Prior("Beta", mu=0.03, dims="product").constrain(lower=0.01, upper=0.03),
        "q": Prior("Beta", dims="product").constrain(lower=0.3, upper=0.5),
        "likelihood": Prior("NegativeBinomial", n=1.5, dims="product"),
    }

Generemos y visualicemos los priors.

FACTOR = 50_000
priors = create_bass_priors(factor=FACTOR)
/Users/will/mamba/envs/pymc-marketing-dev/lib/python3.10/site-packages/preliz/distributions/beta.py:127: RuntimeWarning: invalid value encountered in scalar divide
  mu = alpha / alpha_plus_beta
/Users/will/mamba/envs/pymc-marketing-dev/lib/python3.10/site-packages/preliz/distributions/beta.py:128: RuntimeWarning: invalid value encountered in scalar divide
  sigma = (alpha * beta) ** 0.5 / alpha_plus_beta / (alpha_plus_beta + 1) ** 0.5
 The requested mass is 0.95, but the computed one is 0.528
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(15, 12))

priors["p"].preliz.plot_pdf(ax=ax[0])
ax[0].set(title="Innovation Coefficient (p)")
priors["q"].preliz.plot_pdf(ax=ax[1])
ax[1].set(title="Imitation Coefficient (q)")
fig.suptitle(
    "Prior Distributions for Bass Model Parameters",
    fontsize=18,
    fontweight="bold",
    y=0.95,
);

Observe que hemos elegido los priors dentro de los rangos habituales de los estudios empíricos:

  • Coeficiente de innovación (p): Mide la influencia externa como la publicidad y los medios - típicamente \(0.01-0.03\)

  • Coeficiente de imitación (q): Mide la influencia interna como el boca a boca - típicamente \(0.3-0.5\)

Generar Datos Sintéticos#

Con el modelo generativo Bass, podemos generar un conjunto de datos sintético muestreando del previo y eligiendo una muestra particular para usar como datos observados. Para este propósito, definimos dos funciones auxiliares.

def sample_prior_bass_data(model: pm.Model) -> xr.DataArray:
    """Generate a sample from the prior predictive distribution of the Bass model.

    Parameters
    ----------
    model : pymc.Model
        The PyMC model to sample from

    Returns
    -------
    xarray.DataArray
        Simulated adoption data
    """
    with model:
        idata = pm.sample_prior_predictive(random_seed=rng)
    return idata["prior"]["y"].sel(chain=0, draw=0)


def transform_to_actual_dates(bass_data, product_start, possible_dates) -> pd.DataFrame:
    """Transform simulation data from time index to calendar dates.

    Parameters
    ----------
    bass_data : xarray.DataArray
        Simulated bass model data
    product_start : pandas.Series
        Start date for each product
    possible_dates : pandas.DatetimeIndex
        All dates in the simulation period

    Returns
    -------
    pandas.DataFrame
        Adoption data with actual calendar dates
    """
    bass_data = bass_data.to_dataset()
    bass_data["product_start"] = product_start.to_xarray()

    df_bass_data = (
        bass_data.to_dataframe().drop(columns=["chain", "draw"]).reset_index()
    )
    df_bass_data["actual_date"] = df_bass_data["product_start"] + pd.to_timedelta(
        7 * df_bass_data["T"], unit="days"
    )

    return (
        df_bass_data.set_index(["actual_date", "product"])
        .y.unstack(fill_value=0)
        .reindex(possible_dates, fill_value=0)
    )

Ahora podemos generar los datos observados:

# Setup simulation parameters
T, possible_dates, _, products, product_start, coords = setup_simulation_parameters()

# Create and configure the Bass model
generative_model = create_bass_model(t=T, coords=coords, observed=None, priors=priors)

# Sample and select one "observed" dataset.
bass_data = sample_prior_bass_data(generative_model)
actual_data = transform_to_actual_dates(bass_data, product_start, possible_dates)
Sampling: [m_unscaled, p, q, y]

El marco de datos actual_data tiene el formato típico de un conjunto de datos real.

actual_data
product P0 P1 P2 P3 P4 P5 P6 P7 P8
2023-01-02 0 0 451 0 0 0 0 0 0
2023-01-09 0 0 56 0 0 0 0 0 0
2023-01-16 0 0 1948 0 0 0 0 0 0
2023-01-23 0 0 3742 0 0 0 0 0 0
2023-01-30 0 239 156 0 0 137 0 0 0
2023-02-06 0 4199 4732 0 0 2208 0 0 0
2023-02-13 0 1646 6477 0 0 1671 0 0 0
2023-02-20 0 1810 2824 0 0 780 0 0 0
2023-02-27 0 17004 2583 1485 0 6002 0 0 0
2023-03-06 0 4956 2508 2679 0 8211 0 0 0
2023-03-13 1277 7956 3255 2340 0 2404 0 0 0
2023-03-20 1403 4926 1030 3838 0 3646 0 0 0
2023-03-27 1818 1091 3145 1443 0 13557 0 0 0
2023-04-03 2865 4696 613 85 0 2857 0 0 0
2023-04-10 1410 8236 717 2858 0 3112 0 0 0
2023-04-17 1472 499 77 3519 0 3920 0 0 0
2023-04-24 11868 1783 340 2431 0 1661 0 0 0
2023-05-01 4669 102 314 8487 0 2908 0 0 0
2023-05-08 3423 61 210 1479 0 2729 0 0 0
2023-05-15 3625 1226 11 1544 0 2125 0 0 0
2023-05-22 1591 256 110 391 0 1164 0 0 0
2023-05-29 3351 149 78 2397 0 849 0 0 0
2023-06-05 1246 205 9 205 0 1060 0 0 0
2023-06-12 1805 106 22 187 0 229 0 0 0
2023-06-19 1353 75 15 131 0 38 0 0 0
2023-06-26 457 48 5 116 0 88 0 0 0
2023-07-03 170 46 8 108 0 79 0 0 0
2023-07-10 44 67 6 57 0 16 0 0 0
2023-07-17 271 8 1 37 0 14 0 0 0
2023-07-24 42 0 1 4 0 19 0 0 2578
2023-07-31 32 6 1 9 0 0 0 0 1418
2023-08-07 93 5 0 3 0 10 0 0 1005
2023-08-14 83 1 1 0 0 4 0 0 2560
2023-08-21 47 0 0 3 0 1 0 0 3666
2023-08-28 8 0 0 1 0 0 0 0 13816
2023-09-04 4 0 0 0 0 0 0 2496 7358
2023-09-11 4 0 0 0 0 0 556 1266 6785
2023-09-18 2 0 0 0 0 3 2151 3637 2370
2023-09-25 2 0 0 0 0 0 4980 1431 1351
2023-10-02 0 1 0 1 0 0 2124 507 2038
2023-10-09 0 0 0 0 0 0 14239 1632 3441
2023-10-16 0 0 0 0 0 0 25224 4681 562
2023-10-23 0 0 0 0 0 0 17233 2095 3801
2023-10-30 0 0 0 0 1042 0 4728 1408 446
2023-11-06 0 0 0 0 1776 0 1890 1890 487
2023-11-13 0 0 0 0 1697 0 1058 3665 673
2023-11-20 0 0 0 0 6648 0 943 391 169
2023-11-27 0 0 0 0 4746 0 605 2720 374
2023-12-04 0 0 0 0 1843 0 1554 1382 98
2023-12-11 0 0 0 0 3598 0 2304 147 151
2023-12-18 0 0 0 0 6576 0 124 2582 42
2023-12-25 0 0 0 0 3610 0 215 110 160

Por otro lado, los bass_data tienen los mismos datos que los arreglos indexados por tiempo (relativo) y producto.

Visualicemos ambos.

fig, ax = plt.subplots(
    nrows=2, ncols=1, figsize=(15, 12), sharex=False, sharey=True, layout="constrained"
)

# Plot raw simulated data (by time step)
bass_data.to_series().unstack().plot(ax=ax[0])
ax[0].legend(
    title="Product", title_fontsize=14, loc="center left", bbox_to_anchor=(1, 0.5)
)
ax[0].set(
    title="Simulated Weekly Adoption by Product (Time Steps)",
    xlabel="Time Step (Weeks)",
    ylabel="Number of Adoptions",
)

# Plot data with actual calendar dates
actual_data.plot(ax=ax[1])
ax[1].legend(
    title="Product", title_fontsize=14, loc="center left", bbox_to_anchor=(1, 0.5)
)
ax[1].set(
    title="Simulated Weekly Adoption by Product (Calendar Dates)",
    xlabel="Date",
    ylabel="Number of Adoptions",
)

fig.suptitle(
    "Bass Diffusion Model - Simulated Product Adoption", fontsize=18, fontweight="bold"
);

Ajustar el Modelo#

Ahora estamos listos para ajustar el modelo y generar las distribuciones predictivas posteriores.

# We condition the model on observed data.
with pm.observe(generative_model, {"y": bass_data.values}) as model:
    idata = pm.sample(
        tune=1_500,
        draws=2_000,
        chains=4,
        nuts_sampler="nutpie",
        compile_kwargs={"mode": "NUMBA"},
        random_seed=rng,
    )

    idata.extend(
        pm.sample_posterior_predictive(
            idata, model=model, extend_inferencedata=True, random_seed=rng
        )
    )
Sampling ... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00 / 0:00:01

No tenemos ninguna divergencia. Veamos el resumen de los parámetros.

az.summary(data=idata, var_names=["p", "q", "m"])
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p[P0] 0.026 0.005 0.018 0.036 0.000 0.000 10750.0 5899.0 1.0
p[P1] 0.030 0.005 0.020 0.039 0.000 0.000 11579.0 6089.0 1.0
p[P2] 0.027 0.005 0.017 0.036 0.000 0.000 11292.0 5970.0 1.0
p[P3] 0.030 0.005 0.020 0.040 0.000 0.000 12126.0 6250.0 1.0
p[P4] 0.026 0.005 0.018 0.035 0.000 0.000 12027.0 6287.0 1.0
p[P5] 0.021 0.004 0.013 0.028 0.000 0.000 10939.0 5817.0 1.0
p[P6] 0.032 0.005 0.022 0.042 0.000 0.000 11071.0 5834.0 1.0
p[P7] 0.030 0.005 0.020 0.039 0.000 0.000 12427.0 5130.0 1.0
p[P8] 0.030 0.005 0.021 0.040 0.000 0.000 11252.0 6353.0 1.0
q[P0] 0.402 0.019 0.368 0.439 0.000 0.000 10124.0 5923.0 1.0
q[P1] 0.394 0.019 0.360 0.429 0.000 0.000 10431.0 6172.0 1.0
q[P2] 0.397 0.019 0.363 0.434 0.000 0.000 11818.0 6179.0 1.0
q[P3] 0.450 0.022 0.413 0.493 0.000 0.000 12285.0 6374.0 1.0
q[P4] 0.354 0.017 0.324 0.386 0.000 0.000 11480.0 6068.0 1.0
q[P5] 0.385 0.018 0.350 0.418 0.000 0.000 9830.0 5566.0 1.0
q[P6] 0.430 0.020 0.393 0.468 0.000 0.000 12521.0 6256.0 1.0
q[P7] 0.344 0.017 0.311 0.375 0.000 0.000 12231.0 5460.0 1.0
q[P8] 0.339 0.017 0.307 0.369 0.000 0.000 10948.0 6146.0 1.0
m[P0] 49568.996 4491.414 40760.951 57490.821 41.520 55.743 11720.0 5933.0 1.0
m[P1] 51816.841 4487.349 43766.599 60653.332 41.988 55.334 11242.0 5793.0 1.0
m[P2] 47118.829 4436.392 39310.947 55843.304 41.551 54.987 11186.0 5118.0 1.0
m[P3] 48429.471 4518.438 40231.737 57157.439 41.948 54.340 11472.0 5854.0 1.0
m[P4] 51994.167 4501.491 43959.011 60680.809 43.404 54.416 10713.0 5973.0 1.0
m[P5] 53276.493 4568.591 45196.257 62426.608 46.587 61.073 9433.0 5492.0 1.0
m[P6] 53087.905 4612.168 44525.164 61948.611 45.914 58.424 10125.0 5873.0 1.0
m[P7] 47684.913 4315.345 39879.468 55899.426 40.015 50.408 11727.0 6210.0 1.0
m[P8] 51232.596 4464.032 42849.916 59268.568 43.671 57.224 10498.0 5213.0 1.0
_ = az.plot_trace(
    data=idata,
    var_names=["p", "q", "m"],
    compact=True,
    backend_kwargs={"figsize": (12, 7), "layout": "constrained"},
)
plt.gcf().suptitle("Model Trace", fontsize=16);

En general, los diagnósticos y el seguimiento se ven bien.

A continuación, examinamos las distribuciones posteriores de los parámetros.

ax, *_ = az.plot_forest(idata["posterior"]["p"], combined=True)
ax.axvline(x=priors["p"].parameters["mu"], color="gray", linestyle="--")
ax.get_figure().suptitle("Innovation Coefficient (p)", fontsize=18, fontweight="bold")
Text(0.5, 0.98, 'Innovation Coefficient (p)')
../../_images/136f8cbb2f7bbfbea59d5751fd74cf7f5e43eaf4a0ae59e8ec0d88132e8409bd.png
ax, *_ = az.plot_forest(idata["posterior"]["q"], combined=True)
ax.axvline(x=priors["q"].preliz.mean(), color="gray", linestyle="--")
ax.get_figure().suptitle("Imitation Coefficient (q)", fontsize=18, fontweight="bold")
Text(0.5, 0.98, 'Imitation Coefficient (q)')
../../_images/c8e9a3ff323878bda958c34dd2aae218bc69196ff373e56d31a1ecf9ddcaf82a.png

Observamos cierta heterogeneidad en los parámetros, pero en general están centrados en los valores verdaderos (del modelo generativo).

Examinando las Predicciones Posteriores para Productos Específicos#

Veamos las distribuciones predictivas posteriores para ver qué tan bien nuestro modelo captura los datos simulados.

fig, axes = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 12), sharex=True, sharey=True, layout="constrained"
)

idata["posterior_predictive"]["y"].pipe(plot_curve, {"T"}, axes=axes)

for i, ax in enumerate(axes.flatten()):
    ax.plot(T, bass_data[:, i], color="black")

fig.suptitle("Posterior Predictive vs Observed Data", fontsize=18, fontweight="bold");
fig, axes = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 12), sharex=True, sharey=True, layout="constrained"
)

idata["posterior_predictive"]["y"].cumsum(dim="T").pipe(plot_curve, {"T"}, axes=axes)

for i, ax in enumerate(axes.flatten()):
    ax.plot(T, bass_data[:, i].cumsum(), color="black")

fig.suptitle(
    "Cumulative Posterior Predictive vs Cumulative Observed Data",
    fontsize=18,
    fontweight="bold",
);
observed_cumulative = bass_data.cumsum(dim="T").isel(T=-1).to_series()

ref_val = {
    "m": [
        {"product": name, "ref_val": value}
        for name, value in observed_cumulative.items()
    ]
}

az.plot_posterior(
    idata.posterior,
    var_names=["m"],
    backend_kwargs=dict(sharex=True, layout="constrained", figsize=(15, 12)),
    ref_val=ref_val,
)

max_T = bass_data.coords["T"].max().item()
fig = plt.gcf()
fig.suptitle(
    f"Estimated Market Cap (m) vs Observed Cumulative at T={max_T}",
    fontsize=18,
    fontweight="bold",
);

En general, el modelo hace un buen trabajo al capturar los datos.

A continuación, examinamos a los adoptantes, que representan el valor esperado de la probabilidad.

fig, axes = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 12), sharex=True, sharey=True, layout="constrained"
)

idata["posterior"]["adopters"].pipe(plot_curve, {"T"}, axes=axes)

for i, ax in enumerate(axes.flatten()):
    ax.plot(T, bass_data[:, i], color="black")

fig.suptitle("Adopters vs Observed Data", fontsize=18, fontweight="bold");

Esto muestra que el ajuste es, de hecho, bastante razonable.

También podemos evaluar la calidad del modelo analizando los datos acumulativos:

Nota

Recuerde que los adoptantes son la media de la distribución, por lo que vemos algunas curvas acumulativas por encima y algunas por debajo.

Mire el idata["posterior_predictive"]["y"] para los datos observados.

fig, axes = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 12), sharex=True, sharey=True, layout="constrained"
)

idata["posterior"]["adopters"].cumsum(dim="T").pipe(plot_curve, {"T"}, axes=axes)

for i, ax in enumerate(axes.flatten()):
    ax.plot(T, bass_data[:, i].cumsum(), color="black")

fig.suptitle("Adopters Cumulative vs Observed Data", fontsize=18, fontweight="bold");

Podemos mejorar esta vista al analizar los componentes del modelo: innovadores e imitadores (en naranja y verde, respectivamente).

fig, axes = plt.subplots(
    nrows=3, ncols=3, figsize=(15, 12), sharex=True, sharey=True, layout="constrained"
)

idata["posterior"]["adopters"].cumsum(dim="T").pipe(
    plot_curve, {"T"}, colors=3 * 3 * ["C0"], axes=axes
)

idata["posterior"]["innovators"].pipe(
    plot_curve, {"T"}, colors=3 * 3 * ["C1"], axes=axes
)
idata["posterior"]["imitators"].pipe(
    plot_curve, {"T"}, colors=3 * 3 * ["C2"], axes=axes
)

for i, ax in enumerate(axes.flatten()):
    ax.plot(T, bass_data[:, i].cumsum(), color="black")

fig.suptitle("Innovators vs Imitators", fontsize=18, fontweight="bold");

Finalmente, podemos inspeccionar el pico de la curva de adopción.

ax, *_ = az.plot_forest(idata["posterior"]["peak"], combined=True)
ax.get_figure().suptitle("Peak", fontsize=18, fontweight="bold");

Esto se ajusta bastante bien a los datos observados. Veamos, por ejemplo, el producto P4.

fig, ax = plt.subplots()

product_id = 4

bass_data[:, product_id].plot(ax=ax, color="black")

idata["posterior"]["adopters"].sel(product=f"P{product_id}").pipe(
    plot_curve, {"T"}, axes=ax
)

peak_hdi = az.hdi(idata["posterior"]["peak"].sel(product=f"P{product_id}"))["peak"]
ax.axvspan(
    peak_hdi.sel(hdi="lower").item(),
    peak_hdi.sel(hdi="higher").item(),
    color="C1",
    alpha=0.4,
)

ax.set_title(f"Peak Product {products[product_id]}", fontsize=18, fontweight="bold");
%load_ext watermark
%watermark -n -u -v -iv -w -p nutpie,pymc_marketing,pytensor
Last updated: Wed May 21 2025

Python implementation: CPython
Python version       : 3.10.16
IPython version      : 8.34.0

nutpie        : 0.14.3
pymc_marketing: 0.13.1
pytensor      : 2.30.3

pymc_marketing: 0.13.1
xarray        : 2025.3.1
arviz         : 0.21.0
numpy         : 1.26.4
pandas        : 2.2.3
matplotlib    : 3.10.1
pymc          : 5.22.0

Watermark: 2.5.0