Skip to contents

Due to IRR-induced measurement error, default MMs and AMCEs in conjoint analysis are biased. By default, projoint produces bias-corrected estimates automatically.

4.1 Load the projoint package and set up the data

library(projoint)

outcomes <- paste0("choice", seq(from = 1, to = 8, by = 1))
outcomes <- c(outcomes, "choice1_repeated_flipped")
out1 <- reshape_projoint(exampleData1, outcomes)

4.2 Why do we recommend you use IDs?

To estimate and visualize the quantities of your interest using our package (at least in the current version), you need to set the attributes and levels using the IDs, such as att1 and level2, rather than their actual labels, such as “Housing Cost” (for att1) and “15% of pre-tax income” (for level1 of att1). We think that this approach is prone to error because some applications may include foreign characters (e.g., 2-byte Japanese characters) or special characters. Sometimes, your design may include the same levels for more than one attribute; for example, “Teaching quality” = {“High”, “Low”} and “Research quality” = {“High”, “Low”)). To avoid any possible strange result, we use the unique identifiers for each attribute-level.

To check which IDs correspond to which attributes and levels, you can type the following line in your script:

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_labels() and save a CSV file for your reference.

4.3 Estimate MMs

There are two main setups for which projoint can produce estimates. The profile-level is the most common approach in the literature, while the choice-level is more flexible and powerful.

MM (profile-level) – all levels

The default method in projoint is to estimate marginal means (MMs) at the profile-level. If you run projoint(out1), it will conduct this analysis. We expand all those arguments below for clarity:

mm0 <- projoint(.data = out1,
                .qoi = NULL,
                .by_var = NULL,
                .structure = "profile_level",
                .estimand = "mm",
                .se_method = "analytical",
                .irr = NULL, 
                .remove_ties = TRUE,
                .ignore_position = NULL,
                .n_sims = NULL,
                .n_boot = NULL,
                .weights_1 = NULL,
                .clusters_1 = NULL,
                .se_type_1 = "classical",
                .weights_2 = NULL,
                .clusters_2 = NULL,
                .se_type_2 = "classical")

print(mm0)
## [A projoint output]
##  Estimand: mm 
##  Structure: profile_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(mm0)
## # A tibble: 48 × 6
##    estimand       estimate     se conf.low conf.high att_level_choose
##    <chr>             <dbl>  <dbl>    <dbl>     <dbl> <chr>           
##  1 mm_uncorrected    0.574 0.0134    0.548     0.601 att1:level1     
##  2 mm_corrected      0.614 0.0207    0.573     0.654 att1:level1     
##  3 mm_uncorrected    0.485 0.0134    0.458     0.511 att1:level2     
##  4 mm_corrected      0.477 0.0204    0.437     0.517 att1:level2     
##  5 mm_uncorrected    0.445 0.0131    0.419     0.470 att1:level3     
##  6 mm_corrected      0.416 0.0203    0.376     0.455 att1:level3     
##  7 mm_uncorrected    0.489 0.0133    0.463     0.515 att2:level1     
##  8 mm_corrected      0.483 0.0202    0.443     0.522 att2:level1     
##  9 mm_uncorrected    0.524 0.0130    0.498     0.549 att2:level2     
## 10 mm_corrected      0.536 0.0200    0.497     0.575 att2:level2     
## # ℹ 38 more rows

MM (profile-level) – specific level

However, it is also possible to specify the precise attributes, and attribute levels, of interest using the set_qoi function as below. The set_qoi function is the main way for users to specify more complex estimands in conjoint analysis. Al arguments for qoi() are expanded for clarity, but the default arguments in projoint() are dropped for simplicity:

qoi_1 <- set_qoi(
  .structure = "profile_level",
  .estimand = "mm",
  .att_choose = "att1", 
  .lev_choose = "level1")

mm1 <- projoint(.data = out1,
                .qoi = qoi_1)

print(mm1)
## [A projoint output]
##  Estimand: mm 
##  Structure: profile_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(mm1)
## # A tibble: 2 × 7
##   estimand       estimate     se conf.low conf.high att_level_choose
##   <chr>             <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 mm_uncorrected    0.574 0.0134    0.548     0.601 att1:level1     
## 2 mm_corrected      0.614 0.0207    0.573     0.654 att1:level1     
## # ℹ 1 more variable: att_level_notchoose <chr>

MM (profile-level) – specific level, using the predicted IRR

If your conjoint survey design does not include the recommended repeated task, you can use the specific value for .irr, which you can estimate using the predict_tau() function or specify the value based on prior knowledge. In the following, we use 0.75, which is most likely IRR we find in our test data sets. The default arguments for both set_qoi() and projoint() are dropped hereafter.

mm1b <- projoint(.data = out1,
                 .qoi = qoi_1,
                 .irr = 0.75)

