Skip to contents

Prior specifications define your assumptions about respondent preferences before collecting data. These priors serve two key purposes in cbcTools: optimizing experimental designs (for D-optimal methods) and simulating choices. This article shows how to create and work with prior specifications using cbc_priors().

What Are Priors?

Priors represent your beliefs about how attributes influence respondent choices before collecting data. They specify:

  • Direction of effects: Positive values increase utility, negative values decrease it.
  • Magnitude of effects: Larger absolute values indicate stronger preferences.
  • Heterogeneity in effects: Random parameters can be used to specify heterogeneity in preferences across the population.

Sources of Prior Information

  • Literature review: Published studies in similar contexts.
  • Expert judgment: Domain knowledge from researchers or practitioners.
  • Pilot studies: Small preliminary studies to estimate effects.
  • Previous studies: Your own past research in related areas.
  • Theoretical expectations: Economic theory or behavioral assumptions.

Basic Prior Specification

For the purposes of this article, we’ll keep working with the same set of profiles about apples:

library(cbcTools)

profiles <- cbc_profiles(
  price = c(1, 1.5, 2, 2.5, 3),
  type = c('Fuji', 'Gala', 'Honeycrisp'),
  freshness = c('Poor', 'Average', 'Excellent')
)

profiles
#> CBC Profiles
#> ============
#> price       : Continuous (5 levels, range: 1.00-3.00)
#> type        : Categorical (3 levels: Fuji, Gala, Honeycrisp)
#> freshness   : Categorical (3 levels: Poor, Average, Excellent)
#> 
#> Profiles: 45
#> First few rows:
#>   profileID price type freshness
#> 1         1   1.0 Fuji      Poor
#> 2         2   1.5 Fuji      Poor
#> 3         3   2.0 Fuji      Poor
#> 4         4   2.5 Fuji      Poor
#> 5         5   3.0 Fuji      Poor
#> 6         6   1.0 Gala      Poor
#> ... and 39 more rows

Fixed Parameters

Fixed parameters assume you know the exact coefficient values. Start with the simplest case:

# Basic fixed priors
priors_fixed <- cbc_priors(
  profiles = profiles,
  price = -0.25, # Negative = prefer lower prices
  type = c(0.5, 1.0), # Preferences relative to reference level
  freshness = c(0.6, 1.2) # Preferences relative to reference level
)

priors_fixed
#> CBC Prior Specifications:
#> 
#> price:
#>   Continuous variable
#>   Levels: 1, 1.5, 2, 2.5, 3 
#>   Fixed parameter
#>     Coefficient: -0.25
#> 
#> type:
#>   Categorical variable
#>   Levels: Fuji, Gala, Honeycrisp 
#>   Reference level: Fuji
#>   Fixed parameter
#>     Gala: 0.5
#>     Honeycrisp: 1
#> 
#> freshness:
#>   Categorical variable
#>   Levels: Poor, Average, Excellent 
#>   Reference level: Poor
#>   Fixed parameter
#>     Average: 0.6
#>     Excellent: 1.2

Understanding Categorical Variables

For categorical attributes, the reference level is set by the first level defined in cbc_profiles(), which in this case is "Fuji" for Type and "Poor" for Freshness. This would imply the following for the above set of priors:

Type:

  • Fuji: coefficient = 0 (Reference level)
  • Gala: coefficient = 0.5 (preferred over Fuji)
  • Honeycrisp: coefficient = 1.0 (most preferred)

Freshness:

  • Poor: coefficient = 0 (Reference level)
  • Average: coefficient = 0.6
  • Excellent: coefficient = 1.2

Using Named Specifications

You can specify the levels for categorical priors using names:

priors_named <- cbc_priors(
  profiles = profiles,
  price = -0.25,
  type = c("Gala" = 0.5, "Honeycrisp" = 1.0),
  freshness = c("Average" = 0.6, "Excellent" = 1.2)
)

This produces the same set of priors as the priors_fixed example above:

identical(priors_fixed$pars, priors_named$pars)
#> [1] TRUE

Random Parameters

Random parameters allow for preference heterogeneity across respondents. Use rand_spec() to define random parameters. In the example below, we use "n" to specify normal distributions for the price and freshness attributes. Note that for freshness the mean and sd are vectors since it is a categorical attribute with three levels (the first level is the reference level):

priors_random <- cbc_priors(
  profiles = profiles,
  price = rand_spec(
    dist = "n",
    mean = -0.25,
    sd = 0.1
  ),
  type = c(0.5, 1.0),
  freshness = rand_spec(
    dist = "n",
    mean = c(0.6, 1.2),
    sd = c(0.1, 0.1)
  )
)

