Skip to content

Commit

Permalink
Add vctrs methods (#247)
Browse files Browse the repository at this point in the history
* move to dynamic S3 registration onLoad

* Add self-to-self coercion methods

* Add proxy and restore for units vectors

* Add integration test with dplyr

Co-authored-by: Iñaki Úcar <iucar@fedoraproject.org>
  • Loading branch information
lionel- and Enchufa2 authored Jun 11, 2020
1 parent 79a0d88 commit 9683538
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 102 deletions.
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Imports:
LinkingTo:
Rcpp (>= 0.12.10)
Suggests:
dplyr (>= 1.0.0),
udunits2,
NISTunits,
measurements,
Expand All @@ -24,7 +25,8 @@ Suggests:
testthat,
ggforce,
rmarkdown,
magrittr
magrittr,
vctrs (>= 0.3.1)
VignetteBuilder: knitr
Description: Support for measurement units in R vectors, matrices
and arrays: automatic propagation, conversion, derivation
Expand Down
13 changes: 0 additions & 13 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,6 @@ export(unitless)
export(units_options)
export(valid_udunits)
export(valid_udunits_prefixes)
if(getRversion() >= "3.6.0") {
S3method(pillar::type_sum, units)
S3method(pillar::type_sum, mixed_units)
S3method(pillar::pillar_shaft, units)
S3method(pillar::pillar_shaft, mixed_units)
S3method(pillar::format_type_sum, type_sum_units)
} else {
export(type_sum.units)
export(type_sum.mixed_units)
export(pillar_shaft.units)
export(pillar_shaft.mixed_units)
export(format_type_sum.type_sum_units)
}
import(graphics)
import(stats)
import(utils)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* fix replacement operation for `units` objects; #233 addressing #232

* fix compatibility with dplyr 1.0; #247 addressing #239

# version 0.6-6

* prettier `str` print for units and mixed units; #228 addressing #227
Expand Down
2 changes: 2 additions & 0 deletions R/init.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ NULL
else if (l10n_info()[["Latin-1"]]) "latin1"
else "ascii"
ud_set_encoding(native)

register_all_s3_methods()
}