print(mm1b)
## [A projoint output]
##  Estimand: mm 
##  Structure: profile_level 
##  IRR: Assumed (0.75) 
##  Tau: 0.1464466 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(mm1b)
## # A tibble: 2 × 7
##   estimand       estimate     se conf.low conf.high att_level_choose
##   <chr>             <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 mm_uncorrected    0.574 0.0134    0.548     0.601 att1:level1     
## 2 mm_corrected      0.605 0.0190    0.568     0.643 att1:level1     
## # ℹ 1 more variable: att_level_notchoose <chr>

MM (choice-level) – specific level

As soon as users wish to switch from profile-level to choice-level analysis, the set_qoi function is required. There are exponentially more choice-level estimands than there are profile-level estimands, so it is impractical to view all of them. Here we specify that we want to return the marginal mean of attribute 1, levels 1 and 3 at the choice-level.

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

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

print(mm2)
## [A projoint output]
##  Estimand: mm 
##  Structure: choice_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(mm2)
## # 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>

4.4 Estimate AMCEs

Note that we can replicate all above analyses using the AMCE instead of the MM just by specifying that the .estimand = "amce" rather than the default "mm".

AMCE (profile-level) – all levels

amce0 <- projoint(.data = out1,
                  .estimand = "amce")

print(amce0)
## [A projoint output]
##  Estimand: amce 
##  Structure: profile_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(amce0)
## # A tibble: 34 × 7
##    estimand         estimate     se conf.low conf.high att_level_choose
##    <chr>               <dbl>  <dbl>    <dbl>     <dbl> <chr>           
##  1 amce_uncorrected -0.0899  0.0190 -0.127     -0.0527 att1:level2     
##  2 amce_corrected   -0.137   0.0290 -0.194     -0.0801 att1:level2     
##  3 amce_uncorrected -0.130   0.0188 -0.167     -0.0931 att1:level3     
##  4 amce_corrected   -0.198   0.0294 -0.256     -0.140  att1:level3     
##  5 amce_uncorrected  0.0348  0.0186 -0.00170    0.0713 att2:level2     
##  6 amce_corrected    0.0530  0.0284 -0.00258    0.109  att2:level2     
##  7 amce_uncorrected -0.00177 0.0188 -0.0386     0.0350 att2:level3     
##  8 amce_corrected   -0.00270 0.0286 -0.0589     0.0535 att2:level3     
##  9 amce_uncorrected  0.0240  0.0204 -0.0159     0.0640 att3:level2     
## 10 amce_corrected    0.0366  0.0312 -0.0246     0.0979 att3:level2     
## # ℹ 24 more rows
## # ℹ 1 more variable: att_level_choose_baseline <chr>

AMCE (profile-level) – specific level

qoi_3 <- set_qoi(
  .structure = "profile_level",
  .estimand = "amce",
  .att_choose = "att1", 
  .lev_choose = "level3",
  .att_choose_b = "att1",
  .lev_choose_b = "level1"
)

amce1 <- projoint(.data = out1,
                  .qoi = qoi_3,
                  .estimand = "amce")

print(amce1)
## [A projoint output]
##  Estimand: amce 
##  Structure: profile_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(amce1)
## # A tibble: 2 × 9
##   estimand         estimate     se conf.low conf.high att_level_choose
##   <chr>               <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 amce_uncorrected   -0.130 0.0188   -0.167   -0.0931 att1:level3     
## 2 amce_corrected     -0.198 0.0294   -0.256   -0.140  att1:level3     
## # ℹ 3 more variables: att_level_notchoose <chr>,
## #   att_level_choose_baseline <chr>, att_level_notchoose_baseline <chr>

AMCE (profile-level) – specific level, using the predicted IRR

amce1b <- projoint(.data = out1,
                 .qoi = qoi_3,
                 .estimand = "amce",
                 .irr = 0.75)

print(amce1b)
## [A projoint output]
##  Estimand: amce 
##  Structure: profile_level 
##  IRR: Assumed (0.75) 
##  Tau: 0.1464466 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(amce1b)
## # A tibble: 2 × 9
##   estimand         estimate     se conf.low conf.high att_level_choose
##   <chr>               <dbl>  <dbl>    <dbl>     <dbl> <chr>           
## 1 amce_uncorrected   -0.130 0.0188   -0.167   -0.0931 att1:level3     
## 2 amce_corrected     -0.184 0.0266   -0.236   -0.132  att1:level3     
## # ℹ 3 more variables: att_level_notchoose <chr>,
## #   att_level_choose_baseline <chr>, att_level_notchoose_baseline <chr>

AMCE (choice-level) – 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(.data = out1,
                  .qoi = qoi_4,
                  .structure = "choice_level",
                  .estimand = "amce", 
                  .ignore_position = TRUE)

print(amce2)
## [A projoint output]
##  Estimand: amce 
##  Structure: choice_level 
##  IRR: Estimated 
##  Tau: 0.1721281 
##  Remove ties: TRUE 
##  SE methods: analytical
summary(amce2)
## # 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>