18  Diseño de experimentos y estimación avanzada: cbcTools y logitr

Published

June 29, 2026

18.1 Introducción: diseñar antes de recoger, estimar con más flexibilidad

El capítulo anterior partió de una encuesta CBC ya recogida, con un diseño de tareas de elección generado de forma simplificada (combinaciones aleatorias de niveles). En un proyecto real, el diseño del experimento —qué perfiles concretos se muestran a cada encuestado, en qué combinaciones, con qué grado de equilibrio estadístico— es una decisión metodológica crítica que precede a la recogida de datos, y de la que depende directamente la calidad de las estimaciones posteriores. Además, el modelo logit condicional estimado con mlogit es la base, pero no agota las posibilidades de modelado: existen parametrizaciones alternativas que abordan limitaciones del modelo logit condicional estándar.

Estas dos cuestiones son precisamente el objeto de dos paquetes de R desarrollados por John Paul Helveston (Universidad George Washington): cbcTools, para el diseño y la evaluación de experimentos CBC antes de su lanzamiento al campo, y logitr, para la estimación rápida de modelos logit con parametrizaciones alternativas a las de mlogit (Helveston 2023, 2025). Ambos paquetes no sustituyen a mlogit —que sigue siendo el estándar académico de referencia desarrollado en el capítulo anterior—, sino que lo complementan: cbcTools interviene antes de la recogida de datos, en la fase de diseño; logitr ofrece una alternativa de estimación más rápida y con parametrizaciones adicionales una vez los datos ya están disponibles.

18.2 Diseño de experimentos CBC con cbcTools

Antes de salir a campo, es necesario decidir qué perfiles concretos se presentarán a cada encuestado y en qué tareas de elección se agruparán. Un diseño deficiente —por ejemplo, uno en el que dos niveles de un atributo aparezcan siempre juntos— puede hacer que sea estadísticamente imposible separar sus efectos individuales sobre la utilidad. El paquete cbcTools automatiza este proceso siguiendo un flujo de varios pasos: generar el universo de perfiles posibles, generar el diseño del experimento, inspeccionar su calidad estadística, y simular las elecciones que produciría una muestra de encuestados antes incluso de salir a campo (Helveston 2025).

18.2.1 Generación del universo de perfiles

Code
# Step 1: generate the universe of all possible profiles from the attribute levels
ebook_profiles <- cbcTools::cbc_profiles(
  storage = c("8GB", "16GB"),
  screen  = c("5in", "6in", "7in"),
  color   = c("Black", "White", "Silver"),
  price   = c(99, 129, 159)
)

head(ebook_profiles)
CBC Profiles
============
storage     : Categorical (2 levels: 8GB, 16GB)
screen      : Categorical (3 levels: 5in, 6in, 7in)
color       : Categorical (3 levels: Black, White, Silver)
price       : Continuous (3 levels, range: 99.00-159.00)

Profiles: 6
First few rows:
  profileID storage screen color price
1         1     8GB    5in Black    99
2         2    16GB    5in Black    99
3         3     8GB    6in Black    99
4         4    16GB    6in Black    99
5         5     8GB    7in Black    99
6         6    16GB    7in Black    99

18.2.2 Generación del diseño del experimento

Code
# Step 2: generate a randomized design (3 alternatives per choice task, 8 tasks per respondent)
ebook_design_cbctools <- cbcTools::cbc_design(
  profiles = ebook_profiles,
  n_resp   = 200,
  n_alts   = 3,
  n_q      = 8
)

head(ebook_design_cbctools)
Design method: random
Encoding: standard
Structure: 200 respondents × 8 questions × 3 alternatives
Profile usage: 54/54 (100.0%)

💡 Use cbc_inspect() for a more detailed summary

First few rows of design:
  profileID respID qID altID obsID storage screen  color price
1        34      1   1     1     1    16GB    6in Silver   129
2        50      1   1     2     1    16GB    5in Silver   159
3        16      1   1     3     1    16GB    6in Silver    99
4        29      1   2     1     2     8GB    7in  White   129
5        42      1   2     2     2    16GB    7in  Black   159
6        11      1   2     3     2     8GB    7in  White    99

18.2.3 Evaluación de la calidad del diseño

