# Tidy the environment prior to loading packages ----
# cmdlR::tidy()
rm(list = ls(all = TRUE))
# Load the packages ----
pkgs <- c("cmdlr")
invisible(lapply(pkgs, require, character.only = TRUE))
# Define the list of estimation options ----
control <- list(
optimizer = "ucminf",
method = "BFGS"
)
# Define the list of model options ----
# NOTE: The outside good must be the first good and it is always available and
# consumed.
model_options <- list(
name = "MDCEV with outside good",
description = "A MDCEV model with an outside good using the Apollo time use data to check code performance. Check out the Apollo package",
id = "indivID",
ct = "ct",
choice = "weekend", # This is not used for the MDCEV model, but needs to be specified
alt_avail = list(
outside = 1,
work = 1,
school = 1,
shopping = 1,
private = 1,
leisure = 1
),
fixed = c("delta_outside", "sigma"),
param = list(
alpha_base = 0,
gamma_work = 1,
gamma_school = 1,
gamma_shopping = 1,
gamma_private = 1,
gamma_leisure = 1,
delta_outside = 0,
delta_work = 0,
delta_school = 0,
delta_shopping = 0,
delta_private = 0,
delta_leisure = 0,
delta_work_FT = 0,
delta_work_wknd = 0,
delta_school_young = 0,
delta_leisure_wknd = 0,
sigma = 0
)
)
# Likelihood function - returns the probability of the sequence of choices ----
ll <- function(param) {
# Define the list of utilities ----
# NOTE: The utility of the outside good must be equal to 0. The constant (created in the data)
# ensures that all filled-in rows are equal to NA
V <- list(
outside = delta_outside * constant,
work = delta_work + delta_work_FT * occ_full_time + delta_work_wknd * weekend,
school = delta_school + delta_school_young * (age<=30),
shopping = delta_shopping * constant,
private = delta_private * constant,
leisure = delta_leisure + delta_leisure_wknd * weekend
)
# Define consumption / continuous choice ----
x_star <- list(
outside = t_outside / 60,
work = t_a02 / 60,
school = t_a03 / 60,
shopping = t_a04 / 60,
private = t_a05 / 60,
leisure = t_leisure / 60
)
# Define prices / cost ----
# Price of outside good (p_1) must be set equal to 1 for term 3 to calculate correctly
p_star <- list(
outside = 1,
work = 1,
school = 1,
shopping = 1,
private = 1,
leisure = 1
)
# Define alpha ----
alpha <- list(
outside = 1 / (1 + exp(-alpha_base)),
work = 1 / (1 + exp(-alpha_base)),
school = 1 / (1 + exp(-alpha_base)),
shopping = 1 / (1 + exp(-alpha_base)),
private = 1 / (1 + exp(-alpha_base)),
leisure = 1 / (1 + exp(-alpha_base))
)
# Define gamma ----
gamma <- list(
outside = 1, # This is just here to make the indexing work out. It's never actually used
work = gamma_work,
school = gamma_school,
shopping = gamma_shopping,
private = gamma_private,
leisure = gamma_leisure
)
# Define sigma ----
sigma <- exp(sigma)
# Calculate the probabilities ----
# Define the discrete choice indicator
discrete_choice <- lapply(x_star, ">", 0)
# Define the total number of chosen alternatives M
total_chosen <- Reduce("+", discrete_choice)
# Discrete choice available
discrete_choice_avail <- mapply("*", discrete_choice, alt_avail, SIMPLIFY = FALSE)
# Calculate V
V <- lapply(seq_along(V), function(i) {
if(i == 1) {
# Only works if the outside good is the first alternative AND the utility is set to zero
V[[i]] + ((alpha[[i]] - 1) * log(x_star[[i]])) * alt_avail[[i]]
} else {
V[[i]] + ((alpha[[i]] - 1) * log((x_star[[i]] / gamma[[i]]) + 1) - log(p_star[[i]])) * alt_avail[[i]]
}
})
# Taking the natural logarithm of P() makes it easier to program the log-likelihood
# function. Equation 33 in Bhat (2008). P() then becomes 5 terms that can be summed
# First term
term_1 <- (1 - total_chosen) * log(sigma)
# Second term
# Calculate log_fi - the first alternative is the outside good
log_fi <- lapply(seq_along(alpha), function(i) {
if (i == 1) {
log(1 - alpha[[i]]) - log(x_star[[i]])
} else {
log(1 - alpha[[i]]) - log(x_star[[i]] + gamma[[i]])
}
})
term_2 <- Reduce("+", lapply(seq_along(log_fi), function(i) {
# Use discrete choice and alt_avail to restrict to zero if not chosen
log_fi[[i]] * discrete_choice_avail[[i]]
}))
# Third term
term_3 <- log(Reduce("+", lapply(seq_along(log_fi), function(i) {
(p_star[[i]] / exp(log_fi[[i]])) * discrete_choice_avail[[i]]
})))
# Fourth term
term_4_1 <- Reduce("+", lapply(seq_along(V), function(i) {
(V[[i]] / sigma) * discrete_choice_avail[[i]]
}))
term_4_2 <- log(Reduce("+", lapply(seq_along(V), function(i) {
exp(V[[i]] / sigma) * alt_avail[[i]]
})))
term_4 <- term_4_1 - (total_chosen * term_4_2)
# Fith term
term_5 <- lfactorial(total_chosen - 1)
# Add the terms of the log-likelihood expression
pr_chosen <- exp(term_1 + term_2 + term_3 + term_4 + term_5)
# Calculate the product over the panel by reshaping to have rows equal to S
pr_chosen <- matrix(pr_chosen, nrow = n_ct)
# If the data is padded, we need to insert ones before taking the product
index_na <- is.na(pr_chosen)
pr_chosen[index_na][!is.nan(pr_chosen[index_na])] <- 1
# Calculate the probability of the sequence
pr_seq <- Rfast::colprods(pr_chosen)
# Return the likelihood value
ll <- log(pr_seq)
attributes(ll) <- list(
pr_chosen = pr_chosen
)
return(-ll)
}
# Load and manipulate the data ----
db <- apollo::apollo_timeUseData
db$t_outside <- rowSums(db[, c("t_a01", "t_a06", "t_a10", "t_a11", "t_a12")])
db$t_leisure <- rowSums(db[, c("t_a07", "t_a08", "t_a09")])
# Create a constant and a new choice task variable
db$constant <- 1
db$ct <- Reduce("c", lapply(unique(db$indivID), function(x) seq_len(length(which(db$indivID == x)))))
# Prepare estimation environment ----
estim_env <- prepare(ll, db, model_options, control)
# Estimate the model ----
model <- estimate(ll, estim_env, model_options, control)
# Get a summary of the results ----
summary(model)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.