tests/testthat/test-choice_likelihood.R

test_that("choice_likelihood precomputes sufficient statistics", {

  choice_data <- choice_data(
    data_frame = train_choice,
    format = "wide",
    column_choice = "choice",
    column_decider = "deciderID",
    column_occasion = "occasionID"
  )

  choice_effects <- choice_effects(
    choice_formula = choice_formula(
      formula = choice ~ price + time + change + comfort | 0,
      error_term = "probit"
    ),
    choice_alternatives = choice_alternatives(
      J = 2, alternatives = c("A", "B")
    )
  )

  likelihood <- choice_likelihood(
    choice_data = choice_data,
    choice_effects = choice_effects
  )

  expect_s3_class(likelihood, "choice_likelihood")
  expect_true(is.choice_likelihood(likelihood))
  expect_length(likelihood$design_matrices, nrow(choice_data))
  expect_length(likelihood$choice_indices, nrow(choice_data))

  choice_parameters <- generate_choice_parameters(choice_effects)

  direct_eval <- compute_choice_likelihood(
    choice_parameters = choice_parameters,
    choice_data = choice_data,
    choice_effects = choice_effects,
    logarithm = TRUE
  )
  precomp_eval <- compute_choice_likelihood(
    choice_parameters = choice_parameters,
    choice_data = likelihood,
    choice_effects = choice_effects,
    logarithm = TRUE
  )
  expect_equal(direct_eval, precomp_eval)

  optim_space <- switch_parameter_space(choice_parameters, choice_effects)
  optim_eval <- compute_choice_likelihood(
    choice_parameters = optim_space,
    choice_data = likelihood,
    choice_effects = choice_effects,
    logarithm = TRUE
  )
  expect_equal(precomp_eval, optim_eval)
})

test_that("choice likelihood handles ordered data", {
  ordered_df <- data.frame(
    deciderID = 1:4,
    choice = factor(
      c("low", "medium", "high", "medium"),
      levels = c("low", "medium", "high"),
      ordered = TRUE
    )
  )

  ch_data <- choice_data(
    data_frame = ordered_df,
    format = "wide",
    column_choice = "choice",
    column_decider = "deciderID",
    choice_type = "ordered"
  )

  ordered_effects <- choice_effects(
    choice_formula = choice_formula(
      formula = choice ~ 0 | 0,
      error_term = "probit"
    ),
    choice_alternatives = choice_alternatives(
      J = 3,
      alternatives = c("low", "medium", "high"),
      ordered = TRUE
    )
  )

  likelihood <- choice_likelihood(
    choice_data = ch_data,
    choice_effects = ordered_effects
  )
  expect_s3_class(likelihood, "choice_likelihood")

  params <- choice_parameters(
    Sigma = 1,
    gamma = c(0, 1)
  )
  expect_silent(compute_choice_likelihood(
    choice_parameters = params,
    choice_data = likelihood,
    choice_effects = ordered_effects
  ))
})