Una vez generado el diseño, cbcTools permite inspeccionar su calidad estadística antes de recoger ningún dato real, comprobando el balance y el solapamiento de niveles entre alternativas:

Code
cbcTools::cbc_inspect(ebook_design_cbctools, c("balance", "overlap"))
DESIGN SUMMARY
=========================

ATTRIBUTE BALANCE
=================
Overall balance score: 0.989 (higher is better)

Individual attribute level counts:

storage:

 8GB 16GB 
2404 2396 
  Balance score: 0.998 (higher is better)

screen:

 5in  6in  7in 
1574 1642 1584 
  Balance score: 0.978 (higher is better)

color:

 Black  White Silver 
  1593   1591   1616 
  Balance score: 0.991 (higher is better)

price:

  99  129  159 
1602 1583 1615 
  Balance score: 0.990 (higher is better)

ATTRIBUTE OVERLAP
=================
Overall overlap score: 0.130 (lower is better)

Counts of attribute overlap:
(# of questions with N unique levels)

storage: Categorical variable
  Questions by # unique levels:
  1 (complete overlap):  22.0%  (352 / 1600 questions)
  2 (no overlap):        78.0%  (1248 / 1600 questions)
  Average unique levels per question: 1.78

screen: Categorical variable
  Questions by # unique levels:
  1 (complete overlap):   9.5%  (152 / 1600 questions)
  2 (partial overlap):   67.1%  (1074 / 1600 questions)
  3 (no overlap):        23.4%  (374 / 1600 questions)
  Average unique levels per question: 2.14

color: Categorical variable
  Questions by # unique levels:
  1 (complete overlap):  10.5%  (168 / 1600 questions)
  2 (partial overlap):   66.4%  (1062 / 1600 questions)
  3 (no overlap):        23.1%  (370 / 1600 questions)
  Average unique levels per question: 2.13

price: Continuous variable
  Questions by # unique levels:
  1 (complete overlap):   9.9%  (159 / 1600 questions)
  2 (partial overlap):   66.6%  (1066 / 1600 questions)
  3 (no overlap):        23.4%  (375 / 1600 questions)
  Average unique levels per question: 2.13

Un diseño bien equilibrado presenta cada nivel de cada atributo con una frecuencia similar a lo largo de todas las tareas de elección, y evita que el mismo nivel se repita en exceso dentro de una misma tarea (lo que dificulta distinguir su efecto del de otros atributos). cbcTools también permite simular elecciones a partir de unos coeficientes hipotéticos —antes de tener datos reales—, lo que resulta especialmente útil para realizar un análisis de potencia: estimar cuántos encuestados serían necesarios para detectar con suficiente precisión el efecto esperado de cada atributo (Helveston 2025).

TipCuándo usar cbcTools

cbcTools interviene en la fase de diseño, antes de la recogida de datos. Su uso es relevante cuando se está planificando un nuevo estudio CBC y se necesita decidir el número de tareas de elección, el número de alternativas por tarea, o evaluar la calidad estadística de un diseño antes de su lanzamiento. No participa en la estimación del modelo a partir de datos ya recogidos, que es la función de paquetes como mlogit (capítulo anterior) o logitr (siguiente sección de este capítulo).

18.3 Estimación con logitr: parametrización en espacio de disposición a pagar

El modelo logit condicional estimado en el capítulo anterior con mlogit se especifica en lo que se conoce como espacio de preferencias (preference space): se estima un coeficiente para cada atributo y, por separado, un coeficiente para el precio, y la disposición a pagar se calcula después de la estimación, dividiendo cada coeficiente de atributo entre el coeficiente de precio (con signo cambiado).

logitr permite, además de reproducir esta misma parametrización, estimar el modelo directamente en el espacio de disposición a pagar (WTP space): en lugar de estimar coeficientes de utilidad y calcular la disposición a pagar a posteriori, el modelo estima directamente los parámetros de disposición a pagar como parte del propio proceso de optimización (Helveston 2023). Esta reparametrización resulta especialmente relevante en modelos con heterogeneidad de preferencias entre individuos (modelos logit mixtos), donde el cálculo de la disposición a pagar a partir de coeficientes en espacio de preferencias puede producir estimaciones con una varianza excesiva o poco interpretables (Helveston 2023).

18.3.1 Preparación de los datos

Se simula un conjunto de datos CBC con la misma estructura del capítulo anterior, de forma autocontenida en este capítulo:

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_utilities <- c(
  storage16GB  = 0.9,
  screen6in    = 0.5, screen7in = 0.3,
  colorWhite   = 0.2, colorSilver = 0.4,
  price        = -0.025
)

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)
  )

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)
  )

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)