priors_random
#> CBC Prior Specifications:
#> 
#> price:
#>   Continuous variable
#>   Levels: 1, 1.5, 2, 2.5, 3 
#>   Random - Normal distribution
#>     Mean: -0.25
#>     SD:   0.1
#> 
#> type:
#>   Categorical variable
#>   Levels: Fuji, Gala, Honeycrisp 
#>   Reference level: Fuji
#>   Fixed parameter
#>     Gala: 0.5
#>     Honeycrisp: 1
#> 
#> freshness:
#>   Categorical variable
#>   Levels: Poor, Average, Excellent 
#>   Reference level: Poor
#>   Random - Normal distribution
#>     Average:
#>       Mean: 0.6
#>       SD:   0.1
#>     Excellent:
#>       Mean: 1.2
#>       SD:   0.1
#> 
#> Correlation Matrix:
#>                    price freshnessAverage freshnessExcellent
#> price                  1                0                  0
#> freshnessAverage       0                1                  0
#> freshnessExcellent     0                0                  1

Three distributions are supported:

  • "n": normal
  • "ln": log-normal (forces positivity)
  • "cn": censored normal (forces positivity)

Parameter Correlations

Model correlations between random parameters can be included using cor_spec():

priors_correlated <- cbc_priors(
  profiles = profiles,
  price = rand_spec(
    dist = "n",
    mean = -0.1,
    sd = 0.05,
    correlations = list(
      cor_spec(
        with = "type",
        with_level = "Honeycrisp",
        value = 0.3
      )
    )
  ),
  type = rand_spec(
    dist = "n",
    mean = c("Gala" = 0.1, "Honeycrisp" = 0.2),
    sd = c("Gala" = 0.05, "Honeycrisp" = 0.1)
  ),
  freshness = c(0.1, 0.2)
)

# View the correlation matrix
priors_correlated$correlation
#>                price typeGala typeHoneycrisp
#> price            1.0        0            0.3
#> typeGala         0.0        1            0.0
#> typeHoneycrisp   0.3        0            1.0

Types of Correlations

General correlation between all levels of two attributes:

cor_spec(
  with = "type",
  value = -0.2
)

Correlation with a specific level of a categorical attribute:

cor_spec(
  with = "type",
  with_level = "Honeycrisp",
  value = 0.3
)

Correlation from a specific level to another specific level:

cor_spec(
  with = "freshness",
  level = "Gala",
  with_level = "Excellent",
  value = 0.4
)

Interaction Effects

You can include interaction terms in your priors using int_spec():

# Create priors with interaction effects
priors_interactions <- cbc_priors(
  profiles = profiles,
  price = -0.25,
  type = c("Fuji" = 0.5, "Honeycrisp" = 1.0),
  freshness = c("Average" = 0.6, "Excellent" = 1.2),
  interactions = list(
    # Price sensitivity varies by apple type
    int_spec(
      between = c("price", "type"),
      with_level = "Fuji",
      value = 0.1
    ),
    int_spec(
      between = c("price", "type"),
      with_level = "Honeycrisp",
      value = 0.2
    ),
    # Type preferences vary by freshness
    int_spec(
      between = c("type", "freshness"),
      level = "Honeycrisp",
      with_level = "Excellent",
      value = 0.3
    )
  )
)

priors_interactions
#> CBC Prior Specifications:
#> 
#> price:
#>   Continuous variable
#>   Levels: 1, 1.5, 2, 2.5, 3 
#>   Fixed parameter
#>     Coefficient: -0.25
#> 
#> type:
#>   Categorical variable
#>   Levels: Fuji, Gala, Honeycrisp 
#>   Reference level: Gala
#>   Fixed parameter
#>     Fuji: 0.5
#>     Honeycrisp: 1
#> 
#> freshness:
#>   Categorical variable
#>   Levels: Poor, Average, Excellent 
#>   Reference level: Poor
#>   Fixed parameter
#>     Average: 0.6
#>     Excellent: 1.2
#> 
#> Interactions:
#>   price × type[Fuji]: 0.1
#>   price × type[Honeycrisp]: 0.2
#>   type[Honeycrisp] × freshness[Excellent]: 0.3

Interaction Types

Continuous × Categorical: Must specify categorical level

int_spec(
  between = c("price", "type"),
  with_level = "Fuji",
  value = 0.05
)

Categorical × Categorical: Must specify both levels:

int_spec(
  between = c("type", "freshness"),
  level = "Gala",
  with_level = "Excellent",
  value = 0.1
)

Continuous × Continuous: No level specification needed:

