17  Análisis conjoint basado en elecciones (CBC)

Published

June 29, 2026

17.1 Introducción: de valorar perfiles a elegir entre alternativas

El capítulo anterior desarrolló el conjoint tradicional, donde cada encuestado valora perfiles de producto de forma aislada. Aunque sencillo de estimar, este enfoque exige al encuestado una tarea cognitiva poco realista: en la práctica, nadie compra un producto evaluándolo en solitario, sino comparándolo directamente con las alternativas disponibles en el mercado.

El análisis conjoint basado en elecciones (Choice-Based Conjoint, CBC) resuelve esta limitación: en cada tarea, se presentan al encuestado varios perfiles de producto a la vez —normalmente entre 2 y 4— junto con la opción de no elegir ninguno (“ninguna de las anteriores”), y se le pide que seleccione cuál compraría. Cada encuestado responde a varias tareas de elección sucesivas, cada una con una combinación distinta de perfiles. Esta dinámica simula de forma mucho más realista el proceso real de decisión de compra, y es la razón por la que el CBC ha desplazado al conjoint tradicional como estándar de la industria desde la década de 1990.

TipUna nota para quien viene de SPSS

El análisis CBC no tiene equivalente en SPSS Conjoint, que está orientado exclusivamente al conjoint tradicional desarrollado en el capítulo anterior. El CBC se aborda en R mediante modelos logit condicionales, siendo mlogit el paquete de referencia académica más extendido y documentado para esta técnica.

17.2 El modelo estadístico: la teoría de la utilidad aleatoria

El fundamento estadístico del CBC es la teoría de la utilidad aleatoria (Random Utility Theory), formalizada por Daniel McFadden, premio Nobel de Economía por este trabajo (McFadden 1974). Su idea central: cada individuo asigna a cada alternativa una utilidad (un nivel de satisfacción o atractivo) compuesta por una parte sistemática, explicable por los atributos observados, y una parte aleatoria, que recoge factores no observados:

\[U_{ij} = V_{ij} + \varepsilon_{ij}\]

donde \(U_{ij}\) es la utilidad total que el individuo \(i\) asigna a la alternativa \(j\), \(V_{ij}\) es la utilidad sistemática (función de los atributos del producto) y \(\varepsilon_{ij}\) es el componente aleatorio. El individuo elige la alternativa que le proporciona la mayor utilidad total.

La utilidad sistemática se modela como una combinación lineal de los niveles de los atributos:

\[V_{ij} = \beta_1 X_{1j} + \beta_2 X_{2j} + \dots + \beta_k X_{kj}\]

Cada coeficiente \(\beta\) representa la parte de utilidad (part-worth utility) que aporta cada nivel de cada atributo: cuánto contribuye, por ejemplo, tener 16 GB en lugar de 8 GB a la utilidad total del producto.

Bajo el supuesto de que los errores \(\varepsilon_{ij}\) siguen una distribución de valores extremos (Gumbel), se obtiene el modelo logit condicional (conditional logit), cuya probabilidad de elección sigue la forma:

\[P(j) = \frac{e^{V_j}}{\sum_{k=1}^{J} e^{V_k}}\]

Esta es la ecuación que estima el modelo: la probabilidad de elegir la alternativa \(j\) depende de su utilidad relativa frente a todas las demás alternativas presentadas en esa tarea de elección.

17.3 De los coeficientes a las decisiones de negocio

Una vez estimado el modelo, sus coeficientes (\(\beta\)) se traducen en tres tipos de información directamente accionable para el negocio.

17.3.1 Importancia relativa de los atributos

Para cada atributo, se calcula el rango de utilidad que abarca (la diferencia entre su nivel más preferido y el menos preferido). Los atributos con mayor rango son los que más influyen en la decisión de compra; normalizando estos rangos a porcentajes se obtiene la importancia relativa de cada atributo.

17.3.2 Disposición a pagar (Willingness to Pay)

Cuando el precio es uno de los atributos del modelo, su coeficiente puede usarse como “tipo de cambio” entre utilidad y dinero. La disposición a pagar por mejorar de un nivel a otro en cualquier atributo se calcula como el cociente entre la diferencia de utilidad de esos niveles y el coeficiente del precio:

