Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vctrs methods #247

Merged
merged 5 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})