Skip to content

Commit

Permalink
Merge pull request #22 from jhelvy/design-fixes
Browse files Browse the repository at this point in the history
Design fixes
  • Loading branch information
jhelvy authored Jul 13, 2023
2 parents f2814ba + 7b455dd commit d8a33c6
Show file tree
Hide file tree
Showing 11 changed files with 908 additions and 511 deletions.
3 changes: 0 additions & 3 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@
^docs$
^pkgdown$
^\.github$
vignettes
^README\.Rmd$
^LICENSE\.md$
^cjTools\.Rproj$
^doc$
^Meta$
^next_release\.md$
^build\.R$
^foo\.R$
^cran-comments\.md$
Expand Down
3 changes: 3 additions & 0 deletions CRAN-SUBMISSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Version: 0.5.0
Date: 2023-07-11 10:27:50 UTC
SHA: a1ceb125c1d1f805dfa343c723227d8efe2565f5
9 changes: 6 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
Package: cbcTools
Title: A Simulation-Based Workflow to Design and Evaluate Choice-Based Conjoint Survey Experiments
Version: 0.4.0
Title: Choice-Based Conjoint Experiment Design Generation and Power Evaluation in R
Version: 0.5.0
Maintainer: John Helveston <[email protected]>
Authors@R: c(
person(given = "John",
family = "Helveston",
role = c("cre", "aut", "cph"),
email = "[email protected]",
comment = c(ORCID = "0000-0002-2657-9191")))
Description: A simulation-based workflow to design and evaluate choice-based conjoint survey experiments. Generate a variety of survey designs, including full factorial designs, orthogonal designs, and Bayesian D-efficient designs as well as designs with "no choice" options and "labeled" (also known as "alternative specific") designs. Full factorial and orthogonal designs are obtained using the 'DoE.base' package (Grömping, 2018) <doi:10.18637/jss.v085.i05>. Bayesian D-efficient designs are obtained using the 'idefix' package (Traets et al, 2020) <doi:10.18637/jss.v096.i03>. Conveniently inspect the design balance and overlap, and simulate choice data for a survey design either randomly or according to a multinomial or mixed logit utility model defined by user-provided prior parameters. Conduct a power analysis for a given survey design by estimating the same model on different subsets of the data to simulate different sample sizes. Choice simulation and model estimation in power analyses are handled using the 'logitr' package (Helveston, 2023) <doi:10.18637/jss.v105.i10>.
Description: Design and evaluate choice-based conjoint survey experiments. Generate a variety of survey designs, including random designs, full factorial designs, orthogonal designs, D-optimal designs, and Bayesian D-efficient designs as well as designs with "no choice" options and "labeled" (also known as "alternative specific") designs. Conveniently inspect the design balance and overlap, and simulate choice data for a survey design either randomly or according to a multinomial or mixed logit utility model defined by user-provided prior parameters. Conduct a power analysis for a given survey design by estimating the same model on different subsets of the data to simulate different sample sizes. Full factorial and orthogonal designs are obtained using the 'DoE.base' package (Grömping, 2018) <doi:10.18637/jss.v085.i05>. D-optimal designs are obtained using the 'AlgDesign' package (Wheeler, 2022) <https://CRAN.R-project.org/package=AlgDesign>. Bayesian D-efficient designs are obtained using the 'idefix' package (Traets et al, 2020) <doi:10.18637/jss.v096.i03>. Choice simulation and model estimation in power analyses are handled using the 'logitr' package (Helveston, 2023) <doi:10.18637/jss.v105.i10>.
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
VignetteBuilder: knitr
Depends:
R (>= 3.5.0)
Suggests:
here,
knitr,
testthat,
tibble
Imports:
AlgDesign,
DoE.base,
fastDummies,
ggplot2,
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# cbcTools (development version)

# cbcTools 0.5.0

- Further revisions to the `method` argument in the `cbc_design()` function.
- Added the `"random"` and `"dopt"` methods.
- Added restrictions so that orthogonal designs cannot use the `label` argument or restricted profile sets (as either of these would result in a non-orthogonal design).

# cbcTools 0.4.0

- Adjustments made to the `method` argument in the `cbc_design()` function in preparation for potentially adding new design methods.
Expand Down
815 changes: 509 additions & 306 deletions R/design.R

Large diffs are not rendered by default.

83 changes: 55 additions & 28 deletions R/input_checks.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,22 @@ check_inputs_restrict <- function(profiles) {
}

