diff --git a/.DS_Store b/.DS_Store index bc9d806..d1e21bc 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/data-raw/ACHR_Modified_2.php b/data-raw/ACHR_Modified_2.php new file mode 100644 index 0000000..e50924a --- /dev/null +++ b/data-raw/ACHR_Modified_2.php @@ -0,0 +1,242 @@ + array("Man", "Woman"), + "Age" => array("40", "52", "61"), + "Graduate degree from" => array("University of Michigan", + "Florida State University", + "Kennesaw State University", + "Capella University"), + "Prior years of relevant experience" => array("5", "10", "15"), + "Communication skills" => array("Moderate", "Strong", "Very Strong"), + "Strength of references" => array("Moderate", "Strong", "Very Strong"), + "Quality of writing sample" => array("Moderate", "Strong", "Very Strong"), + "Race/ethnicity" => array("White", "Black", "Hispanic", "Asian") + ); + +$restrictionarray = array(); + +$probabilityarray = array( + "Gender" => array(0.5, 0.5), + "Age" => array(0.33333, 0.33333, 0.33333), + "Graduate degree from" => array(0.25, 0.25, 0.25, 0.25), + "Prior years of relevant experience" => array(0.33333, 0.33333, 0.33333), + "Communication skills" => array(0.33333, 0.33333, 0.33333), + "Strength of references" => array(0.33333, 0.33333, 0.33333), + "Quality of writing sample" => array(0.33333, 0.33333, 0.33333), + "Race/ethnicity" => array(0.4, 0.2, 0.2, 0.2) + ); + +// Indicator for whether weighted randomization should be enabled or not +$weighted = 1; + +// K = Number of tasks displayed to the respondent +$K = 20; + +// N = Number of profiles displayed in each task +$N = 2; + +// num_attributes = Number of Attributes in the Array +$num_attributes = count($featurearray); + + +$attrconstraintarray = array(); + + +// Re-randomize the $featurearray + +// Place the $featurearray keys into a new array +$featureArrayKeys = array(); +$incr = 0; + +foreach($featurearray as $attribute => $levels){ + $featureArrayKeys[$incr] = $attribute; + $incr = $incr + 1; +} + +// Backup $featureArrayKeys +$featureArrayKeysBackup = $featureArrayKeys; + +// If order randomization constraints exist, drop all of the non-free attributes +if (count($attrconstraintarray) != 0){ + foreach ($attrconstraintarray as $constraints){ + if (count($constraints) > 1){ + for ($p = 1; $p < count($constraints); $p++){ + if (in_array($constraints[$p], $featureArrayKeys)){ + $remkey = array_search($constraints[$p],$featureArrayKeys); + unset($featureArrayKeys[$remkey]); + } + } + } + } +} +// Re-set the array key indices +$featureArrayKeys = array_values($featureArrayKeys); +// Re-randomize the $featurearray keys +shuffle($featureArrayKeys); + +// Re-insert the non-free attributes constrained by $attrconstraintarray +if (count($attrconstraintarray) != 0){ + foreach ($attrconstraintarray as $constraints){ + if (count($constraints) > 1){ + $insertloc = $constraints[0]; + if (in_array($insertloc, $featureArrayKeys)){ + $insert_block = array($insertloc); + for ($p = 1; $p < count($constraints); $p++){ + if (in_array($constraints[$p], $featureArrayKeysBackup)){ + array_push($insert_block, $constraints[$p]); + } + } + + $begin_index = array_search($insertloc, $featureArrayKeys); + array_splice($featureArrayKeys, $begin_index, 1, $insert_block); + } + } + } +} + + +// Re-generate the new $featurearray - label it $featureArrayNew + +$featureArrayNew = array(); +foreach($featureArrayKeys as $key){ + $featureArrayNew[$key] = $featurearray[$key]; +} + + +// Initialize the array returned to the user +// Naming Convention +// Level Name: B-[task number]-[profile number]-[attribute number] +// Attribute Name: B-[task number]-[attribute number] +// Example: B-1-3-2, Returns the level corresponding to Task 1, Profile 3, Attribute 2 +// B-3-3, Returns the attribute name corresponding to Task 3, Attribute 3 + +$returnarray = array(); + +// For each task $p +for($p = 1; $p <= $K; $p++){ + weighted_randomize_race(); + $type = weighted_randomize_race(); + + // Repeat until non-restricted profile generated + $complete = False; + + while ($complete == False){ + + // For each profile $i + for($i = 1; $i <= $N; $i++){ + + + // Create a count for $attributes to be incremented in the next loop + $attr = 0; + + // Create a dictionary to hold profile's attributes + $profile_dict = array(); + + // For each attribute $attribute and level array $levels in task $p + foreach($featureArrayNew as $attribute => $levels){ + + // Increment attribute count + $attr = $attr + 1; + + if ($attribute == "Race/ethnicity"){ + $treat_number = $attr; + } + + // Create key for attribute name + $attr_key = "B-" . (string)$p . "-" . (string)$attr; + + // Store attribute name in $returnarray + $returnarray[$attr_key] = $attribute; + + // Get length of $levels array + $num_levels = count($levels); + + // Randomly select one of the level indices + if ($weighted == 1){ + $level_index = weighted_randomize($probabilityarray, $attribute) - 1; + + }else{ + $level_index = mt_rand(1,$num_levels) - 1; + } + + // Pull out the selected level + $chosen_level = $levels[$level_index]; + + // Store selected level in $profileDict + $profile_dict[$attribute] = $chosen_level; + + // Create key for level in $returnarray + $level_key = "B-" . (string)$p . "-" . (string)$i . "-" . (string)$attr; + + // Store selected level in $returnarray + $returnarray[$level_key] = $chosen_level; + + } + + } + + $treat_profile_one = "B-" . (string)$p . "-1-" . (string)$treat_number; + $treat_profile_two = "B-" . (string)$p . "-2-" . (string)$treat_number; + $cond1 = $returnarray[$treat_profile_one] == "White" && $returnarray[$treat_profile_two] == $type; + $cond2 = $returnarray[$treat_profile_two] == "White" && $returnarray[$treat_profile_one] == $type; + + if ($cond1 or $cond2){ + $complete = True; + } + + } + } + + +// Return the array back to Qualtrics +print json_encode($returnarray); +?> diff --git a/vignettes/01-setup.Rmd b/vignettes/01-setup.Rmd index f335656..6bb83a6 100644 --- a/vignettes/01-setup.Rmd +++ b/vignettes/01-setup.Rmd @@ -9,32 +9,64 @@ vignette: > %\VignetteEncoding{UTF-8} --- -We are working on developing our own tools for building a conjoint survey in Qualtrics. In the meantime, we recommend using Anton Strezhnev's **Conjoint Survey Design Tool** (Link: [conjointSDT](https://github.com/astrezhnev/conjointsdt/)). Strezhnev's guide to designing and implementing conjoint surveys in Qualtrics is excellent and we will not reiterate it here. But to summarize: researchers should first design their conjoint study in ConjointSDT and produce a JavaScript file, which is then inputted into the first screen of your Qualtrics survey, using the Edit Question Javascript functionality. See the following screenshot as an example: +We are working on developing our own tools for building a conjoint survey in Qualtrics. In the meantime, we recommend using Anton Strezhnev's **Conjoint Survey Design Tool** (Link: [conjointSDT](https://github.com/astrezhnev/conjointsdt/)). -![](../man/figures/embedded_js.png){#id .class width=80% height=80%} +### 1.1 Generate a JavaScript or PHP randomizer + +Strezhnev's guide to designing and implementing conjoint surveys in Qualtrics is excellent and we will not reiterate it here. But to summarize: researchers should first use ConjointSDT to produce a JavaScript or PHP randomizer. + +#### JavaScript +The JavaScript randomizer can be inputted into the first screen of your Qualtrics survey, using the Edit Question Javascript functionality. See the following screenshot as an example: + +![](../man/figures/embedded_js.png){#id .class width=80% height=80%} The above Javascript is available from [here](https://raw.githubusercontent.com/yhoriuchi/projoint/master/data-raw/example.js). The JavaScript produced by ConjointSDT will run internally within Qualtrics and automatically creates the values for *embedded fields*. These are the attributes and levels that constitute the profile pairs of each conjoint task. For example, embedded field "K-1-1-7" contains the value (level) of the seventh attribute for the first profile (of two) in the first task, and "K-5-2-5" contains the level of the fifth attribute for the second profile (of two) in the fifth task. -What you should do is to design, for each task, a table using HTML and insert these embedded fiels. The following is the screenshot of the first task in our example. +#### PHP + +The PHP randomizer must be hosted by a server. The following is an example hosted at our server: https://www.horiuchi.org/php/ACHR_Modified_2.php. (This PHP file is available from [here](https://raw.githubusercontent.com/yhoriuchi/projoint/master/data-raw/ACHR_Modified_2.php).) This is the PHP randomizer used by [Agadjanian, Carey, Horiuchi, and Ryan (2023)](https://www.nowpublishers.com/article/Details/QJPS-21119). + +### 1.2 Modify your Javascript or PHP randomizer + +To add some additional constraints, such as removing ties between two profiles, you need to revise your PHP scripts or Javascript manually. In the future release of this package, we will add additional feature to make this step easier. For now, we recommend you use other resources, including [Open AI's GPT-4](https://openai.com/gpt-4)), to learn how to modify your code. The above PHP example has the following manually added component at the end so that the race of one profile (candidate) is always "White" and another is always "Black," "Asian," or "Hispanic." + +```{r, eval=FALSE} + +$treat_profile_one = "B-" . (string)$p . "-1-" . (string)$treat_number; +$treat_profile_two = "B-" . (string)$p . "-2-" . (string)$treat_number; +$cond1 = $returnarray[$treat_profile_one] == "White" && $returnarray[$treat_profile_two] == $type; +$cond2 = $returnarray[$treat_profile_two] == "White" && $returnarray[$treat_profile_one] == $type; + +if ($cond1 or $cond2){$complete = True;} + +``` + +If you have some other examples of adding additional constraints, please send an email to the package maintainer, [Yusaku Horiuchi](mailto:yusaku.horiuchi@gmail.com) Thank you! + +### 1.3 Add conjoint tables with embedded fields in Qualtrics + +After you set up your JavaScript or PHP randomizer, the next step is to design, for each task, a table using HTML and insert these embedded fiels. The following is the screenshot of the first task in our example. ![](../man/figures/screenshot_first.png){#id .class width=80% height=80%} The complete HTML for the first conjoint task in this survey is available from [here](https://raw.githubusercontent.com/yhoriuchi/projoint/master/data-raw/task_first.html). -A typical conjoint study will include five to ten tasks. The number of questions with such HTML pages should correspond to the number of tasks. The embedded fields in each task are different: The first digit after the "K" will increment from 1 to 10 as the tasks progress. +A typical conjoint study will include 5-10 tasks. The number of questions with such HTML pages should correspond to the number of tasks. The embedded fields in each task are different: The first digit after the "K" will increment from 1 to 10 as the tasks progress. + +#### Adding a repeated task (recommended!) If we wish to implement *a repeated task*, then, all we need to do is copy the task to be repeated (say, the first task) to a later point in the survey (say, after the fifth task). We recommend that researchers *flip the order* of the two profiles as well. Therefore, Profile 1 in the original task becomes Profile 2 in the new task. To do this, we would simply swap the middle digit of the embedded fields as below. The repeated task will then look like the following: ![](../man/figures/screenshot_last.png){#id .class width=80% height=80%} -The complete HTML for the first conjoint task in this survey is available from [here](https://raw.githubusercontent.com/yhoriuchi/projoint/master/data-raw/task_repeated.html). +The complete HTML for the repeated conjoint task in this survey is available from [here](https://raw.githubusercontent.com/yhoriuchi/projoint/master/data-raw/task_repeated.html). -With the repeated task, researchers can use to measure intra-respondent reliability (IRR). +Importantly,with the repeated task, researchers can use to measure intra-respondent reliability (IRR). -For more information about [conjointSDT](https://github.com/astrezhnev/conjointsdt/), see Strezhnev's detailed instructions. +### 1.4 A sample Qualtrics survey If you want to copy the Qualtric survey that we designed for our exmaple, download the QSF file below and import it to your Qualtrics account. You can then use it as a template to design your own conjoint survey experiment! diff --git a/vignettes/02-wrangle.Rmd b/vignettes/02-wrangle.Rmd index 2a39f74..d5ff23d 100644 --- a/vignettes/02-wrangle.Rmd +++ b/vignettes/02-wrangle.Rmd @@ -160,13 +160,13 @@ You can find this data set on GitHub: [labels_original.csv](https://github.com/y The figure based on the original order and labels is in the alphabetical order: ```{r, fig.height=8, fig.width=8} mm <- projoint(out1, .estimand = "mm") -plot(mm, .estimand = "mm") +plot(mm) ``` The labels and order of all attribute-levels in the second figure is the same as Figure 2 in [Mummolo and Nall (2017)](https://doi.org/10.1086/687569). ```{r, fig.height=8, fig.width=8} mm <- projoint(out1_arranged, .estimand = "mm") -plot(mm, .estimand = "mm") +plot(mm) ``` diff --git a/vignettes/03-predict.Rmd b/vignettes/03-predict.Rmd index 7cadaa2..e3f5996 100644 --- a/vignettes/03-predict.Rmd +++ b/vignettes/03-predict.Rmd @@ -17,27 +17,14 @@ When there is a repeated task, it is easy to estimate intra-respondent reliabili library(projoint) ``` -### 3.2 Read and wrangle data without the repeated tasks +### 3.2 Predict IRR based on the extrapolation method -As before, we start by reshaping our data and setting `.repeated` to `FALSE`: +As before, you should start by reading your Qualtrics file and reshaping it using `reshape_projiont()`. See [2.2 Read and wrangle data, with the flipped repeated tasks](https://yhoriuchi.github.io/projoint/articles/02-wrangle.html#read-and-wrangle-data). But we skip this step in this instruction and we use the already wrangled data named "out1_arranged". +We pass this data set to the `predict_tau` function, which both calculates IRR and produces a figure showing the extrapolation method visually. (see [2.3 Arrange the order and labels of attributes and levels](https://yhoriuchi.github.io/projoint/articles/02-wrangle.html#arrange-the-order-and-labels-of-attributes-and-levels})). +With the flipped repeated tasks ```{r} -outcomes <- paste0("choice", seq(from = 1, to = 8, by = 1)) -out3 <- reshape_projoint(.dataframe = exampleData3, - .idvar = "ResponseId", - .outcomes = outcomes, - .outcomes_ids = c("A", "B"), - .alphabet = "K", - .repeated = FALSE, - .flipped = NULL) -``` - -### 3.3 Predict IRR based on the extrapolation method - -Then, we pass this data set to the `predict_tau` function, which both calculates IRR and produces a figure showing the extrapolation method visually. - -```{r} -predicted_irr <- predict_tau(out3) +predicted_irr <- predict_tau(out1_arranged) ``` A `projoint_tau` object, created by `predict_tau`, can be explored using the usual tools. The `print` method explains that this estimate of tau was produced via extrapolation rather than assumed or calculated using a repeated task and presents that estimate: @@ -54,6 +41,6 @@ summary(predicted_irr) And the `plot` method renders a plot showing the extrapolated value of tau: -```{r} +```{r, fig.height=4, fig.width=8} plot(predicted_irr) ``` \ No newline at end of file diff --git a/vignettes/04-estimate.Rmd b/vignettes/04-estimate.Rmd index 9676c52..5176a2c 100644 --- a/vignettes/04-estimate.Rmd +++ b/vignettes/04-estimate.Rmd @@ -9,8 +9,7 @@ vignette: > %\VignetteEncoding{UTF-8} --- -Due to IRR-induced measurement error, default MMs and AMCEs in conjoint analysis are biased. By default, `projoint` produces bias-corrected estimates automatically. - +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