test_that("choice likelihood enables estimation across model families", {
  skip()

  estimation_specs <- list(
    list(
      label = "multinomial logit",
      seed = 101L,
      N = 300L,
      choice_type = "discrete",
      choice_formula = choice_formula(
        formula = choice ~ x1 + x2 | 0 | 0,
        error_term = "logit"
      ),
      choice_alternatives = choice_alternatives(
        J = 3,
        alternatives = c("A", "B", "C")
      ),
      parameters = choice_parameters(
        beta = c(0.5, -0.35)
      ),
      tol = list(beta = 0.15)
    ),
    list(
      label = "multinomial probit",
      seed = 102L,
      N = 300L,
      choice_type = "discrete",
      choice_formula = choice_formula(
        formula = choice ~ z1 + z2 | 0 | 0,
        error_term = "probit"
      ),
      choice_alternatives = choice_alternatives(
        J = 2,
        alternatives = c("A", "B")
      ),
      parameters = choice_parameters(
        beta = c(-0.4, 0.3),
        Sigma = matrix(c(0, 0, 0, 1), nrow = 2)
      ),
      tol = list(beta = 0.25)
    ),
    list(
      label = "ordered logit",
      seed = 103L,
      N = 400L,
      choice_type = "ordered",
      choice_formula = choice_formula(
        formula = choice ~ q1 + q2 | 0 | 0,
        error_term = "logit"
      ),
      choice_alternatives = choice_alternatives(
        J = 4,
        alternatives = c("low", "medium", "high", "very_high"),
        ordered = TRUE
      ),
      parameters = choice_parameters(
        beta = c(0.25, -0.5),
        gamma = c(0, 0.6, 1.4)
      ),
      tol = list(beta = 0.2, gamma = 0.2)
    ),
    list(
      label = "ordered probit",
      seed = 104L,
      N = 400L,
      choice_type = "ordered",
      choice_formula = choice_formula(
        formula = choice ~ r1 + r2 | 0 | 0,
        error_term = "probit"
      ),
      choice_alternatives = choice_alternatives(
        J = 4,
        alternatives = c("low", "medium", "high", "very_high"),
        ordered = TRUE
      ),
      parameters = choice_parameters(
        beta = c(-0.3, 0.45),
        Sigma = 1.3,
        gamma = c(0, 0.5, 1.2)
      ),
      tol = list(beta = 0.2, gamma = 0.2, Sigma = 0.25)
    ),
    list(
      label = "ranked logit",
      seed = 105L,
      N = 250L,
      choice_type = "ranked",
      choice_formula = choice_formula(
        formula = choice ~ s1 + s2 | 0 | 0,
        error_term = "logit"
      ),
      choice_alternatives = choice_alternatives(
        J = 3,
        alternatives = c("A", "B", "C")
      ),
      parameters = choice_parameters(
        beta = c(0.35, -0.25)
      ),
      tol = list(beta = 0.25)
    ),
    list(
      label = "ranked probit",
      seed = 106L,
      N = 150L,
      choice_type = "ranked",
      choice_formula = choice_formula(
        formula = choice ~ t1 + t2 | 0 | 0,
        error_term = "probit"
      ),
      choice_alternatives = choice_alternatives(
        J = 3,
        alternatives = c("A", "B", "C")
      ),
      parameters = choice_parameters(
        beta = c(-0.45, 0.4),
        Sigma = matrix(
          c(0, 0, 0,
            0, 1, 0.2,
            0, 0.2, 1.5),
          nrow = 3,
          byrow = TRUE
        )
      ),
      tol = list(beta = 0.3)
    )
  )

  for (spec in estimation_specs) {
    set.seed(spec$seed)

    choice_effects <- choice_effects(
      choice_formula = spec$choice_formula,
      choice_alternatives = spec$choice_alternatives
    )

    sample_size <- if (!is.null(spec$N)) spec$N else 200L
    identifiers <- generate_choice_identifiers(N = sample_size)
    choice_data <- generate_choice_data(
      choice_effects = choice_effects,
      choice_identifiers = identifiers,
      choice_parameters = spec$parameters,
      column_choice = "choice",
      choice_type = spec$choice_type
    )

    likelihood <- choice_likelihood(
      choice_data = choice_data,
      choice_effects = choice_effects
    )

    true_vector <- switch_parameter_space(spec$parameters, choice_effects)
    optim_start <- as.numeric(true_vector)
    if (length(optim_start) == 0L) {
      next
    }
    optim_start <- optim_start + stats::rnorm(length(optim_start), sd = 0.1)

    objective <- function(par) {
      likelihood$objective(
        choice_parameters = par,
        logarithm = TRUE,
        negative = TRUE
      )
    }

    fit <- stats::optim(
      par = optim_start,
      fn = objective,
      method = "BFGS",
      control = list(maxit = 200, reltol = 1e-8)
    )

    expect_identical(fit$convergence, 0L, info = spec$label)

    estimated <- switch_parameter_space(fit$par, choice_effects)

    expect_equal(
      estimated$beta,
      spec$parameters$beta,
      tolerance = spec$tol$beta,
      check.attributes = FALSE,
      info = sprintf("beta - %s", spec$label)
    )

    if (!is.null(spec$parameters$gamma)) {
      expect_equal(
        estimated$gamma,
        spec$parameters$gamma,
        tolerance = spec$tol$gamma,
        check.attributes = FALSE,
        info = sprintf("gamma - %s", spec$label)
      )
    }

    if (!is.null(spec$parameters$Sigma) && !is.null(spec$tol$Sigma)) {
      expect_equal(
        estimated$Sigma,
        spec$parameters$Sigma,
        tolerance = spec$tol$Sigma,
        check.attributes = FALSE,
        info = sprintf("Sigma - %s", spec$label)
      )
    }
  }
})

Try the choicedata package in your browser

Any scripts or data that you put into this service are public.

choicedata documentation built on Nov. 5, 2025, 5:46 p.m.