check_design_method <- function(method, priors) {

# Check that an appropriate method is used

if (! method %in% c(
'random', 'full', 'orthogonal', 'dopt', 'CEA', 'Modfed'
)) {
stop(
'The "method" argument must be set to "random", "full", ',
'"orthogonal", "dopt", "CEA", or "Modfed"'
)
}

# Check that a Bayesian method is used if priors are used

if (!is.null(priors)) {
if (! method_is_bayesian(method)) {
if (!method_is_bayesian(method)) {
# Set method to 'CEA' if priors are specified and
# user didn't specify an appropriate method.
warning(
Expand All @@ -39,6 +53,7 @@ check_design_method <- function(method, priors) {
method <- 'CEA'
}
}

return(method)
}

Expand All @@ -56,61 +71,73 @@ check_inputs_design <- function(
priors,
prior_no_choice,
probs,
keep_d_eff,
keep_db_error,
max_iter,
parallel
parallel,
profiles_restricted
) {

# Checks on blocking

if (n_blocks < 1) {
stop('n_blocks must be greater than or equal to 1')
}

if (n_blocks > n_resp) {
stop("Maximum allowable number of blocks is one block per respondent")
stop("Maximum allowable number of blocks is one block per respondent")
}

# If using a Bayesian D-efficient design with a no choice option, user must
# specify a value for prior_no_choice
if (no_choice) {
if (!is.null(priors) & is.null(prior_no_choice)) {
if ((n_blocks > 1) & (method == 'random')) {
stop(
'The "random" method cannot use blocking. Either change the design ',
'method or set "n_blocks = 1"'
)
if ((method == 'full') & profiles_restricted) {
stop(
'If "no_choice = TRUE", you must specify the prior utility ',
'value for the "no choice" option using prior_no_choice'
'The "full" method cannot use restricted profiles when blocking ',
'is used. Either set "n_blocks" to 1 or use an unrestricted ',
'set of profiles'
)
}
}

# The labeled design isn't yet supported for Bayesian D-efficient designs
# Checks on labeled designs

if (!is.null(label) & method_is_bayesian(method)) {
stop(
'The use of the "label" argument is currently not compatible with ',
'Bayesian D-efficient designs'
)
if (!is.null(label)) {
if (!method %in% c('random', 'full')) {
stop(
'Labeled designs are currently only supported with the "random" or ',
'"full" method.'
)
}
}

# Check that an appropriate method is used
# Check on restricted profile sets

if (! method %in% c('full', 'orthogonal', 'CEA', 'Modfed')) {
if (profiles_restricted) {
if (!method %in% c('random', 'full')) {
stop(
'The "method" argument must be set to "full", "orthogonal", ',
'"Modfed", or "CEA"'
'Restricted profile sets can only be used with the "random", "full" ',
'"dopt", or "Modfed" methods'
)
}
}

# Check that priors are appropriate if specified

if (!is.null(priors)) {

# Check that user specified an appropriate method
# This should already be handled
if (! method_is_bayesian(method)) {
stop(
'Since "priors" are specified, the "method" argument must ',
'be either "Modfed" or "CEA" to obtain a Bayesian ',
'D-efficient design'
)
}
# If using a Bayesian D-efficient design with a no choice option,
# user must specify a value for prior_no_choice

if (no_choice & is.null(prior_no_choice)) {
stop(
'If "no_choice = TRUE" with the "CEA" or "Modfed" method, you must ',
'specify the prior utility for the "no choice" option using ',
'"prior_no_choice"'
)
}

# Check that prior names aren't missing
prior_names <- names(priors)
Expand Down
18 changes: 10 additions & 8 deletions R/power.R
Original file line number Diff line number Diff line change
Expand Up @@ -249,19 +249,20 @@ extract_errors <- function(models) {
#' @importFrom rlang .data
#' @export
#' @examples
#' \dontrun{
#' library(cbcTools)
#'
#' # Generate all possible profiles
#' profiles <- cbc_profiles(
#' price = c(1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5),
#' price = c(1, 1.5, 2, 2.5, 3),
#' type = c("Fuji", "Gala", "Honeycrisp"),
#' freshness = c('Poor', 'Average', 'Excellent')
#' )
#'
#' # Make designs to compare: full factorial vs bayesian d-efficient
#' design_full <- cbc_design(
#' design_random <- cbc_design(
#' profiles = profiles,
#' n_resp = 300, n_alts = 3, n_q = 6
#' n_resp = 100, n_alts = 3, n_q = 6
#' )
#' # Same priors will be used in bayesian design and simulated choices
#' priors <- list(
Expand All @@ -271,26 +272,27 @@ extract_errors <- function(models) {
#' )
#' design_bayesian <- cbc_design(
#' profiles = profiles,
#' n_resp = 300, n_alts = 3, n_q = 6, n_start = 1, method = "CEA",
#' n_resp = 100, n_alts = 3, n_q = 6, n_start = 1, method = "CEA",
#' priors = priors, parallel = FALSE
#' )
#'
#' # Obtain power for each design by simulating choices
#' power_full <- design_full |>
#' power_random <- design_random |>
#' cbc_choices(obsID = "obsID", priors = priors) |>
#' cbc_power(
#' pars = c("price", "type", "freshness"),
#' outcome = "choice", obsID = "obsID", nbreaks = 10, n_q = 6, n_cores = 2
#' outcome = "choice", obsID = "obsID", nbreaks = 5, n_q = 6, n_cores = 2
#' )
#' power_bayesian <- design_bayesian |>
#' cbc_choices(obsID = "obsID", priors = priors) |>
#' cbc_power(
#' pars = c("price", "type", "freshness"),
#' outcome = "choice", obsID = "obsID", nbreaks = 10, n_q = 6, n_cores = 2
#' outcome = "choice", obsID = "obsID", nbreaks = 5, n_q = 6, n_cores = 2
#' )
#'
#' # Compare power of each design
#' plot_compare_power(power_bayesian, power_full)
#' plot_compare_power(power_bayesian, power_random)
#' }
plot_compare_power <- function(...) {
power <- list(...)
design_names <- unlist(lapply(as.list(match.call())[-1], deparse))
Expand Down
Loading

0 comments on commit d8a33c6

Please sign in to comment.