int_spec(
  between = c("price", "weight"),
  value = 0.02
)

No-Choice Priors

For designs with no-choice options, specify the no-choice utility with the no_choice argument:

# Fixed no-choice prior
priors_nochoice_fixed <- cbc_priors(
  profiles = profiles,
  price = -0.25,
  type = c(0.5, 1.0),
  freshness = c(0.6, 1.2),
  no_choice = -0.5 # Negative values make no-choice less attractive
)

# Random no-choice prior
priors_nochoice_random <- cbc_priors(
  profiles = profiles,
  price = -0.25,
  type = c(0.5, 1.0),
  freshness = c(0.6, 1.2),
  no_choice = rand_spec(dist = "n", mean = -0.5, sd = 0.2)
)

priors_nochoice_fixed
#> CBC Prior Specifications:
#> 
#> price:
#>   Continuous variable
#>   Levels: 1, 1.5, 2, 2.5, 3 
#>   Fixed parameter
#>     Coefficient: -0.25
#> 
#> type:
#>   Categorical variable
#>   Levels: Fuji, Gala, Honeycrisp 
#>   Reference level: Fuji
#>   Fixed parameter
#>     Gala: 0.5
#>     Honeycrisp: 1
#> 
#> freshness:
#>   Categorical variable
#>   Levels: Poor, Average, Excellent 
#>   Reference level: Poor
#>   Fixed parameter
#>     Average: 0.6
#>     Excellent: 1.2
#> 
#> no_choice:
#>   Continuous variable
#>   Fixed parameter
#>     Coefficient: -0.5

Parameter Draws for Bayesian Analysis

When you specify random parameters, cbc_priors() automatically generates parameter draws for Bayesian D-error calculation. You can conrol both the draw type with the draw_type argument ("halton" or "sobol") and the number of draws with the n_draws argument, e.g.:

priors_bayesian <- cbc_priors(
  profiles = profiles,
  price = rand_spec(
    dist = "n",
    mean = -0.25,
    sd = 0.1
  ),
  type = rand_spec(
    dist = "n",
    mean = c(0.5, 1.0),
    sd = c(0.1, 0.2)
  ),
  freshness = c(0.6, 1.2),
  n_draws = 500, # Default = 100
  draw_type = "sobol" # Default = "halton"
)

# Inspect the parameter draws
price_draws <- priors_bayesian$par_draws[, 1]
cat("Parameter draws dimensions:", dim(priors_bayesian$par_draws), "\n")
#> Parameter draws dimensions: 500 5
cat("Mean of price draws:", mean(price_draws), "\n")
#> Mean of price draws: -0.2496651
cat("SD of price draws:", sd(price_draws), "\n")
#> SD of price draws: 0.09856398

# Plot distribution of one parameter
hist(
  price_draws,
  main = "Distribution of Price Parameter Draws",
  xlab = "Price Coefficient"
)

Common Pitfalls

Mismatched Scales

Ensure prior magnitudes match your attribute scales:

# Problem: Price in dollars, prior assumes price in cents
profiles_dollars <- cbc_profiles(price = c(1.00, 2.00, 3.00), ...)
priors_cents <- cbc_priors(profiles_dollars, price = -10, ...) # Too large!

# Solution: Match scales
priors_dollars <- cbc_priors(profiles_dollars, price = -0.10, ...) # Appropriate

Wrong Reference Levels

Remember the first level is always the reference:

# If you want "Excellent" as reference, reorder profiles
profiles_reordered <- cbc_profiles(
  price = c(1, 1.5, 2, 2.5, 3),
  type = c('Fuji', 'Gala', 'Honeycrisp'),
  freshness = c('Excellent', 'Average', 'Poor') # Excellent now reference
)

priors_reordered <- cbc_priors(
  profiles_reordered,
  price = -0.1,
  type = c(0.1, 0.2),
  freshness = c(-0.1, -0.2) # Negative = worse than excellent
)

Incompatible Restrictions

Ensure your priors are compatible with restricted profiles:

# If you've restricted certain profile combinations,
# make sure your priors don't assume those combinations are common
restricted_profiles <- cbc_restrict(
  profiles,
  type == "Fuji" & price > 2.5
)

# Prior should reflect that expensive Fuji combinations don't exist
priors_compatible <- cbc_priors(restricted_profiles, ...)

Using Priors in Practice

Once created, priors are used in:

  1. Design optimization: Pass to cbc_design() for D-optimal methods. See the Generating Designs article for more details.
  2. Choice simulation: Pass to cbc_choices() for realistic choice patterns. See the Simulating Choices article for more details.