.onAttach <- function(libname, pkgname) {
Expand Down
42 changes: 0 additions & 42 deletions R/misc.R
Original file line number Diff line number Diff line change
Expand Up @@ -114,48 +114,6 @@ seq.units = function(from, to, by = ((to - from)/(length.out - 1)),
set_units(NextMethod(), uuu, mode = "standard")
}

#' type_sum function for units
#' @name tibble
#' @param x see \link[pillar]{type_sum}
#' @param ... see \link[pillar]{type_sum}
#' @rawNamespace if(getRversion() >= "3.6.0") {
#' S3method(pillar::type_sum, units)
#' S3method(pillar::type_sum, mixed_units)
#' S3method(pillar::pillar_shaft, units)
#' S3method(pillar::pillar_shaft, mixed_units)
#' S3method(pillar::format_type_sum, type_sum_units)
#' } else {
#' export(type_sum.units)
#' export(type_sum.mixed_units)
#' export(pillar_shaft.units)
#' export(pillar_shaft.mixed_units)
#' export(format_type_sum.type_sum_units)
#' }
type_sum.units <- function(x, ...) {
gr = units_options("group")
# see https://github.com/r-lib/pillar/issues/73 : currently the [ and ] mess up things.
structure(paste0(gr[1], as.character(units(x)), gr[2]),
class = "type_sum_units")
}
#' @name tibble
#' @param width ignored
format_type_sum.type_sum_units <- function(x, width, ...) {
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
pillar::style_subtle(x)
}

#' pillar_shaft function for units
#' @name tibble
pillar_shaft.units <- function(x, ...) {
u_char <- as.character(units(x))
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
#out <- paste(format(unclass(x), ...), pillar::style_subtle(u_char))
out <- format(unclass(x), ...)
pillar::new_pillar_shaft_simple(out, align = "right", min_width = 8)
}

#' @export
str.units = function(object, ...) {
gr <- units_options("group")
Expand Down
13 changes: 0 additions & 13 deletions R/mixed.R
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,3 @@ Ops.mixed_units = function(e1, e2) {
ret = .as.mixed_units(ret)
ret
}

#' @name tibble
type_sum.mixed_units <- function(x, ...) {
"mixed_units"
}

#' @name tibble
pillar_shaft.mixed_units <- function(x, ...) {
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
out <- format(x, ...)
pillar::new_pillar_shaft_simple(out, align = "right", min_width = 6)
}
115 changes: 115 additions & 0 deletions R/tidyverse.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
type_sum.units <- function(x, ...) {
gr = units_options("group")
# see https://github.com/r-lib/pillar/issues/73 : currently the [ and ] mess up things.
structure(paste0(gr[1], as.character(units(x)), gr[2]),
class = "type_sum_units")
}

type_sum.mixed_units <- function(x, ...) {
"mixed_units"
}

pillar_shaft.units <- function(x, ...) {
u_char <- as.character(units(x))
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
#out <- paste(format(unclass(x), ...), pillar::style_subtle(u_char))
out <- format(unclass(x), ...)
pillar::new_pillar_shaft_simple(out, align = "right", min_width = 8)
}

pillar_shaft.mixed_units <- function(x, ...) {
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
out <- format(x, ...)
pillar::new_pillar_shaft_simple(out, align = "right", min_width = 6)
}

format_type_sum.type_sum_units <- function(x, width, ...) {
if (! requireNamespace("pillar", quietly = TRUE))
stop("package pillar not available: install first?")
pillar::style_subtle(x)
}


# vctrs proxying and restoration -------------------------------------

vec_proxy.units = function(x, ...) {
x
}
vec_restore.units = function(x, to, ...) {
set_units(x, units(to), mode = "standard")
}


# vctrs coercion -----------------------------------------------------

vec_ptype2.units.units = function(x, y, ..., x_arg = "", y_arg = "") {
x_units = units(x)
y_units = units(y)

if (!ud_are_convertible(x_units, y_units))
vctrs::stop_incompatible_type(x, y, x_arg = x_arg, y_arg = y_arg)

x_bare = drop_units(x)
y_bare = drop_units(y)
common = vctrs::vec_ptype2(x_bare, y_bare, ..., x_arg = x_arg, y_arg = y_arg)

# Use left-hand side units
set_units(common, x_units, mode = "standard")
}

vec_cast.units.units = function(x, to, ..., x_arg = "", to_arg = "") {
x_units = units(x)
to_units = units(to)

if (!ud_are_convertible(x_units, to_units))
vctrs::stop_incompatible_cast(x, to, x_arg = x_arg, to_arg = to_arg)

# Convert to target units before converting base type. Unit
# conversion might change the type and so must happen first.
out = set_units(x, to_units, mode = "standard")

out_bare = drop_units(out)
to_bare = drop_units(to)
out = vctrs::vec_cast(out_bare, to_bare, ..., x_arg = x_arg, to_arg = to_arg)

# Set target units again
set_units(out, to_units, mode = "standard")
}


#nocov start
register_all_s3_methods <- function() {
register_s3_method("pillar::type_sum", "units")
register_s3_method("pillar::type_sum", "mixed_units")
register_s3_method("pillar::pillar_shaft", "units")
register_s3_method("pillar::pillar_shaft", "mixed_units")
register_s3_method("pillar::format_type_sum", "type_sum_units")
register_s3_method("vctrs::vec_proxy", "units")
register_s3_method("vctrs::vec_restore", "units")
register_s3_method("vctrs::vec_ptype2", "units.units")
register_s3_method("vctrs::vec_cast", "units.units")
}

register_s3_method <- function(generic, class, fun=NULL) {
stopifnot(is.character(generic), length(generic) == 1)
stopifnot(is.character(class), length(class) == 1)

pieces <- strsplit(generic, "::")[[1]]
stopifnot(length(pieces) == 2)
package <- pieces[[1]]
generic <- pieces[[2]]

if (is.null(fun))
fun <- get(paste0(generic, ".", class), envir=parent.frame())
stopifnot(is.function(fun))

if (package %in% loadedNamespaces())
registerS3method(generic, class, fun, envir=asNamespace(package))

# Always register hook in case package is later unloaded & reloaded
setHook(packageEvent(package, "onLoad"), function(...)
registerS3method(generic, class, fun, envir=asNamespace(package)))
}
# nocov end
33 changes: 0 additions & 33 deletions man/tibble.Rd

This file was deleted.

82 changes: 82 additions & 0 deletions tests/testthat/test_tidyverse.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

skip_if_not_installed("vctrs")

test_that("units have coercion methods", {
x = set_units(1:3, "cm")
y = set_units(4.0, "m")
z = set_units(10, "celsius")

expect_error(vctrs::vec_ptype_common(y, x, z), class = "vctrs_error_incompatible_type")
expect_error(vctrs::vec_cast_common(y, x, z), class = "vctrs_error_incompatible_type")

expect_identical(vctrs::vec_ptype_common(x, y, x), set_units(double(), "cm"))
expect_identical(vctrs::vec_ptype_common(x, x), set_units(integer(), "cm"))
expect_identical(vctrs::vec_ptype_common(y, x, x), set_units(double(), "m"))

expect_identical(
vctrs::vec_cast_common(x, y),
list(set_units(c(1, 2, 3), "cm"), set_units(400, "cm"))
)
expect_identical(
vctrs::vec_cast_common(y, x),
list(set_units(4, "m"), set_units(c(0.01, 0.02, 0.03), "m"))
)

# Casting to integer with fractional cm is lossy
expect_error(
vctrs::vec_cast_common(y, x, .to = set_units(0L, "m")),
class = "vctrs_error_cast_lossy"
)
})

test_that("can combine units vectors", {
x <- set_units(1:3, "cm")
y <- set_units(4, "m")

exp = set_units(c(1, 2, 3, 400), "cm")
expect_identical(vctrs::vec_c(x, y), exp)

# Recursive case
df1 = tibble::tibble(x = tibble::tibble(x = x))
df2 = tibble::tibble(x = tibble::tibble(x = y))
df_exp = tibble::tibble(x = tibble::tibble(x = exp))
expect_identical(vctrs::vec_c(df1, df2), df_exp)
})

test_that("can slice units vectors", {
x = set_units(1:3, "cm")
exp = list(set_units(1L, "cm"), set_units(2L, "cm"), set_units(3L, "cm"))
expect_identical(vctrs::vec_chop(x), exp)

# Recursive case
df = tibble::tibble(tibble::tibble(x = x))
exp = list(
tibble::tibble(x = set_units(1L, "cm")),
tibble::tibble(x = set_units(2L, "cm")),
tibble::tibble(x = set_units(3L, "cm"))
)
expect_identical(vctrs::vec_chop(df), exp)
})


skip_if_not_installed("dplyr")

`%>%` <- dplyr::`%>%`

test_that("split-apply-combine with dplyr and base agree", {
iris2 <- iris
for (i in 1:4)
units(iris2[,i]) <- "cm"

out <- iris2 %>%
dplyr::group_by(Species) %>%
dplyr::summarise(dplyr::across(where(is.numeric), mean))

# Transform to list of lists
out <- vctrs::vec_chop(out[2:5]) %>%
stats::setNames(out$Species) %>%
lapply(as.list)

exp <- lapply(split(iris2[1:4], iris2$Species), lapply, mean)
expect_equal(out, exp)
})

0 comments on commit 9683538

Please sign in to comment.