# Getting Started

cbcTools provides a complete toolkit for designing and analyzing choice-based conjoint (CBC) experiments. This article walks through the entire workflow from defining attributes to creating and inspecting designs and determining sample size requirements, providing a quick start guide for new users and an overview of the package’s capabilities. Other articles cover more details on each step.

# The cbcTools Workflow

The package supports a step-by-step process for developing choice experiment designs:

Each step uses functions that begin with `cbc_` and builds on the previous step:

1. Generate Profiles → `cbc_profiles()` - Define attributes and levels
2. Specify Priors → `cbc_priors()` - Specify prior preference assumptions (optional)
3. Generate Designs → `cbc_design()` - Create choice question design
4. Inspect Designs → `cbc_inspect()` - Evaluate design quality
5. Simulate Choices → `cbc_choices()` - Generate realistic choice data
6. Assess Power → `cbc_power()` - Determine sample size requirements

Let’s walk through each step with a complete example. Imagine we’re designing a choice experiment to understand consumer preferences for apples. We want to study how price, type, and freshness influence purchase decisions.

## Step 1: Generate Profiles

Start by defining the attributes and levels for your experiment:

    profiles <- cbc_profiles(
      price = c(1.0, 1.5, 2.0, 2.5, 3.0), # Price per pound ($)
      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

This creates all possible combinations of attribute levels - our “universe” of possible products to include in choice questions.

See the Generating Profiles article for more details and options on defining profiles, such as including restrictions.

## Step 2: Specify Priors

Specify your assumptions about consumer preferences based on theory, literature, or pilot studies. These can be used for generating designs that incorporate these expected preferences as well as simulating choices for a given design.

    priors <- cbc_priors(
      profiles = profiles,
      price = -0.25, # Negative = people prefer lower prices
      type = c(0.5, 1), # Gala and Honeycrisp preferred over Fuji (reference)
      freshness = c(0.6, 1.2) # Average and Excellent preferred over Poor (reference)
    )

    priors
    #> 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 Reference Levels

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 (reference), Gala (+0.5), Honeycrisp (+1.0)
- Freshness: Poor (reference), Average (+0.6), Excellent (+1.2)

See the Specifying Priors article for more details and options on defining priors.

## Step 3: Generate Designs

Create the set of choice questions that respondents will see. Designs are created with standard encoding (categorical variables). You can convert to dummy or effects coding using `cbc_encode()`:

    design <- cbc_design(
      profiles = profiles,
      method = "stochastic", # D-optimal method
      n_alts = 3, # 2 alternatives per choice question
      n_q = 6, # 6 questions per respondent
      n_resp = 300, # 300 respondents
      priors = priors # Use our priors for optimization
    )

    design
    #> Design method: stochastic
    #> Encoding: standard
    #> Structure: 300 respondents × 6 questions × 3 alternatives
    #> Profile usage: 17/45 (37.8%)
    #> D-error: 0.862847
    #>
    #> 💡 Use cbc_inspect() for a more detailed summary
    #>
    #> First few rows of design:
    #>   profileID blockID respID qID altID obsID price       type freshness
    #> 1         6       1      1   1     1     1   1.0       Gala      Poor
    #> 2        29       1      1   1     2     1   2.5 Honeycrisp   Average
    #> 3        31       1      1   1     3     1   1.0       Fuji Excellent
    #> 4        35       1      1   2     1     2   3.0       Fuji Excellent
    #> 5        25       1      1   2     2     2   3.0       Gala   Average
    #> 6        26       1      1   2     3     2   1.0 Honeycrisp   Average
    #> ... and 5394 more rows

The design generated is sufficient for a full survey for `n_resp` respondents. cbcTools offers several design methods, each with their own trade-offs:

- `"random"`: Random profiles for each respondent.
- `"shortcut"`: Frequency-balanced, often results in minimal overlap within choice questions.
- `"minoverlap"`: Prioritizes minimizing attribute overlap within choice questions.
- `"balanced"`: Optimizes both frequency balance and pairwise attribute interactions.
- `"stochastic"`: Minimizes D-error by randomly swapping profiles.
- `"modfed"`: Minimizes D-error by swapping out all possible profiles (slower, more thorough).
- `"cea"`: Minimizes D-error by attribute-by-attribute swapping.

All design methods ensure:

1. No duplicate profiles within any choice set.
2. No duplicate choice sets within any respondent.
3. Dominance removal (if enabled) eliminates choice sets with dominant alternatives.

See the Generating Designs article for more details and options on generating experiment designs, such as including “no choice” options, using labeled designs, removing dominant options, and details about each design algorithm.

## Step 4: Inspect Design

Use the `cbc_inspect()` function to evaluate the quality and properties of your design. Key things to look for:

- D-error: Lower values indicate more efficient designs
- Balance: Higher scores indicate better attribute level balance
- Overlap: Lower scores indicate less attribute overlap within questions
- Profile usage: Higher percentages indicate better use of available profiles

See `cbc_inspect` for more details.

    cbc_inspect(design)
    #> DESIGN SUMMARY
    #> =========================
    #>
    #> STRUCTURE
    #> ================
    #> Method: stochastic
    #> Created: 2025-10-20 15:22:22
    #> Respondents: 300
    #> Questions per respondent: 6
    #> Alternatives per question: 3
    #> Total choice sets: 1800
    #> Profile usage: 17/45 (37.8%)
    #>
    #> SUMMARY METRICS
    #> =================
    #> D-error (with priors): 0.862847
    #> D-error (null model): 0.787209
    #> (Lower values indicate more efficient designs)
    #>
    #> Overall balance score: 0.771 (higher is better)
    #> Overall overlap score: 0.000 (lower is better)
    #>
    #> VARIABLE ENCODING
    #> =================
    #> Format: Standard (categorical) (type, freshness)
    #> 💡 Use cbc_encode() to convert to dummy or effects coding
    #>
    #> ATTRIBUTE BALANCE
    #> =================
    #> Overall balance score: 0.771 (higher is better)
    #>
    #> Individual attribute level counts:
    #>
    #> price:
    #>
    #>    1  1.5    2  2.5    3
    #> 2100  300  300  900 1800
    #>   Balance score: 0.563 (higher is better)
    #>
    #> type:
    #>
    #>       Fuji       Gala Honeycrisp
    #>       1800       1800       1800
    #>   Balance score: 1.000 (higher is better)
    #>
    #> freshness:
    #>
    #>      Poor   Average Excellent
    #>      2400      1800      1200
    #>   Balance score: 0.750 (higher is better)
    #>
    #> ATTRIBUTE OVERLAP
    #> =================
    #> Overall overlap score: 0.000 (lower is better)
    #>
    #> Counts of attribute overlap:
    #> (# of questions with N unique levels)
    #>
    #> price: Continuous variable
    #>   Questions by # unique levels:
    #>   1 (complete overlap):   0.0%  (0 / 1800 questions)
    #>   2 (partial overlap):   83.3%  (1500 / 1800 questions)
    #>   3 (partial overlap):   16.7%  (300 / 1800 questions)
    #>   4 (partial overlap):    0.0%  (0 / 1800 questions)
    #>   5 (no overlap):         0.0%  (0 / 1800 questions)
    #>   Average unique levels per question: 2.17
    #>
    #> type: Categorical variable
    #>   Questions by # unique levels:
    #>   1 (complete overlap):   0.0%  (0 / 1800 questions)
    #>   2 (partial overlap):    0.0%  (0 / 1800 questions)
    #>   3 (no overlap):       100.0%  (1800 / 1800 questions)
    #>   Average unique levels per question: 3.00
    #>
    #> freshness: Categorical variable
    #>   Questions by # unique levels:
    #>   1 (complete overlap):   0.0%  (0 / 1800 questions)
    #>   2 (partial overlap):   66.7%  (1200 / 1800 questions)
    #>   3 (no overlap):        33.3%  (600 / 1800 questions)
    #>   Average unique levels per question: 2.33

## Step 5: Simulate Choices

Generate realistic choice data to test your design with the `cbc_choices()` function. By default, random choices are made, but if you provide priors with the `priors` argument, choices will be made according to the utility model defined by your priors:

    # Simulate choices using our priors
    choices <- cbc_choices(design, priors = priors)

    choices
    #> CBC Choice Data
    #> ===============
    #> Encoding: standard
    #> Observations: 1800 choice tasks
    #> Alternatives per task: 3
    #> Respondents: 300
    #> Questions per respondent: 6
    #> Total choices made: 1800
    #>
    #> Simulation method: utility_based
    #> Original design D-error: 0.862847
    #> Priors: Used for utility-based simulation
    #> Simulated at: 2025-10-20 15:22:22
    #>
    #> Choice rates by alternative:
    #>   Alt 1: 33.7% (607 choices)
    #>   Alt 2: 34.9% (628 choices)
    #>   Alt 3: 31.4% (565 choices)
    #>
    #> First few rows:
    #>   profileID blockID respID qID altID obsID price       type freshness choice
    #> 1         6       1      1   1     1     1   1.0       Gala      Poor      0
    #> 2        29       1      1   1     2     1   2.5 Honeycrisp   Average      1
    #> 3        31       1      1   1     3     1   1.0       Fuji Excellent      0
    #> 4        35       1      1   2     1     2   3.0       Fuji Excellent      0
    #> 5        25       1      1   2     2     2   3.0       Gala   Average      0
    #> 6        26       1      1   2     3     2   1.0 Honeycrisp   Average      1
    #> ... and 5394 more rows

Taking a look at some quick summaries, you can see that the simulated choice patterns align with our priors - lower prices, preferred apple types, and better freshness should be chosen more often (not always for all levels though as there is some level of randomness involved):

    choices_cat <- choices

    # Filter for the chosen rows only
    choices_cat <- choices_cat[which(choices_cat$choice == 1), ]

    # Counts of choices made for each attribute level
    table(choices_cat$price)
    #>
    #>   1 1.5   2 2.5   3
    #> 691 172  36 449 452
    table(choices_cat$type)
    #>
    #>       Fuji       Gala Honeycrisp
    #>        380        528        892
    table(choices_cat$freshness)
    #>
    #>      Poor   Average Excellent
    #>       551       680       569

See the Simulating Choices article for more details and options on inspeciting and comparing different experiment designs.

## Step 6: Assess Power

Determine if your sample size provides adequate statistical power. The `cbc_power()` function auto-determines the attributes in your design and estimates multiple logit models using incrementally increasing sample sizes to assess power:

    power <- cbc_power(choices)
    power
    #> CBC Power Analysis Results
    #> ==========================
    #>
    #> Sample sizes tested: 30 to 300 (10 breaks)
    #> Significance level: 0.050
    #> Parameters: price, typeGala, typeHoneycrisp, freshnessAverage, freshnessExcellent
    #>
    #> Power summary (probability of detecting true effect):
    #>
    #> n = 30:
    #>   price       : Power = 0.931, SE = 0.0979
    #>   typeGala    : Power = 0.887, SE = 0.2286
    #>   typeHoneycrisp: Power = 0.996, SE = 0.2168
    #>   freshnessAverage: Power = 0.051, SE = 0.2193
    #>   freshnessExcellent: Power = 0.995, SE = 0.2407
    #>
    #> n = 90:
    #>   price       : Power = 0.992, SE = 0.0548
    #>   typeGala    : Power = 0.952, SE = 0.1301
    #>   typeHoneycrisp: Power = 1.000, SE = 0.1226
    #>   freshnessAverage: Power = 0.855, SE = 0.1213
    #>   freshnessExcellent: Power = 1.000, SE = 0.1379
    #>
    #> n = 180:
    #>   price       : Power = 1.000, SE = 0.0389
    #>   typeGala    : Power = 1.000, SE = 0.0948
    #>   typeHoneycrisp: Power = 1.000, SE = 0.0895
    #>   freshnessAverage: Power = 1.000, SE = 0.0853
    #>   freshnessExcellent: Power = 1.000, SE = 0.1001
    #>
    #> n = 240:
    #>   price       : Power = 1.000, SE = 0.0338
    #>   typeGala    : Power = 1.000, SE = 0.0825
    #>   typeHoneycrisp: Power = 1.000, SE = 0.0776
    #>   freshnessAverage: Power = 1.000, SE = 0.0744
    #>   freshnessExcellent: Power = 1.000, SE = 0.0871
    #>
    #> n = 300:
    #>   price       : Power = 1.000, SE = 0.0303
    #>   typeGala    : Power = 1.000, SE = 0.0748
    #>   typeHoneycrisp: Power = 1.000, SE = 0.0703
    #>   freshnessAverage: Power = 1.000, SE = 0.0668
    #>   freshnessExcellent: Power = 1.000, SE = 0.0789
    #>
    #> Use plot() to visualize power curves.
    #> Use summary() for detailed power analysis.

You can easily visualize the results as well using the `plot()` function:

    plot(power, type = "power", power_threshold = 0.9)

Finally, using the `summary()` function you can determine the exact size required to identify each attribute:

    summary(power, power_threshold = 0.9)
    #> CBC Power Analysis Summary
    #> ===========================
    #>
    #> Sample size requirements for 90% power:
    #>
    #> price          : n >= 30 (achieves 93.1% power, SE = 0.0979)
    #> typeGala       : n >= 60 (achieves 94.6% power, SE = 0.1590)
    #> typeHoneycrisp : n >= 30 (achieves 99.6% power, SE = 0.2168)
    #> freshnessAverage: n >= 120 (achieves 97.4% power, SE = 0.1052)
    #> freshnessExcellent: n >= 30 (achieves 99.5% power, SE = 0.2407)

See the Power article for more details and options on conducting power analyses to better understand your experiment designs.