# logitr requires a choice-task identifier that is unique across the entire
# dataset (not just within each respondent), since task_id alone repeats
# across all 200 respondents.
ebook_data <- ebook_data |>
  dplyr::mutate(obs_id_chr = paste(resp_id, task_id, sep = "_"))
ebook_data$obs_id <- as.integer(factor(ebook_data$obs_id_chr))
ebook_data$obs_id_chr <- NULL

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

Como en el capítulo anterior, se convierten explícitamente a factor las variables categóricas, con el orden de niveles fijado, para garantizar que el modelo nombre los coeficientes con las etiquetas reales:

Code
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"))
  )

18.3.2 Estimación en espacio de preferencias

Se estima primero el modelo en espacio de preferencias, equivalente al estimado con mlogit en el capítulo anterior. Al igual que con mlogit, se fuerza explícitamente contr.treatment para garantizar que los coeficientes se nombren con las etiquetas reales de cada nivel, con independencia de la configuración de contrastes de la sesión:

Code
old_contrasts <- options("contrasts")
options(contrasts = c("contr.treatment", "contr.poly"))

cbc_model_logitr <- logitr::logitr(
  data    = ebook_data,
  outcome = "choice",
  obsID   = "obs_id",
  pars    = c("storage", "screen", "color", "price")
)

options(old_contrasts)

summary(cbc_model_logitr)
=================================================

Model estimated on: lu. jun. 29 16:23:31 2026 

Using logitr version: 1.1.3 

Call:
logitr::logitr(data = ebook_data, outcome = "choice", obsID = "obs_id", 
    pars = c("storage", "screen", "color", "price"))

Frequencies of alternatives:
      1       2       3 
0.35750 0.32125 0.32125 

Exit Status: 3, Optimization stopped because ftol_rel or ftol_abs was reached.
                                
Model Type:    Multinomial Logit
Model Space:          Preference
Model Run:                1 of 1
Iterations:                   12
Elapsed Time:        0h:0m:0.01s
Algorithm:        NLOPT_LD_LBFGS
Weights Used?:             FALSE
Robust?                    FALSE

Model Coefficients: 
              Estimate Std. Error  z-value  Pr(>|z|)    
storage16GB  1.0232913  0.0744856  13.7381 < 2.2e-16 ***
screen6in    0.5769625  0.0869520   6.6354 3.236e-11 ***
screen7in    0.3158375  0.0888749   3.5537 0.0003798 ***
colorWhite   0.3494203  0.0882649   3.9588 7.534e-05 ***
colorSilver  0.5308115  0.0861656   6.1604 7.258e-10 ***
price       -0.0320996  0.0015942 -20.1351 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
                                     
Log-Likelihood:         -1388.4167308
Null Log-Likelihood:    -1757.7796619
AIC:                     2788.8334616
BIC:                     2821.1000000
McFadden R2:                0.2101304
Adj McFadden R2:            0.2067170
Number of Observations:  1600.0000000

Los coeficientes estimados son sustancialmente equivalentes a los obtenidos con mlogit en el capítulo anterior.

18.3.3 Estimación en espacio de disposición a pagar

logitr permite reestimar directamente el mismo modelo en espacio de disposición a pagar, especificando qué variable representa el precio mediante el argumento scalePar:

Code
old_contrasts <- options("contrasts")
options(contrasts = c("contr.treatment", "contr.poly"))

cbc_model_wtp <- logitr::logitr(
  data     = ebook_data,
  outcome  = "choice",
  obsID    = "obs_id",
  pars     = c("storage", "screen", "color"),
  scalePar = "price"
)

options(old_contrasts)

summary(cbc_model_wtp)
=================================================

Model estimated on: lu. jun. 29 16:23:31 2026 

Using logitr version: 1.1.3 

Call:
logitr::logitr(data = ebook_data, outcome = "choice", obsID = "obs_id", 
    pars = c("storage", "screen", "color"), scalePar = "price")

Frequencies of alternatives:
      1       2       3 