\[WTP = \frac{\beta_{atributo}}{-\beta_{precio}}\]

17.3.3 Simulación de cuota de mercado

El resultado más potente del CBC: una vez estimado el modelo, se puede simular cómo se repartiría la preferencia de los encuestados entre cualquier conjunto hipotético de productos —incluyendo productos que aún no existen en el mercado—, aplicando la ecuación de probabilidad de elección a un nuevo conjunto de perfiles.

17.4 Aplicación práctica: diseño de un lector de libros electrónicos

Se retoma el caso del capítulo anterior —el diseño de un nuevo lector de libros electrónicos— pero ahora con un estudio CBC: en lugar de valorar perfiles aislados, los encuestados eligen entre varios perfiles presentados conjuntamente.

17.4.1 Objetivo de la investigación

Objetivo: estimar un modelo de elección discreta (CBC) a partir de los datos de una encuesta, para determinar la importancia relativa de cada atributo del producto, calcular la disposición a pagar por las mejoras de cada atributo, y simular la cuota de preferencia de distintas configuraciones de producto.

17.4.2 Preparación de los datos

Se ha encuestado a 200 consumidores, cada uno respondiendo a 8 tareas de elección entre 3 perfiles de lectores electrónicos. Los atributos testados son los mismos del capítulo anterior: capacidad de almacenamiento (8 GB / 16 GB), tamaño de pantalla (5” / 6” / 7”), color (negro / blanco / plateado) y precio (99€ / 129€ / 159€).

Los datos de un estudio CBC se estructuran en formato largo: una fila por cada alternativa presentada en cada tarea de elección, con una variable binaria que indica si esa alternativa fue la elegida.

Code
set.seed(7)

n_respondents   <- 200
n_tasks         <- 8
n_alternatives  <- 3

storage_levels <- c("8GB", "16GB")
screen_levels  <- c("5in", "6in", "7in")
color_levels   <- c("Black", "White", "Silver")
price_levels   <- c(99, 129, 159)

# True (simulated) part-worth utilities, used to generate realistic choices
true_utilities <- c(
  storage16GB  = 0.9,
  screen6in    = 0.5, screen7in = 0.3,
  colorWhite   = 0.2, colorSilver = 0.4,
  price        = -0.025  # negative: higher price reduces utility
)

# Build the full design: one row per alternative per task per respondent
ebook_design <- expand.grid(
  resp_id  = 1:n_respondents,
  task_id  = 1:n_tasks,
  alt_id   = 1:n_alternatives
) |>
  dplyr::arrange(resp_id, task_id, alt_id) |>
  dplyr::mutate(
    storage = sample(storage_levels, dplyr::n(), replace = TRUE),
    screen  = sample(screen_levels,  dplyr::n(), replace = TRUE),
    color   = sample(color_levels,   dplyr::n(), replace = TRUE),
    price   = sample(price_levels,   dplyr::n(), replace = TRUE)
  )

# Compute systematic utility for each alternative using the true part-worths
ebook_design <- ebook_design |>
  dplyr::mutate(
    utility = dplyr::if_else(storage == "16GB", true_utilities["storage16GB"], 0) +
              dplyr::case_when(
                screen == "6in" ~ true_utilities["screen6in"],
                screen == "7in" ~ true_utilities["screen7in"],
                TRUE ~ 0
              ) +
              dplyr::case_when(
                color == "White"  ~ true_utilities["colorWhite"],
                color == "Silver" ~ true_utilities["colorSilver"],
                TRUE ~ 0
              ) +
              price * true_utilities["price"] +
              rnorm(dplyr::n(), 0, 1)  # random utility component
  )

# Within each task, the respondent picks the alternative with the highest utility
ebook_data <- ebook_design |>
  dplyr::group_by(resp_id, task_id) |>
  dplyr::mutate(choice = as.integer(utility == max(utility))) |>
  dplyr::ungroup() |>
  dplyr::select(resp_id, task_id, alt_id, storage, screen, color, price, choice)

head(ebook_data, 9)
# A tibble: 9 × 8
  resp_id task_id alt_id storage screen color  price choice
    <int>   <int>  <int> <chr>   <chr>  <chr>  <dbl>  <int>
