Nothing
#' Cumulative access based on a travel cost cutoff
#'
#' Calculates the number of opportunities accessible under a given specified
#' travel cost cutoff.
#' @template description_generic_cost
#'
#' @template travel_matrix
#' @template land_use_data
#' @template opportunity
#' @param travel_cost A `character` vector. The name of the columns in
#' `travel_matrix` with the travel costs between origins and destinations to
#' be considered in the calculation.
#' @param cutoff Either a `numeric` vector or a list of `numeric` vectors, one
#' for each cost specified in `travel_cost`. The travel cost cutoffs to
#' consider when calculating accessibility levels. If a list, the function
#' finds every single possible cutoff combination and use them to calculate
#' accessibility (e.g. if one specifies that travel time cutoffs should be 30
#' and 60 minutes and that monetary cost cutoffs should be 5 and 10 dollars,
#' the output includes accessibility estimates limited at 30 min & 5 dollars,
#' 30 min & 10 dollars, 60 min & 5 dollars and 60 min & 10 dollars). In these
#' cases, cost constraints are considered simultaneously - i.e. only trips
#' that take 30 minutes or less AND 5 dollars or less to be completed, for
#' example, are included in the accessibility output. The cutoff parameter is
#' not included in the final output if the input includes only a single cutoff
#' for a single travel cost.
#' @template group_by
#' @template active
#' @param fill_missing_ids A `logical`. Calculating cumulative accessibility may
#' result in missing ids if the they cannot reach any of the destinations
#' within the specified travel cost cutoff. For example, using a travel time
#' cutoff of 20 minutes, when estimating the accessibility of origin `A` that
#' can only reach destinations with more than 40 minutes results in id `A`
#' not being included in the output. When `TRUE` (the default), the function
#' identifies which origins would be left out and fills their respective
#' accessibility values with 0, which incurs in a performance penalty.
#'
#' @template return_accessibility
#'
#' @family cumulative access
#'
#' @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"))
#'
#' # active accessibility: number of schools accessible from each origin
#' df <- cumulative_cutoff(
#' travel_matrix = travel_matrix,
#' land_use_data = land_use_data,
#' cutoff = 30,
#' opportunity = "schools",
#' travel_cost = "travel_time"
#' )
#' head(df)
#'
#' df <- cumulative_cutoff(
#' travel_matrix = travel_matrix,
#' land_use_data = land_use_data,
#' cutoff = c(30, 60),
#' opportunity = "schools",
#' travel_cost = "travel_time"
#' )
#' head(df)
#'
#' # passive accessibility: number of people that can reach each destination
#' df <- cumulative_cutoff(
#' travel_matrix = travel_matrix,
#' land_use_data = land_use_data,
#' cutoff = 30,
#' opportunity = "population",
#' travel_cost = "travel_time",
#' active = FALSE
#' )
#' head(df)
#'
#' # using multiple travel costs
#' pareto_frontier <- readRDS(file.path(data_dir, "pareto_frontier.rds"))
#'
#' df <- cumulative_cutoff(
#' pareto_frontier,
#' land_use_data = land_use_data,
#' opportunity = "jobs",
#' travel_cost = c("travel_time", "monetary_cost"),
#' cutoff = list(c(20, 30), c(0, 5, 10))
#' )
#' head(df)
#'
#' @export
cumulative_cutoff <- function(travel_matrix,
land_use_data,
opportunity,
travel_cost,
cutoff,
group_by = character(0),
active = TRUE,
fill_missing_ids = TRUE) {
checkmate::assert_string(opportunity)
checkmate::assert_character(
travel_cost,
any.missing = FALSE,
min.len = 1,
unique = TRUE
)
checkmate::assert_logical(active, len = 1, any.missing = FALSE)
checkmate::assert_logical(fill_missing_ids, len = 1, any.missing = FALSE)
assert_cutoff(cutoff, travel_cost)
assert_group_by(group_by)
assert_travel_matrix(travel_matrix, travel_cost, group_by)
assert_land_use_data(
land_use_data,
travel_matrix,
opportunity,
active = active
)
# 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 = ifelse(active, "to_id", "from_id")
)
group_id <- ifelse(active, "from_id", "to_id")
groups <- c(group_id, group_by)
warn_extra_cols(travel_matrix, travel_cost, group_id, groups)
cutoff_list <- if (is.list(cutoff)) {
cutoff
} else {
list(cutoff)
}
names(cutoff_list) <- travel_cost
cutoff_df <- do.call(data.table::CJ, cutoff_list)
env <- environment()
.opportunity_colname <- opportunity
access <- do.call(
mapply,
args = c(
as.list(cutoff_df),
SIMPLIFY = FALSE,
FUN = function(...) {
cutoff_args <- unlist(list(...))
subset_expr <- paste(
names(cutoff_args),
cutoff_args,
sep = " <= ",
collapse = " & "
)
subset_expr <- parse(text = subset_expr)
cum_opps <- data[eval(subset_expr)]
if (length(travel_cost) > 1) {
.od_group <- c(setdiff(groups, group_id), "from_id", "to_id")
cum_opps <- cum_opps[cum_opps[, .I[1], by = .od_group]$V1]
}
cum_opps <- cum_opps[
,
.(access = sum(get(.opportunity_colname))),
by = eval(groups, envir = env)
]
cutoff_id_expr <- paste0(
"`:=`(",
paste(
names(cutoff_args),
cutoff_args,
sep = " = ",
collapse = ", "
),
")"
)
cutoff_id_expr <- parse(text = cutoff_id_expr)
cum_opps[, eval(cutoff_id_expr)]
}
)
)
access <- data.table::rbindlist(access)
if (fill_missing_ids) {
unique_values <- lapply(groups, function(x) unique(travel_matrix[[x]]))
names(unique_values) <- groups
unique_values <- append(unique_values, cutoff_list)
possible_combinations <- do.call(data.table::CJ, unique_values)
if (nrow(access) < nrow(possible_combinations)) {
access <- do_fill_missing_ids(
access,
possible_combinations,
groups = c(groups, travel_cost)
)
}
}
data.table::setnames(access, c(group_id, "access"), c("id", opportunity))
data.table::setcolorder(
access,
c("id", setdiff(groups, group_id), travel_cost, opportunity)
)
data.table::setorderv(access, c("id", setdiff(groups, group_id), travel_cost))
env <- environment()
if (length(travel_cost) == 1 && length(cutoff) == 1) {
access[, get("travel_cost", envir = env) := NULL]
}
if (exists("original_class")) class(access) <- original_class
return(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.