0.35750 0.32125 0.32125 

Exit Status: 3, Optimization stopped because ftol_rel or ftol_abs was reached.
                                 
Model Type:     Multinomial Logit
Model Space:   Willingness-to-Pay
Model Run:                 1 of 1
Iterations:                    18
Elapsed Time:         0h:0m:0.03s
Algorithm:         NLOPT_LD_LBFGS
Weights Used?:              FALSE
Robust?                     FALSE

Model Coefficients: 
              Estimate Std. Error z-value  Pr(>|z|)    
scalePar     0.0320995  0.0015942 20.1351 < 2.2e-16 ***
storage16GB 31.8782342  2.4897648 12.8037 < 2.2e-16 ***
screen6in   17.9716510  2.7799264  6.4648 1.014e-10 ***
screen7in    9.8416054  2.7874173  3.5307 0.0004144 ***
colorWhite  10.8840306  2.7648876  3.9365 8.267e-05 ***
colorSilver 16.5356009  2.7148585  6.0908 1.124e-09 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
                                     
Log-Likelihood:         -1388.4167289
Null Log-Likelihood:    -1757.7796619
AIC:                     2788.8334577
BIC:                     2821.1000000
McFadden R2:                0.2101304
Adj McFadden R2:            0.2067170
Number of Observations:  1600.0000000

En esta parametrización, los coeficientes estimados son ya directamente la disposición a pagar, expresada en las mismas unidades que el precio (euros), sin necesidad del cálculo posterior que se hizo manualmente con los coeficientes de mlogit en el capítulo anterior.

Code
logitr::wtp(cbc_model_logitr, scalePar = "price")
              Estimate Std. Error z-value  Pr(>|z|)    
storage16GB 31.8786192  2.5031265 12.7355 < 2.2e-16 ***
screen6in   17.9741265  2.7868844  6.4495 1.122e-10 ***
screen7in    9.8392925  2.7947687  3.5206 0.0004306 ***
colorWhite  10.8854992  2.7839579  3.9101 9.227e-05 ***
colorSilver 16.5363839  2.7263373  6.0654 1.316e-09 ***
scalePar     0.0320996  0.0015937 20.1419 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

La función logitr::wtp() permite además calcular la disposición a pagar a partir de un modelo en espacio de preferencias —de forma análoga al cálculo manual realizado en el capítulo anterior con mlogit—, confirmando la coherencia entre ambas vías de estimación.

TipCuándo usar logitr

logitr es una alternativa de estimación, no de diseño. Resulta especialmente útil cuando: (1) se necesita una parametrización directa en espacio de disposición a pagar, evitando el cálculo posterior y sus problemas de varianza en modelos con heterogeneidad; (2) se va a estimar un modelo logit mixto con parámetros aleatorios entre individuos, donde logitr ofrece tiempos de estimación significativamente menores que mlogit; o (3) se necesita estimar un modelo ponderado, asignando distinto peso a distintas observaciones de la muestra.

18.4 Síntesis: tres paquetes, tres momentos del análisis CBC

Paquete Fase del análisis Función principal Autor de referencia
cbcTools Diseño del experimento Generar y evaluar el diseño antes de recoger datos Helveston
mlogit Estimación (espacio de preferencias) Estándar académico de referencia para el modelo logit condicional Croissant
logitr Estimación (preferencias o WTP) Estimación rápida, con parametrización directa en disposición a pagar Helveston

Los tres paquetes son complementarios, no excluyentes: un flujo de trabajo completo en un proyecto CBC real combinaría cbcTools en la fase de diseño del cuestionario, y mlogit o logitr en la fase de estimación del modelo, según las necesidades concretas del análisis —siendo logitr la opción preferible cuando se trabaja con modelos en espacio de disposición a pagar o con heterogeneidad de preferencias entre individuos.

18.4.1 Conclusiones

Este capítulo ha mostrado el ecosistema de herramientas que complementa al análisis CBC desarrollado en el capítulo anterior: cbcTools para diseñar y evaluar experimentos antes de recoger datos, y logitr para estimar modelos logit con parametrizaciones adicionales —en particular, la estimación directa en espacio de disposición a pagar—. El capítulo siguiente desarrolla una variante distinta de la misma familia de modelos: el MaxDiff, orientado a la priorización de listas de elementos no combinables en productos.