diff --git a/NAMESPACE b/NAMESPACE index e75443c..e3d56b4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,6 +37,7 @@ S3method(as_units,units) S3method(boxplot,units) S3method(c,mixed_units) S3method(c,units) +S3method(cbind,units) S3method(diff,units) S3method(drop_units,data.frame) S3method(drop_units,mixed_units) @@ -55,6 +56,7 @@ S3method(plot,units) S3method(print,mixed_units) S3method(print,units) S3method(quantile,units) +S3method(rbind,units) S3method(rep,units) S3method(seq,units) S3method(set_units,logical) diff --git a/NEWS.md b/NEWS.md index 8e6f0a2..6026df1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # version devel +* Add methods for `cbind` and `rbind`; fixes #311 + * Performance improvements in `data.frame` methods; suggested in #361 @grcatlin * Fix `weighted.mean.units` for unitless objects; #363 diff --git a/R/misc.R b/R/misc.R index 461a19f..b3dba74 100644 --- a/R/misc.R +++ b/R/misc.R @@ -136,3 +136,45 @@ unique.units <- function(x, incomparables = FALSE, ...) { NextMethod() else unique.array(x, incomparables, ...) .as.units(xx, units(x)) } + +#' Combine R Objects by Rows or Columns +#' +#' S3 methods for \code{units} objects (see \code{\link[base]{cbind}}). +#' +#' @inheritParams base::cbind +#' @name cbind.units +#' +#' @examples +#' x <- set_units(1, m/s) +#' y <- set_units(1:3, m/s) +#' z <- set_units(8:10, m/s) +#' (m <- cbind(x, y)) # the '1' (= shorter vector) is recycled +#' (m <- cbind(m, z)[, c(1, 3, 2)]) # insert a column +#' (m <- rbind(m, z)) # insert a row +#' @export +cbind.units <- function(..., deparse.level = 1) { + dots <- list(...) + units_first_arg <- units(dots[[1]]) + class_first_arg <- class(dots[[1]]) + dots <- lapply(dots, function(x) { + dots_unified <- set_units(x, units_first_arg, mode = "standard") + ret <- drop_units(dots_unified) + return(ret) + }) + + nm <- names(as.list(match.call())) + nm <- nm[nm != "" & nm != "deparse.level"] + if (is.null(nm)) + names(dots) <- sapply(substitute(list(...))[-1], deparse) + else names(dots) <- nm + + call <- as.character(match.call()[[1]]) + value <- do.call(call, c(dots, deparse.level=deparse.level)) + attr(value, "units") <- units_first_arg + class(value) <- class_first_arg + return(value) +} + +#' @rdname cbind.units +#' @export +rbind.units <- cbind.units diff --git a/man/cbind.units.Rd b/man/cbind.units.Rd new file mode 100644 index 0000000..35b1115 --- /dev/null +++ b/man/cbind.units.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/misc.R +\name{cbind.units} +\alias{cbind.units} +\alias{rbind.units} +\title{Combine R Objects by Rows or Columns} +\usage{ +\method{cbind}{units}(..., deparse.level = 1) + +\method{rbind}{units}(..., deparse.level = 1) +} +\arguments{ +\item{...}{(generalized) vectors or matrices. These can be given as named + arguments. Other \R objects may be coerced as appropriate, or S4 + methods may be used: see sections \sQuote{Details} and + \sQuote{Value}. (For the \code{"data.frame"} method of \code{cbind} + these can be further arguments to \code{\link[base]{data.frame}} such as + \code{stringsAsFactors}.)} + +\item{deparse.level}{integer controlling the construction of labels in + the case of non-matrix-like arguments (for the default method):\cr + \code{deparse.level = 0} constructs no labels;\cr + the default \code{deparse.level = 1} typically and + \code{deparse.level = 2} always construct labels from the argument + names, see the \sQuote{Value} section below.} +} +\description{ +S3 methods for \code{units} objects (see \code{\link[base]{cbind}}). +} +\examples{ +x <- set_units(1, m/s) +y <- set_units(1:3, m/s) +z <- set_units(8:10, m/s) +(m <- cbind(x, y)) # the '1' (= shorter vector) is recycled +(m <- cbind(m, z)[, c(1, 3, 2)]) # insert a column +(m <- rbind(m, z)) # insert a row +} diff --git a/tests/testthat/test_misc.R b/tests/testthat/test_misc.R index 6a14a42..6726022 100644 --- a/tests/testthat/test_misc.R +++ b/tests/testthat/test_misc.R @@ -157,3 +157,35 @@ test_that("duplicated-related methods work as expected", { expect_equal(anyDuplicated(x), anyDuplicated(drop_units(x))) expect_equal(unique(x), x[1, , drop=FALSE]) }) + +test_that("bind methods work properly", { + a <- set_units(1:10, m) + b <- set_units((1:10) * 0.001, km) + + x <- rbind(x=a, y=a) + y <- rbind(x=a, y=b) + expect_equal(as.numeric(x), as.numeric(y)) + expect_equal(rownames(x), c("x", "y")) + expect_equal(rownames(y), c("x", "y")) + x <- rbind(rbind(a, a), a) + y <- rbind(b, rbind(b, b)) + expect_equal(as.numeric(x), as.numeric(y) * 1000) + expect_equal(rownames(x), c("a", "a", "a")) + expect_equal(rownames(y), c("b", "b", "b")) + + x <- cbind(x=a, y=a) + y <- cbind(x=a, y=b) + expect_equal(as.numeric(x), as.numeric(y)) + expect_equal(colnames(x), c("x", "y")) + expect_equal(colnames(y), c("x", "y")) + x <- cbind(cbind(a, a), a) + y <- cbind(b, cbind(b, b)) + expect_equal(as.numeric(x), as.numeric(y) * 1000) + expect_equal(colnames(x), c("a", "a", "a")) + expect_equal(colnames(y), c("b", "b", "b")) + + z <- cbind( + rbind(a, b), + rbind(x = a, y = b)) + expect_equal(dimnames(z), list(c("a", "b"), NULL)) +})