1       1       1      1 16GB    6in    Silver   159      0
2       1       1      2 8GB     5in    Silver   159      1
3       1       1      3 8GB     5in    White     99      0
4       1       2      1 16GB    7in    White    129      0
5       1       2      2 8GB     7in    Black    129      0
6       1       2      3 16GB    6in    Silver    99      1
7       1       3      1 8GB     7in    White    159      0
8       1       3      2 16GB    7in    Silver   129      1
9       1       3      3 16GB    5in    Black    159      0

Cada bloque de 3 filas corresponde a una tarea de elección: las tres alternativas presentadas a un encuestado en un momento dado, con un 1 en choice para la alternativa seleccionada.

17.4.3 Preparación de los datos para el modelo

Antes de construir el modelo, es necesario convertir explícitamente a factor las variables categóricas del diseño (storage, screen, color), fijando además el orden de sus niveles. Si estas variables permanecen como texto (character), mlogit() puede nombrar los coeficientes estimados de forma genérica y posicional (storage1, screen1, screen2…) en lugar de usar las etiquetas reales de cada nivel (storage16GB, screen6in…), lo que rompe cualquier referencia posterior a esos coeficientes por nombre:

Code
# Explicitly convert categorical attributes to factors with a fixed level
# order. Without this step, mlogit() may name the estimated coefficients
# generically (storage1, screen1...) instead of using the actual level
# labels (storage16GB, screen6in...), breaking every downstream reference
# to those coefficients by name.
ebook_data <- ebook_data |>
  dplyr::mutate(
    storage = factor(storage, levels = c("8GB", "16GB")),
    screen  = factor(screen,  levels = c("5in", "6in", "7in")),
    color   = factor(color,   levels = c("Black", "White", "Silver"))
  )

El paquete mlogit requiere transformar los datos a una estructura específica con mlogit.data(), indicando qué variable contiene la elección y cómo se agrupan las alternativas dentro de cada tarea:

Code
ebook_mlogit <- mlogit::mlogit.data(
  ebook_data,
  choice  = "choice",
  shape   = "long",
  alt.var = "alt_id",
  id.var  = "resp_id"
)
ImportantSobre el sistema de contrastes

R puede codificar las variables categóricas de un modelo con distintos sistemas de contrastes. El más habitual, y el que asume el resto de este capítulo, es contr.treatment: cada nivel se compara contra un nivel de referencia, y los coeficientes del modelo se nombran con la etiqueta real de cada nivel (por ejemplo, storage8GB). Si la sesión de R tiene activado otro sistema —como contr.sum, los contrastes de suma—, mlogit() nombra los coeficientes de forma genérica y posicional (storage1, screen1…), lo que rompe cualquier referencia posterior a esos coeficientes por nombre. Para que el código de este capítulo sea robusto frente a cualquier configuración previa de la sesión, se fija explícitamente contr.treatment justo antes de estimar el modelo, y se restaura la configuración original justo después.

17.4.4 Ejecución del modelo logit condicional

Se especifica el modelo con la sintaxis de fórmula de mlogit(). El sufijo | 0 indica que no se incluye una constante de alternativa (intercepto), apropiado cuando las alternativas no tienen una identidad propia más allá de sus atributos:

Code
# Force standard treatment contrasts (dummy coding against a reference
# level), regardless of any global contrasts option set elsewhere in the
# session (e.g. contr.sum). This guarantees mlogit() names coefficients
# using actual level labels (storage8GB, screen6in...) instead of generic
# positional names (storage1, screen1...).
old_contrasts <- options("contrasts")
options(contrasts = c("contr.treatment", "contr.poly"))

cbc_model <- mlogit::mlogit(
  choice ~ storage + screen + color + price | 0,
  data = ebook_mlogit
)

options(old_contrasts)  # restore the session's original setting

summary(cbc_model)

Call:
mlogit::mlogit(formula = choice ~ storage + screen + color + 
    price | 0, data = ebook_mlogit, method = "nr")

Frequencies of alternatives:choice
      1       2       3 
0.35750 0.32125 0.32125 

nr method
5 iterations, 0h:0m:0s 
g'(-H)^-1g = 3.08E-05 
successive function values within tolerance limits 

Coefficients :
              Estimate Std. Error  z-value  Pr(>|z|)    
storage16GB  1.0232667  0.0744851  13.7379 < 2.2e-16 ***
screen6in    0.5768756  0.0869516   6.6344 3.257e-11 ***
screen7in    0.3159081  0.0888743   3.5545 0.0003786 ***
colorWhite   0.3493719  0.0882643   3.9582 7.550e-05 ***
colorSilver  0.5307838  0.0861650   6.1601 7.271e-10 ***
price       -0.0320995  0.0015942 -20.1351 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-Likelihood: -1388.4

17.4.5 Interpretación de los coeficientes

Code
cbc_coefs <- coef(cbc_model)
cbc_coefs
storage16GB   screen6in   screen7in  colorWhite colorSilver       price 
 1.02326671  0.57687557  0.31590813  0.34937192  0.53078380 -0.03209951 
  • Signo: todos los coeficientes de atributos “deseables” (16 GB, pantallas mayores, colores distintos del negro) son positivos, indicando que aumentan la utilidad y, por tanto, la probabilidad de elección, frente a sus respectivas categorías de referencia (8 GB, pantalla de 5”, color negro). El coeficiente de price es negativo, como cabe esperar: a mayor precio, menor utilidad.
  • Significación: los p-valores (Pr(>|z|)) indican qué atributos influyen de forma estadísticamente significativa en la elección. En este caso, todos los coeficientes son significativos.
  • Magnitud: el coeficiente de storage16GB es el más alto de los atributos no relacionados con el precio, sugiriendo que la capacidad de almacenamiento es el atributo con mayor impacto en la utilidad, después del propio precio.

17.4.6 Importancia relativa de los atributos

Para cada atributo, se calcula el rango de utilidad que cubre (diferencia entre el nivel de mayor y menor utilidad), y se normaliza a porcentaje sobre la suma de todos los rangos:

Code
attribute_ranges <- c(
  storage = abs(unname(cbc_coefs["storage16GB"])),
  screen  = max(0, cbc_coefs["screen6in"], cbc_coefs["screen7in"]) -
            min(0, cbc_coefs["screen6in"], cbc_coefs["screen7in"]),
  color   = max(0, cbc_coefs["colorWhite"], cbc_coefs["colorSilver"]) -
            min(0, cbc_coefs["colorWhite"], cbc_coefs["colorSilver"]),
  price   = abs(cbc_coefs["price"] * (max(price_levels) - min(price_levels)))
)

importance_data <- data.frame(
  attribute  = names(attribute_ranges),
  importance = round(100 * attribute_ranges / sum(attribute_ranges), 1)
)

importance_data
              attribute importance
storage         storage       25.2
screen           screen       14.2
color             color       13.1
price.price price.price       47.5
Code
ggplot2::ggplot(importance_data, ggplot2::aes(x = reorder(attribute, importance), y = importance)) +
  ggplot2::geom_col(fill = "#076fa2") +
  ggplot2::geom_text(ggplot2::aes(label = paste0(importance, "%")), hjust = -0.1) +
  ggplot2::coord_flip() +
  ggplot2::scale_y_continuous(limits = c(0, max(importance_data$importance) * 1.2)) +
  ggplot2::labs(x = NULL, y = "Importancia relativa (%)") +
  ggplot2::theme_minimal()
Figure 17.1: Importancia relativa de los atributos del producto

El precio resulta ser el atributo más determinante en la decisión, seguido de la capacidad de almacenamiento; el color es el atributo de menor peso relativo en la elección.

17.4.7 Disposición a pagar (Willingness to Pay)

Se calcula cuánto estaría dispuesto a pagar un cliente por mejorar de un nivel a otro en cada atributo, usando el coeficiente del precio como referencia de conversión entre utilidad y euros:

Code
wtp_storage      <- unname(cbc_coefs["storage16GB"] / -cbc_coefs["price"])
wtp_screen_6in   <- unname(cbc_coefs["screen6in"]   / -cbc_coefs["price"])
wtp_screen_7in   <- unname(cbc_coefs["screen7in"]   / -cbc_coefs["price"])
wtp_color_white  <- unname(cbc_coefs["colorWhite"]  / -cbc_coefs["price"])
wtp_color_silver <- unname(cbc_coefs["colorSilver"] / -cbc_coefs["price"])

