Skip to contents

🎯 Estimate Corrected MMs or AMCEs

In conjoint analysis, default MMs and AMCEs can be biased due to measurement error from intra-respondent variability.

projoint corrects for this bias automatically.

The following instructions apply to choice-level data. What if you have profile-level data? Our FAQ Page has instructions to estimate and visualize profile-level QOIs.


📦 Prepare Example Data

outcomes <- paste0("choice", 1:8)
outcomes <- c(outcomes, "choice1_repeated_flipped")
out1 <- reshape_projoint(exampleData1, outcomes)

🛠️ Why Use IDs (e.g., att1, level1)?

Before estimating quantities, it’s important to understand how attribute and level IDs work inside projoint.

We recommend working with attribute IDs rather than actual text labels because:

  • Safer against special characters, languages, or typos
  • Allows multiple attributes to have identical labels (e.g., “High” for both “Teaching Quality” and “Research Quality”)

Check attribute-level mappings:

out1$labels
## # A tibble: 24 × 4
##    attribute                level                        attribute_id level_id  
##    <chr>                    <chr>                        <chr>        <chr>     
##  1 Housing Cost             15% of pre-tax income        att1         att1:leve…
##  2 Housing Cost             30% of pre-tax income        att1         att1:leve…
##  3 Housing Cost             40% of pre-tax income        att1         att1:leve…
##  4 Presidential Vote (2020) 30% Democrat, 70% Republican att2         att2:leve…
##  5 Presidential Vote (2020) 50% Democrat, 50% Republican att2         att2:leve…
##  6 Presidential Vote (2020) 70% Democrat, 30% Republican att2         att2:leve…
##  7 Racial Composition       50% White, 50% Nonwhite      att3         att3:leve…
##  8 Racial Composition       75% White, 25% Nonwhite      att3         att3:leve…
##  9 Racial Composition       90% White, 10% Nonwhite      att3         att3:leve…
## 10 Racial Composition       96% White, 4% Nonwhite       att3         att3:leve…
## # ℹ 14 more rows

You can also save these labels for easier editing:

save_labels(out1, "labels.csv")

📈 Estimate Marginal Means (MMs)

Choice-Level MMs (Specific Level)
qoi_2 <- set_qoi(
  .structure = "choice_level",
  .att_choose = "att1",
  .lev_choose = "level3",
  .att_notchoose = "att1",
  .lev_notchoose = "level1"
)

mm2 <- projoint(out1, .qoi = qoi_2, .ignore_position = FALSE)
print(mm2)
## 
## Projoint results object
## -------------------------
## Estimand:  mm 
## Structure:  choice_level 
## Standard error method:  analytical 
## IRR:  Estimated 
## Tau:  0.172 
## Number of estimates:  2
summary(mm2)
## 
## Summary of Projoint Estimates
## ------------------------------
## Estimand:  mm 
## Structure:  choice_level 
## Standard error method:  analytical 
## IRR:  Estimated 
## Tau:  0.172
## # A tibble: 2 × 7
##   estimand       estimate     se conf.low conf.high att_level_choose
##   <chr>             <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 mm_uncorrected    0.402 0.0258    0.352     0.453 att1:level3     
## 2 mm_corrected      0.351 0.0408    0.271     0.431 att1:level3     
## # ℹ 1 more variable: att_level_notchoose <chr>

📉 Estimate AMCEs

Choice-Level AMCEs (Specific Level)
qoi_4 <- set_qoi(
  .structure = "choice_level",
  .estimand = "amce",
  .att_choose = "att1",
  .lev_choose = "level3",
  .att_notchoose = "att1",
  .lev_notchoose = "level1",
  .att_choose_b = "att1",
  .lev_choose_b = "level2",
  .att_notchoose_b = "att1",
  .lev_notchoose_b = "level1"
)

amce2 <- projoint(out1, .qoi = qoi_4, .ignore_position = TRUE)
print(amce2)
## 
## Projoint results object
## -------------------------
## Estimand:  amce 
## Structure:  choice_level 
## Standard error method:  analytical 
## IRR:  Estimated 
## Tau:  0.172 
## Number of estimates:  2
summary(amce2)
## 
## Summary of Projoint Estimates
## ------------------------------
## Estimand:  amce 
## Structure:  choice_level 
## Standard error method:  analytical 
## IRR:  Estimated 
## Tau:  0.172
## # A tibble: 2 × 9
##   estimand         estimate     se conf.low conf.high att_level_choose
##   <chr>               <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 amce_uncorrected  -0.0484 0.0270   -0.101   0.00448 att1:level3     
## 2 amce_corrected    -0.0739 0.0414   -0.155   0.00727 att1:level3     
## # ℹ 3 more variables: att_level_notchoose <chr>,
## #   att_level_choose_baseline <chr>, att_level_notchoose_baseline <chr>

🔎 Predict Intra-Respondent Reliability (IRR)

If your design does not include a repeated task, you can predict IRR using predict_tau(), based on observed respondent behavior.

Predict IRR Using predict_tau()
data(out1_arranged)
predicted_irr <- predict_tau(out1_arranged)

print(predicted_irr)
## Tau estimated using the extrapolation method: 0.743
summary(predicted_irr)
## # A tibble: 8 × 2
##       x predicted
##   <int>     <dbl>
## 1     0     0.743
## 2     1     0.709
## 3     2     0.675
## 4     3     0.640
## 5     4     0.606
## 6     5     0.572
## 7     6     0.537
## 8     7     0.503
plot(predicted_irr)


