Skip to content

Instantly share code, notes, and snippets.

@krlmlr
Last active September 28, 2025 18:31
Show Gist options
  • Select an option

  • Save krlmlr/2b3a72e1bda91b06bce8238319350b43 to your computer and use it in GitHub Desktop.

Select an option

Save krlmlr/2b3a72e1bda91b06bce8238319350b43 to your computer and use it in GitHub Desktop.
Ensure type stability for an R data frame
library(conflicted)
library(rlang)
library(vctrs)
library(dplyr)
ensure_type <- function(.data, ..., .default = NULL, .call = caller_env()) {
try_fetch(
try_ensure_type(.data, ..., .default = {{ .default }}),
error = function(e) {
cli::cli_abort(
call = .call,
"Type stability violated",
parent = e
)
}
)
}
try_ensure_type <- function(.data, ..., .default = NULL, .call = caller_env()) {
default <- enquo(.default)
types <- vctrs::data_frame(...)
names <- names(types)
if (!quo_is_null(default)) {
missing <- setdiff(colnames(.data), names)
names <- c(names, missing)
types[missing] <- rlang::eval_tidy(.default)
}
if (!all(names %in% names(.data))) {
cli::cli_abort(call = .call, c(
"Columns missing",
i = "Columns {.var {setdiff(names, names(.data))}} not found in data"
))
}
out <- .data[names]
for (i in seq_along(out)) {
out[[i]] <- vec_cast(out[[i]], types[[i]], x_arg = names[[i]], call = .call)
}
out |>
as_tibble()
}
test1 <- function() {
data.frame(a = 1:3, b = c(4, 4, 5)) |>
ensure_type(b = integer(), c = character(), .default = numeric())
}
test2 <- function() {
data.frame(a = 1:3, b = c(4, 4, 5)) |>
ensure_type(b = character(), .default = numeric())
}
test3 <- function() {
data.frame(a = 1:3, b = c(4, 4, 5)) |>
ensure_type(b = integer(), .default = numeric())
}
test1()
#> Error in `test1()`:
#> ! Type stability violated
#> Caused by error in `ensure_type()`:
#> ! Columns missing
#> ℹ Columns `c` not found in data
test2()
#> Error in `test2()`:
#> ! Type stability violated
#> Caused by error in `ensure_type()`:
#> ! Can't convert `b` <double> to <character>.
test3()
#> # A tibble: 3 × 2
#> b a
#> <int> <dbl>
#> 1 4 1
#> 2 4 2
#> 3 5 3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment