When and Why to Use
Use this to run a CBC study where each respondent picks one option from a small set of competing product concepts across a sequence of tasks. Ideal for:
- Measuring how attribute levels (brand, price, feature, packaging) drive choice
- Estimating individual-level utility scores via Hierarchical Bayes
- Comparing attribute importance and, with purchase follow-ups, marginal purchase lift
For background on the method, see Choice-Based Conjoint. For the model that produces utility scores and simulated share, see Utility and simulated share methodology.
Other Names and Formats
Choice-based conjoint (CBC) is also known as a discrete choice experiment (DCE) or discrete choice modeling (DCM), also spelled discrete choice modelling. Respondents choose among competing concepts described by attributes and levels. These terms refer to the choice-based task documented here.
How Task Sets Work
task_sets is a mapping of task-set id to an ordered list of tasks, where each task is a list of concept ids. Each respondent is assigned one task set using least-fill balancing, which keeps per-task-set exposure roughly even across the fielding. Each task in the assigned set is then presented as a structured comparison with one concept choice.
The simplest way to generate balanced concepts and task sets is the Conjoint Analysis Design Tool, which produces a code block you can paste straight into the survey alongside your screener.
Dict-Backed vs Media-Backed Concepts
Concepts can be defined in two shapes:
- Dict-backed concepts — Python dicts whose
concept_id_tagkey (defaultid) identifies the concept; attributes are displayed as ordered comparison rows.required_tagssets the displayed row order and identifies the fields required on every concept and retained for reporting. - Media-backed concepts —
MediaItemobjects rendered using the same labels, sizing, and media tags as other media-bearing questions. Useimage_label_field,show_image_label, andimage_sizeto configure rendering.
Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
question | str | yes | - | The question to ask for each conjoint task |
concepts | list[dict] or list[MediaItem] | yes | - | Concept dictionaries or media items to use in tasks |
task_sets | dict[str, list[list[str]]] | yes | - | Mapping of task-set id to tasks, where each task contains concept ids |
concept_id_tag | str | no | 'id' | Tag containing the concept id referenced by task_sets |
required_tags | list[str] or None | no | None | Concept tags to require and report with selected dict-backed concepts |
concept_labels | list[str] or None | no | None | Reusable headings for displayed concept positions; dictionary-backed concepts only |
dont_know_option | str | no | '' | Optional uncertainty choice, separate from explicit none |
randomize | bool | no | False | Randomize option order within each task |
image_label_field | str or None | no | None | Media field to use as the display label for media concepts |
show_image_label | bool | no | True | Whether to show media labels |
image_size | tuple[int, int] or None | no | None | Bounding box size for media options |
number_seconds | int | no | 0 | Seconds to wait before allowing the respondent to continue |
tags | dict[str, Any] or None | no | None | Tags attached to each conjoint task question |
id | str or None | no | None | Optional stable identifier for this question |
none_option | str | no | '' | Completed no-purchase choice beneath the concepts; cannot be combined with anchor_question |
anchor_question | str or None | no | None | Binary or rating purchase follow-up after each concept choice; supports {selected.field} piping |
anchor_labels | dict or None | no | None | Binary labels keyed by yes and no, or integer-keyed labels for a rating scale |
Respondent comparison
On a wide screen, concepts appear side by side with matching attribute rows. The None of these choice sits beneath the concepts when none_option is configured.

On mobile, the concepts stack vertically while retaining each attribute label and the separate None of these choice.

Reusable concept labels
Set concept_labels=["Product A", "Product B"] to label the displayed positions in each standard dictionary-backed conjoint task. Supply at least as many labels as there are concepts in the widest task. The separate None and Don't Know choices do not need concept labels.
Labels are assigned after randomization and reused for each task. Product A means the first displayed position, not one fixed product: the same underlying concept can be Product A in one task and Product B in another. Answers and reporting tags retain the canonical concept IDs and attributes.
The headings are shown with the side-by-side desktop comparison and stacked mobile concepts. This setting does not support media-backed concepts; use their existing media-label options. Omit concept_labels to keep the existing display.
None, Don't Know, and Purchase Follow-ups
Choose the behavior that matches your study:
- Set
none_option="None of these"for an explicit no-purchase alternative beneath the concepts. - Set
anchor_questionto ask whether the respondent would buy the concept they just selected. The platform asks and saves a separate yes/no or rating follow-up after every concept choice, according toanchor_labels. - Use
dont_know_option="Don't know"only for uncertainty. It is separate from a completed no-purchase choice and skips the purchase follow-up.
Explicit none and purchase follow-ups are mutually exclusive. For a purchase follow-up, omit none_option and pass these arguments in your s.conjoint_question(...) call:
anchor_question="Would you buy {selected.brand} for {selected.price}?",
anchor_labels={"yes": "Yes, I would buy it", "no": "No, I would not"},
For a purchase-likelihood rating instead, use integer keys:
anchor_question="How likely are you to buy {selected.brand}?",
anchor_labels={
1: "Definitely would not buy",
2: "Probably would not buy",
3: "Might or might not buy",
4: "Probably would buy",
5: "Definitely would buy",
},
Rating follow-ups use buttons on a scale of two to eleven points. Labels must be distinct and nonblank; do not mix numeric keys with yes/no keys. Raw ratings are retained. Choose the ratings that count as a purchase separately for each anchored report calculation; a survey rating is not automatically a yes/no answer.
{selected.price} and other {selected.field} placeholders insert values from the selected concept dictionary or media tags. Every concept must contain the referenced field. Bare {selected} inserts the concept ID. Do not use a Python f-string: the platform substitutes these values when the respondent chooses a concept.
The answer applies to that choice in that task, even if the same concept appears again. Answering No preserves the forced concept choice and records a negative purchase follow-up; it does not turn the response into Don't know.
The follow-up can also use a simple prompt without placeholders, as in this example:

Example Code
Dict-backed concepts:
from survey import Survey
s = Survey(**globals())
concepts = [
{"id": "A", "brand": "Apple", "color": "Black", "network": "5G", "price": "$800"},
{"id": "B", "brand": "Apple", "color": "Blue", "network": "5G", "price": "$1000"},
{"id": "C", "brand": "Samsung", "color": "Black", "network": "4G", "price": "$600"},
{"id": "D", "brand": "Samsung", "color": "Silver", "network": "5G", "price": "$800"},
]
task_sets = {
"TS001": [["A", "C"], ["B", "D"], ["A", "D"], ["B", "C"]],
"TS002": [["A", "B"], ["C", "D"], ["A", "C"], ["B", "D"]],
}
s.conjoint_question(
question="Which product would you choose?",
concepts=concepts,
task_sets=task_sets,
required_tags=["brand", "color", "network", "price"],
concept_labels=["Product A", "Product B"],
none_option="None of these",
randomize=True,
tags={"study": "smartphone_cbc"},
)
s.complete()
Notes
- Each task response includes the selected concept and its reporting attributes.
- If
dont_know_optionis selected, it is treated as a fixed non-concept option (no concept tags are attached). - The Conjoint Analysis Design Tool produces both the
conceptsandtask_setsstructures; paste its output directly into the survey instead of writing them by hand. - See Choice-Based Conjoint for end-to-end study design and Utility and simulated share methodology for the reporting outputs.
Understand why respondents chose an option
Ask respondents why they chose a concept with a text question or an AI interview. This example includes the chosen brand, milk type, and price in each question, skips None and Don't know answers, and allows up to two AI follow-ups:
concepts = [
{"id": "smily", "brand": "Smily Cows", "fat": "Whole milk", "price": "£1.80/litre"},
{"id": "meadow", "brand": "Meadow Dairy", "fat": "Semi-skimmed", "price": "£1.50/litre"},
{"id": "oak", "brand": "Oak Farm", "fat": "Whole milk", "price": "£1.60/litre"},
]
concepts_by_id = {concept["id"]: concept for concept in concepts}
choices = s.conjoint_question(
"Which milk would you choose?",
concepts=concepts,
task_sets={"milk": [["smily", "meadow"], ["smily", "oak"]]},
required_tags=["brand", "fat", "price"],
none_option="None of these",
dont_know_option="Don't know",
id="milk-choice",
)
for task_index, (task_id, selected) in enumerate(choices.items(), start=1):
if str(selected) not in concepts_by_id:
continue
description = f"{selected.brand}, {selected.fat}, at {selected.price}"
s.qualitative_text_question(
f"What made you choose {description} over the other option in that comparison?",
instructions=(
"Explore the reasons for this choice and the trade-offs involved. "
"Do not assume that brand, fat content, or price determined the answer."
),
max_follow_ups=2,
id=f"milk-choice-why-{task_index}",
tags={"source_task": task_id, "selected_concept": str(selected)},
)
The questions appear after all comparisons in the assigned task set, in task order. You can ask about fewer selections to keep the survey short. required_tags makes the selected attributes available for the follow-up question.
For a single open end, replace the qualitative_text_question call with:
s.text_question(
f"What made you choose {description} over the other option in that comparison?",
id=f"milk-choice-why-{task_index}",
tags={"source_task": task_id, "selected_concept": str(selected)},
)
Use a separate id for each interview. The tags link its answers to the original comparison and selected concept. If you want the AI to ask about competing concepts, include their details in the question or instructions.
For binary or rating purchase follow-ups, use anchor_question as described above. See Running qualitative text interviews for interview design and reporting.
Reporting conjoint results
The automatic Conjoint Analytics report starts with Part Worth Scores, an uncalibrated measure of relative preference for the tested attribute levels. Attribute Importance (%) summarizes each attribute's share of the total utility range. Neither requires an anchor, and neither is an absolute purchase-probability measure.
When the study includes None of these choices or binary/rating purchase follow-ups, the available calculations also include Anchored Part Worth Scores and Anchored Attribute Importance (%). They use the calibrated model; keep them distinct from the uncalibrated versions when comparing or describing results.
Purchase Lift Analytics uses Marginal Purchase Lift and requires binary or rating purchase-follow-up data. For rating follow-ups, choose an inclusive At least or At most purchase cutoff independently for each anchored calculation in the report editor. Cutoffs are retained when the report is saved and reopened. An explicit-none choice alone does not supply it. Marginal Purchase Lift describes a modeled change in purchase probability for an attribute level; it is separate from the Lift report that compares a target group with matched controls.
To change calculations, open the report's Edit dialog. Select the conjoint analytical question in Scope → Questions, then use Analysis → Questions → Calculation. Select multiple calculations when you need to compare them, and choose Cut by questions for demographic comparisons. The editor offers the calculations supported by the question and its data.
See Question aggregation types for the full calculation catalog. Attribute importance depends on the attribute levels and ranges tested, so comparisons between different designs need care.