wtp_table <- data.frame(
  upgrade = c("8GB -> 16GB", "5in -> 6in", "5in -> 7in",
              "Black -> White", "Black -> Silver"),
  wtp_eur = round(c(wtp_storage, wtp_screen_6in, wtp_screen_7in,
                    wtp_color_white, wtp_color_silver), 1)
)

wtp_table
          upgrade wtp_eur
1     8GB -> 16GB    31.9
2      5in -> 6in    18.0
3      5in -> 7in     9.8
4  Black -> White    10.9
5 Black -> Silver    16.5

Los clientes estarían dispuestos a pagar un sobreprecio notable por pasar de 8 a 16 GB de almacenamiento, mientras que la disposición a pagar por el color es mucho más modesta.

17.4.8 Simulación de cuota de preferencia

Code
# Define three hypothetical products competing in the market
market_scenario <- data.frame(
  product  = c("Economy", "Mainstream", "Premium"),
  storage  = c("8GB", "16GB", "16GB"),
  screen   = c("5in", "6in", "7in"),
  color    = c("Black", "White", "Silver"),
  price    = c(99, 129, 159)
)

# Compute the systematic utility of each product using the estimated coefficients
market_scenario <- market_scenario |>
  dplyr::mutate(
    utility = dplyr::if_else(storage == "16GB", unname(cbc_coefs["storage16GB"]), 0) +
              dplyr::case_when(
                screen == "6in" ~ cbc_coefs["screen6in"],
                screen == "7in" ~ cbc_coefs["screen7in"],
                TRUE ~ 0
              ) +
              dplyr::case_when(
                color == "White"  ~ cbc_coefs["colorWhite"],
                color == "Silver" ~ cbc_coefs["colorSilver"],
                TRUE ~ 0
              ) +
              price * cbc_coefs["price"]
  )

# Apply the conditional logit choice probability formula
market_scenario <- market_scenario |>
  dplyr::mutate(
    market_share = round(100 * exp(utility) / sum(exp(utility)), 1)
  )

market_scenario[, c("product", "storage", "screen", "color", "price", "market_share")]
     product storage screen  color price market_share
1    Economy     8GB    5in  Black    99         21.6
2 Mainstream    16GB    6in  White   129         58.0
3    Premium    16GB    7in Silver   159         20.4
Code
ggplot2::ggplot(market_scenario, ggplot2::aes(x = product, y = market_share)) +
  ggplot2::geom_col(fill = "#076fa2") +
  ggplot2::geom_text(ggplot2::aes(label = paste0(market_share, "%")), vjust = -0.5) +
  ggplot2::labs(x = NULL, y = "Cuota de preferencia simulada (%)") +
  ggplot2::theme_minimal()
Figure 17.2: Cuota de preferencia simulada entre tres configuraciones de producto

La simulación permite comparar distintas estrategias de producto antes de fabricar nada: por ejemplo, evaluar si merece la pena el coste adicional de una pantalla mayor frente a una reducción de precio, observando directamente el impacto en la cuota de preferencia simulada.

17.4.9 Conclusiones

El análisis conjoint basado en elecciones ha permitido transformar las respuestas de una encuesta en información directamente accionable para el diseño de producto:

  1. Importancia de los atributos: el precio y la capacidad de almacenamiento son los factores más determinantes en la decisión de compra; el color tiene un peso relativo menor.
  2. Disposición a pagar: se ha cuantificado en euros el valor que los consumidores otorgan a cada mejora de atributo, información directamente útil para decisiones de pricing.
  3. Simulación de mercado: el modelo permite evaluar cualquier configuración de producto antes de su lanzamiento, comparando estrategias de diseño y precio de forma cuantitativa.

El capítulo siguiente retoma este mismo caso práctico para explorar dos paquetes complementarios a mlogit: cbcTools, para el diseño y la evaluación estadística de experimentos CBC antes de la recogida de datos, y logitr, para la estimación de modelos logit con parametrización directa en espacio de disposición a pagar.