Nothing
#' Balancing cost accessibility measure
#'
#' Calculates the balancing cost measure, which is defined as the travel cost
#' required to reach as many opportunities as the number of people in a given
#' origin. Originally proposed by
#' \insertCite{barboza2021balancing;textual}{accessibility}, under the name
#' "balancing time".
#' @template description_generic_cost
#'
#' @template travel_matrix
#' @template land_use_data
#' @template opportunity
#' @template travel_cost
#' @template demand
#' @param cost_increment A number. The cost increment that should be used when
#' defining the travel cost distribution from which the potential balancing
#' costs will be picked. For example, an increment of 1 tends to suitable for
#' travel time distributions, meaning that the function will first check if
#' any origins reach their balancing cost with a travel time of 0 minutes,
#' then 1 minute, 2 minutes, 3, 4, ..., etc. A increment of 1 might be too big
#' for a distribution of monetary costs, on the other hand, which could
#' possibly benefit from a smaller increment of 0.05, for example, resulting
#' in the function looking for balancing costs first at a cost of 0, then
#' 0.05, 0.10, ..., etc. Defaults to 1.
#' @template group_by
#' @template fill_missing_ids_combinations
#'
#' @template return_accessibility
#' @return A data frame containing the accessibility estimates for each origin
#' in the travel matrix. Origins marked with a `NA` balancing cost never reach
#' as many opportunities as there is people residing in them, given the
#' specified travel matrix.
#'
#' @references
#' \insertAllCited{}
#'
#' @examplesIf identical(tolower(Sys.getenv("NOT_CRAN")), "true")
#' data_dir <- system.file("extdata", package = "accessibility")
#' travel_matrix <- readRDS(file.path(data_dir, "travel_matrix.rds"))
#' land_use_data <- readRDS(file.path(data_dir, "land_use_data.rds"))
#'
#' bc <- balancing_cost(
#' travel_matrix,
#' land_use_data,
#' opportunity = "jobs",
#' travel_cost = "travel_time",
#' demand = "population"
#' )
#' head(bc)
#' @export
balancing_cost <- function(travel_matrix,
land_use_data,
opportunity,
travel_cost,
demand,
cost_increment = 1,
group_by = character(0),
fill_missing_ids = TRUE) {
checkmate::assert_string(opportunity)
checkmate::assert_string(travel_cost)
checkmate::assert_string(demand)
checkmate::assert_number(cost_increment, lower = 0, finite = TRUE)
checkmate::assert_logical(fill_missing_ids, len = 1, any.missing = FALSE)
assert_group_by(group_by)
assert_travel_matrix(travel_matrix, travel_cost, group_by)
assert_land_use_data(
land_use_data,
travel_matrix,
opportunity,
demand = demand
)
variable <- 3
# if not a dt, keep original class to assign later when returning result
if (!inherits(travel_matrix, "data.table")) {
original_class <- class(travel_matrix)
data <- data.table::as.data.table(travel_matrix)
} else {
data <- data.table::copy(travel_matrix)
}
if (!inherits(land_use_data, "data.table")) {
land_use_data <- data.table::as.data.table(land_use_data)
}
merge_by_reference(data, land_use_data, opportunity, left_df_idcol = "to_id")
groups <- c("from_id", group_by)
warn_extra_cols(travel_matrix, travel_cost, "from_id", groups)
lookup_vec <- land_use_data[[demand]]
names(lookup_vec) <- land_use_data$id
max_lookup_cost <- if (length(data[[travel_cost]]) > 0) {
max(data[[travel_cost]], na.rm = TRUE)
} else {
0
}
lookup_costs <- seq.int(0, max_lookup_cost, by = cost_increment)
# origins with 0 people should always have a balancing time of 0
.demand_colname <- demand
no_pop_origins <- land_use_data[get(.demand_colname) == 0]$id
no_pop_origins_in_ttm <- no_pop_origins[no_pop_origins %in% data$from_id]
grouped_no_pop_origins <- lapply(
groups,
function(group) {
if (group == "from_id") return(no_pop_origins_in_ttm)
unique(data[[group]])
}
)
names(grouped_no_pop_origins) <- groups
access <- do.call(data.table::CJ, grouped_no_pop_origins)
access[, cost := 0]
data <- data[!(from_id %in% no_pop_origins_in_ttm)]
for (i in lookup_costs) {
cum_opps <- calc_cum_cutoff(data, groups, opportunity, travel_cost, i)
cum_opps[, did_balance := access > lookup_vec[from_id]]
balanced <- cum_opps[did_balance == TRUE]
if (nrow(balanced) > 0) {
balanced[, c("access", "did_balance") := NULL]
balanced[, cost := i]
access <- rbind(access, balanced)
data <- data[!balanced, on = groups]
if (nrow(data) == 0) break
}
}
if (fill_missing_ids) {
unique_values <- lapply(groups, function(x) unique(travel_matrix[[x]]))
names(unique_values) <- groups
possible_combinations <- do.call(data.table::CJ, unique_values)
if (nrow(access) < nrow(possible_combinations)) {
access <- do_fill_missing_ids(
access,
possible_combinations,
groups = groups,
access_col = "cost",
fill_value = NA
)
}
}
data.table::setnames(access, c("from_id", "cost"), c("id", travel_cost))
data.table::setcolorder(
access,
c("id", setdiff(groups, "from_id"), travel_cost)
)
data.table::setorderv(access, c("id", setdiff(groups, "from_id")))
if (exists("original_class")) class(access) <- original_class
return(access[])
}
calc_cum_cutoff <- function(data, groups, opportunity, travel_cost, .cutoff) {
env <- environment()
.opportunity_colname <- opportunity
.cost_colname <- travel_cost
data <- data[get(.cost_colname) <= .cutoff]
cum_cutoff_access <- data[
,
.(access = sum(get(.opportunity_colname))),
by = eval(groups, envir = env)
]
return(cum_cutoff_access)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.