🎨 Visualize MMs or AMCEs

The projoint package provides ready-to-publish plotting tools for conjoint analysis results.

Note: The current version of projoint supports plotting choice-level MMs only.
Support for choice-level AMCEs will be available in future updates!


⚖️ Choice-Level Analysis

Estimate
  • Specify your quantity of interest:
qoi_mm <- set_qoi(
  .structure = "choice_level", # default
  .att_choose = "att1", 
  .lev_choose = "level1", 
  .att_notchoose = "att1", 
  .lev_notchoose = "level3"
)
  • Estimate
choice_mm <- projoint(
  .data = out1_arranged, 
  .qoi = qoi_mm, 
  .ignore_position = TRUE
)
Visualize (Levels)
plot(choice_mm)
Visualize (Differences)
plot(choice_mm, .type = "pointrange")

🌟 Explore and Compare Further

Choice-level analysis opens the door to many new research questions that traditional profile-level analysis often overlooks. This article demonstrates how to estimate deeper quantities and compare subgroups effectively.


📦 Setup

We use the already wrangled and cleaned data out1_arranged.

out1_arranged$labels
## # A tibble: 24 × 4
##    attribute                     level                   attribute_id level_id  
##    <chr>                         <chr>                   <chr>        <chr>     
##  1 Housing Cost (Pre-tax Income) 15%                     att1         att1:leve…
##  2 Housing Cost (Pre-tax Income) 30%                     att1         att1:leve…
##  3 Housing Cost (Pre-tax Income) 40%                     att1         att1:leve…
##  4 Presidential Vote (2020)      50% Dem, 50% Rep        att2         att2:leve…
##  5 Presidential Vote (2020)      30% Dem, 70% Rep        att2         att2:leve…
##  6 Presidential Vote (2020)      70% Dem, 30% Rep        att2         att2:leve…
##  7 Racial Composition            50% White, 50% Nonwhite att3         att3:leve…
##  8 Racial Composition            75% White, 25% Nonwhite att3         att3:leve…
##  9 Racial Composition            90% White, 10% Nonwhite att3         att3:leve…
## 10 Racial Composition            96% White, 4% Nonwhite  att3         att3:leve…
## # ℹ 14 more rows

⚖️ Explore: Compare Trade-offs Directly

Example: Low Housing Costs vs. Low Crime Rates
qoi_1 <- set_qoi(
  .structure = "choice_level",
  .att_choose = "att1",
  .lev_choose = "level1",
  .att_notchoose = "att6",
  .lev_notchoose = "level2"
)

mm1 <- projoint(.data = out1_arranged,
                .qoi = qoi_1,
                .structure = "choice_level",
                .ignore_position = TRUE)

plot(mm1,
     .labels = c("Housing Cost\n(15% of pre-tax income)",
                 "Violent Crime Rate\n(20% > National Average)")) +
  labs(y = "Marginal Mean", x = NULL) +
  coord_cartesian(ylim = c(0, 1)) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "darkgray")
## The attributes are different between the two levels. Check your setting carefully. The attribute label is not added to the figure.


🧩 Explore: Compare Multiple Levels Simultaneously

Example: Urban vs. Suburban Preferences
qoi_2 <- set_qoi(
  .structure = "choice_level",
  .att_choose = "att7",
  .lev_choose = c("level2", "level3"),
  .att_notchoose = "att7",
  .lev_notchoose = c("level5", "level6")
)

mm2 <- projoint(.data = out1_arranged,
                .qoi = qoi_2,
                .structure = "choice_level",
                .ignore_position = TRUE)

plot(mm2,
     .labels = c("City", "Suburban")) +
  labs(y = "Marginal Mean", x = "Type of Place") +
  coord_cartesian(ylim = c(0, 1)) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "darkgray")


📊 Compare: Subgroup Differences

Choice-Level Subgroup Comparison: Party Differences
df_D <- exampleData1 %>% filter(party_1 == "Democrat") %>% reshape_projoint(outcomes)
df_R <- exampleData1 %>% filter(party_1 == "Republican") %>% reshape_projoint(outcomes)
df_0 <- exampleData1 %>% filter(party_1 %in% c("Something else", "Independent")) %>% reshape_projoint(outcomes)

qoi <- set_qoi(
  .structure = "choice_level",
  .estimand = "mm",
  .att_choose = "att2",
  .lev_choose = "level3",
  .att_notchoose = "att2",
  .lev_notchoose = "level1"
)

out_D <- projoint(df_D, qoi)
out_R <- projoint(df_R, qoi)
out_0 <- projoint(df_0, qoi)

out_merged <- bind_rows(
  out_D$estimates %>% mutate(party = "Democrat"),
  out_R$estimates %>% mutate(party = "Republican"),
  out_0$estimates %>% mutate(party = "Independent")
) %>% filter(estimand == "mm_corrected")

# Plot

ggplot(out_merged, aes(y = party, x = estimate)) +
  geom_vline(xintercept = 0.5, linetype = "dashed", color = "gray") +
  geom_pointrange(aes(xmin = conf.low, xmax = conf.high)) +
  geom_text(aes(label = format(round(estimate, 2), nsmall = 2)), vjust = -1) +
  labs(y = NULL, x = "Choice-level Marginal Mean",
       title = "Preference for Democratic-majority areas") +
  theme_classic()


🏠 Home: Home