conjointmatchups provides the data grammar of forced-choice (paired) conjoint experiments: lossless reshaping between the two natural representations of the data, and extraction of matchups, the analysis-ready pairwise sub-experiments that let you ask “when a candidate with these features faced a candidate with those features, which won?”
It is intentionally small. It does not estimate AMCEs or marginal means. It produces the datasets that estimators consume.
The problem it solves
Every forced-choice conjoint task is two things at once:
- Profile-long form (one row per candidate profile): natural for describing and randomizing attributes.
-
Task-wide form (one row per choice task, with per-profile columns like
party_1,party_2): natural for modeling the binary choice.
Analysts hand-roll the pivot between these constantly, and get it wrong: they drop the pairing, misalign the outcome, or silently reorder profiles. conjointmatchups makes that pivot canonical and lossless (as_tasks(), as_profiles()), then adds the move that single-experiment packages skip: restricting to the tasks that realize a controlled contrast between the two profiles and renaming the outcome to a clean binary (get_matchups()). What comes out is ready for lm, glm, estimatr::lm_robust(), or a random-effects meta-analysis across many studies.
The one thing to get right: your input format
conjointmatchups expects profile-long data: one row per candidate profile per choice task, two profiles per task. Each row needs columns that key the profile (a respondent id, a task id, a profile index), a binary chosen indicator (exactly one profile chosen per task), and one column per randomized attribute. The names are yours to choose; every function takes the key column names as arguments.
The shipped kc_yougov dataset (the YouGov sample from Kirkland and Coppock 2018) is in exactly this shape:
library(conjointmatchups)
head(kc_yougov)
#> respondent task profile chosen party gender race age occupation experience resp_party
#> 1 1 1 0 NA man Hispanic older professional prior experience Democrat
#> 1 1 2 1 NA man Black older teacher prior experience Democrat
#> 1 2 1 1 Independent man Black younger business prior experience Democrat
#> 1 2 2 0 Independent woman Hispanic older business prior experience Democrat| You need | In kc_yougov
|
|---|---|
| respondent id | respondent |
| task id (unique within respondent) | task |
| profile index (1/2) | profile |
| binary chosen indicator | chosen |
| one column per attribute |
party, gender, … |
Attributes may be NA when a feature was not shown in a condition (here party appears only in the partisan arm). If your raw export is shaped differently, reshape it into this table first. If it is already task-wide (party_1, party_2, …), as_profiles() gets you here, or hand it straight to get_matchups(). The Get started vignette walks through the whole path.
Usage
library(conjointmatchups)
# profile-long -> task-wide
tasks <- as_tasks(profiles, task_keys = c("study_id", "resp_id", "task_id"),
profile = "profile", outcome = "chosen")
# extract a Republican-vs-Democrat matchup, outcome renamed to A_wins
m <- get_matchups(tasks,
A = list(party = "Republican"),
B = list(party = "Democrat"),
outcome = "chosen")
# estimate the AFCP, respondent-clustered, per study
afcp(m, by = "study_id", clusters = "resp_id", weights = "resp_weights")get_matchups() refuses an ill-defined contrast (one where a single profile could satisfy both sides), because such a “comparison” measures display position, not any real difference. valid_contrast() and contrast_message() expose that check for building interfaces.
Where it sits in the conjoint R ecosystem
The existing conjoint packages all analyze a single experiment and jump straight to an estimand. None of them owns the reshape-and-select layer beneath that step, which is exactly what conjointmatchups provides.
| Package | What it does | Estimand | Layer |
|---|---|---|---|
| cjoint (Hainmueller, Hopkins, Yamamoto 2014) | The original AMCE estimator | AMCE | estimation |
| cregg (Leeper) | Tidy AMCEs and marginal means with ggplot visualization; auto-detects two-way constraints | AMCE, MM | estimation + viz |
| projoint (Horiuchi, Markovich, Yamamoto) | AMCE/MM with measurement-error / reliability correction | AMCE, MM | estimation |
| factorEx (de la Cuesta, Egami, Imai) | Population AMCE and external validity via the profile distribution | pAMCE | estimation |
| afcp (Abramson, Kocak, Magazinnik, Strezhnev) | The Average Feature Choice Probability estimator and preference-cycle tests | AFCP | estimation |
| conjointdatachecks (Knotz) | Carryover and randomization QA checks | — | diagnostics |
| conjoint | Traditional (marketing) conjoint via part-worth utilities | part-worths | estimation |
| marginaleffects | General marginal-effects machinery; conjoint as a worked example | any | estimation |
| conjointmatchups | Reshape profile ⇄ task; extract matchups with a clean outcome | (none) | data grammar |
The relationship to the AFCP estimand is deliberate. conjointmatchups produces the matchup datasets that the AFCP is defined on: for each pair of feature levels, restrict to the tasks pitting them against each other and compute the probability one wins (Abramson, Kocak, Magazinnik, and Strezhnev). The included afcp() is a thin convenience over estimatr::lm_robust() that closes that loop; for the estimator’s inferential machinery and preference-cycle tests, use the afcp package directly, and for AMCE/MM analyses of a single experiment reach for cregg or cjoint.
Design notes
- Column names are yours. Every function takes the key column names (task keys, profile index, outcome stem) as arguments; nothing is hard-coded to a particular coding scheme.
-
Dependency-light core. The reshape and matchup functions depend only on
dplyr,tidyr, andtibble, so an application (e.g. a Shiny explorer) can depend on the data grammar without pulling in an estimation stack.estimatris required only byafcp().
References
Abramson, S. F., Kocak, K., Magazinnik, A., and Strezhnev, A. Detecting preference cycles in forced-choice conjoint experiments.
Hainmueller, J., Hopkins, D. J., and Yamamoto, T. (2014). Causal inference in conjoint analysis: Understanding multidimensional choices via stated preference experiments. Political Analysis, 22(1), 1–30.
Leeper, T. J., Hobolt, S. B., and Tilley, J. (2020). Measuring subgroup preferences in conjoint experiments. Political Analysis, 28(2), 207–221.