diff --git a/R/arith.R b/R/arith.R index d3614998..a412e0e6 100644 --- a/R/arith.R +++ b/R/arith.R @@ -5,7 +5,17 @@ #' S3 Ops Group Generic Functions for units objects #' -#' Ops functions for units objects, including comparison, product and divide, add, subtract +#' Ops functions for units objects, including comparison, product and divide, +#' add, subtract. +#' +#' Users are advised against performing arithmetical operations with +#' temperatures in different units. The \pkg{units} package ensure that results +#' 1) are arithmetically correct, and 2) satisfy dimensional analysis, but could +#' never ensure that results are physically meaningful. Temperature units are +#' special because there is an absolute unit, Kelvin, and relative ones, Celsius +#' and Fahrenheit degrees. Arithmetic operations between them are meaningless +#' from the physical standpoint. Users are thus advised to convert all +#' temperatures to Kelvin before operating. #' #' @param e1 object of class \code{units}, #' or something that can be coerced to it by \code{as_units(e1)} diff --git a/R/conversion.R b/R/conversion.R index 77276a80..b49f9c28 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -1,10 +1,33 @@ -#' Set measurement units on a numeric vector +#' Handle measurement units #' -#' @param x numeric vector, or object of class \code{units} -#' @param value object of class \code{units} or \code{symbolic_units}, or in the case of \code{set_units} expression with symbols that can be resolved in \link{ud_units} (see examples). +#' A number of functions are provided for handling unit objects. +#' \itemize{ +#' \item \code{`units<-`} and \code{units} are the basic functions to set +#' and retrieve units. +#' \item \code{as_units}, a generic with methods for a +#' character string and for quoted language. Note, direct usage of this function +#' by users is typically not necessary, as coercion via \code{as_units} is +#' automatically done with \code{`units<-`} and \code{set_units}. +#' \item \code{make_units}, constructs units from bare expressions. +#' \code{make_units(m/s)} is equivalent to \code{as_units(quote(m/s))}. +#' \item \code{set_units}, a pipe-friendly version of \code{`units<-`}. By +#' default it operates with bare expressions like \code{make_unit}, but this +#' behavior can be disabled by a specifying \code{mode = "standard"} or setting +#' \code{units_options(set_units_mode = "standard")}. +#' } +#' +#' @param x numeric vector, or object of class \code{units}. +#' @param value object of class \code{units} or \code{symbolic_units}, or in the +#' case of \code{set_units} expression with symbols that can be resolved in +#' \link{ud_units} (see examples). +#' +#' @return An object of class \code{units}. +#' +#' @details +#' If \code{value} is of class \code{units} and has a value unequal to 1, this +#' value is ignored unless \code{units_options("simplifiy")} is \code{TRUE}. If +#' \code{simplify} is \code{TRUE}, \code{x} is multiplied by this value. #' -#' @return object of class \code{units} -#' @details if \code{value} is of class \code{units} and has a value unequal to 1, this value is ignored unless \code{units_options("simplifiy")} is \code{TRUE}. If \code{simplify} is \code{TRUE}, \code{x} is multiplied by this value. #' @export #' @name units #' @@ -36,8 +59,6 @@ x } -#' Convert units -#' #' @name units #' @export #' @@ -95,88 +116,21 @@ x } -#' retrieve measurement units from \code{units} object +#' @return The \code{units} method retrieves the units attribute, which is of +#' class \code{symbolic_units}. #' -#' @export #' @name units -#' @return the units method retrieves the units attribute, which is of class \code{symbolic_units} +#' @export units.units <- function(x) { attr(x, "units") } +#' @name units #' @export units.symbolic_units <- function(x) { x } -#' convert object to a units object -#' -#' @param x object of class units -#' @param value an object of class units, or something coercible to one with -#' \code{as_units} -#' @param ... passed on to other methods -#' -#' @export -as_units <- function(x, ...) { - UseMethod("as_units") -} - -#' @export -as_units.units <- function(x, value, ...) { - if(!missing(value) && !identical(units(value), units(x))) - warning("Use set_units() to perform unit conversion. Return unit unmodified") - x -} -#' @export -as_units.symbolic_units <- function(x, value, ...) { - if(!missing(value)) - warning("supplied value ignored") - .as.units(1L, x) -} - -#' @export -#' @name as_units -as_units.default <- function(x, value = unitless, ...) { - if (is.null(x)) return(x) - units(x) <- value - x -} - -#' difftime objects to units -#' -#' @export -#' @name as_units -#' -#' @examples -#' s = Sys.time() -#' d = s - (s+1) -#' as_units(d) -as_units.difftime <- function(x, value, ...) { - u <- attr(x, "units") - x <- unclass(x) - attr(x, "units") <- NULL - - # convert from difftime to udunits2: - if (u == "secs") # secs -> s - x <- x * symbolic_unit("s") - else if (u == "mins") # mins -> min - x <- x * symbolic_unit("min") - else if (u == "hours") # hours -> h - x <- x * symbolic_unit("h") - else if (u == "days") # days -> d - x <- x * symbolic_unit("d") - else if (u == "weeks") { # weeks -> 7 days - x <- 7 * x - x <- x * symbolic_unit("d") - } else - stop(paste("unknown time units", u, "in difftime object")) - - if (!missing(value)) # convert optionally: - units(x) <- value - - x -} - #' @export as.data.frame.units <- function(x, row.names = NULL, optional = FALSE, ...) { df = as.data.frame(unclass(x), row.names, optional, ...) @@ -260,20 +214,90 @@ as.Date.units = function (x, ...) { as.Date(as.numeric(x), origin = as.Date("1970-01-01 00:00:00")) } +#' @param ... passed on to other methods. +#' @param mode if \code{"symbols"} (the default), then unit is constructed from +#' the expression supplied. Otherwise, if\code{mode = "standard"}, +#' standard evaluation is used for the supplied value This argument can be set +#' via a global option \code{units_options(set_units_mode = "standard")} +#' +#' @name units +#' @export +set_units <- function(x, value, ..., mode = units_options("set_units_mode")) + UseMethod("set_units") + #' @export -as_units.POSIXt = function(x, value, ...) { - u = as.numeric(as.POSIXct(x)) - units(u) = symbolic_unit("seconds since 1970-01-01 00:00:00 +00:00") - if (! missing(value)) - units(u) = symbolic_unit(value) - u +set_units.numeric <- function(x, value, ..., mode = units_options("set_units_mode")) { + if (missing(value)) + value <- unitless + else if (mode == "symbols") { + value <- substitute(value) + + if(is.numeric(value) && !identical(value, 1) && !identical(value, 1L)) + stop("The only valid number defining a unit is '1', signifying a unitless unit") + } + + units(x) <- as_units(value, ...) + x } #' @export -as_units.Date = function(x, value, ...) { - u = as.numeric(x) - units(u) = symbolic_unit("days since 1970-01-01") - if (!missing(value)) - units(u) = symbolic_unit(value) - u +set_units.logical <- set_units.numeric + +#' @export +set_units.units <- set_units.numeric + +#' Drop Units +#' +#' Drop units attribute and class. +#' +#' @param x an object with units metadata. +#' +#' @return the numeric without any units attributes, while preserving other +#' attributes like dimensions or other classes. +#' +#' @details Equivalent to \code{units(x) <- NULL}, or the pipe-friendly version +#' \code{set_units(x, NULL)}, but \code{drop_units} will fail if the object has +#' no units metadata. Use the alternatives if you want this operation to succeed +#' regardless of the object type. +#' +#' A \code{data.frame} method is also provided, which checks every column and +#' drops units if any. +#' +#' @export +#' @examples +#' x <- 1 +#' y <- set_units(x, m/s) +#' +#' # this succeeds +#' drop_units(y) +#' set_units(y, NULL) +#' set_units(x, NULL) +#' +#' \dontrun{ +#' # this fails +#' drop_units(x) +#' } +#' +#' df <- data.frame(x=x, y=y) +#' df +#' drop_units(df) +#' +drop_units <- function(x) UseMethod("drop_units") + +#' @name drop_units +#' @export +drop_units.units <- function(x) { + class(x) <- setdiff(class(x), "units") + attr(x, "units") <- NULL + x +} + +#' @name drop_units +#' @export +drop_units.data.frame <- function(x) { + for (i in seq_along(x)) { + if (inherits(x[[i]], "units")) + x[[i]] <- drop_units(x[[i]]) + } + x } diff --git a/R/make_units.R b/R/make_units.R index 4d6107a7..04dd17e5 100644 --- a/R/make_units.R +++ b/R/make_units.R @@ -6,31 +6,12 @@ structure(x, units = value, dim = dim, class = "units") } -#' Unit creation -#' -#' A number of functions are provided for creating unit objects. -#' \itemize{ -#' \item \code{as_units}, a generic with methods for a -#' character string and for quoted language. Note, direct usage of this function -#' by users is typically not necessary, as coercion via \code{as_units} is -#' automatically done with \code{`units<-`} and \code{set_units()}. -#' -#' \item \code{make_units()}, constructs units from bare expressions. -#' \code{make_units(m/s)} is equivalent to \code{as_units(quote(m/s))} -#' -#' \item \code{set_units()}, a pipe_friendly version of \code{`units<-`}. By -#' default it operates with bare expressions like \code{make_unit}, but this -#' behavior can be disabled by a specifying \code{mode = "standard"} or setting -#' \code{units_options(set_units_mode = "standard")}. -#' } -#' +#' @name units #' @export -#' @rdname as_units #' #' @param bare_expression a bare R expression describing units. Must be valid R #' syntax (reserved R syntax words like \code{in} must be backticked) #' -#' @noMd #' @examples #' # The easiest way to assign units to a numeric vector is like this: #' x <- y <- 1:4 @@ -149,6 +130,68 @@ make_units <- function(bare_expression, check_is_valid = TRUE) { as_units.call(substitute(bare_expression), check_is_valid = check_is_valid) } +#' @name units +#' @export +as_units <- function(x, ...) { + UseMethod("as_units") +} + +#' @name units +#' @export +as_units.default <- function(x, value = unitless, ...) { + if (is.null(x)) return(x) + units(x) <- value + x +} + +#' @name units +#' @export +as_units.units <- function(x, value, ...) { + if(!missing(value) && !identical(units(value), units(x))) + warning("Use set_units() to perform unit conversion. Return unit unmodified") + x +} + +#' @name units +#' @export +as_units.symbolic_units <- function(x, value, ...) { + if(!missing(value)) + warning("supplied value ignored") + .as.units(1L, x) +} + +#' @examples +#' s = Sys.time() +#' d = s - (s+1) +#' as_units(d) +#' +#' @name units +#' @export +as_units.difftime <- function(x, value, ...) { + u <- attr(x, "units") + x <- unclass(x) + attr(x, "units") <- NULL + + # convert from difftime to udunits2: + if (u == "secs") # secs -> s + x <- x * symbolic_unit("s") + else if (u == "mins") # mins -> min + x <- x * symbolic_unit("min") + else if (u == "hours") # hours -> h + x <- x * symbolic_unit("h") + else if (u == "days") # days -> d + x <- x * symbolic_unit("d") + else if (u == "weeks") { # weeks -> 7 days + x <- 7 * x + x <- x * symbolic_unit("d") + } else + stop(paste("unknown time units", u, "in difftime object")) + + if (!missing(value)) # convert optionally: + units(x) <- value + + x +} # ----- as_units.character helpers ------ @@ -170,10 +213,8 @@ is_udunits_time <- function(s) { ud_is_parseable(s) && ud_are_convertible(s, "seconds since 1970-01-01") } - -#' @rdname as_units +#' @name units #' @export -#' @noMd #' #' @param force_single_symbol Whether to perform no string parsing and force #' treatment of the string as a single symbol. @@ -184,7 +225,7 @@ is_udunits_time <- function(s) { #' incorrect. #' #' @section Character strings: -#' +#' #' Generally speaking, there are 3 types of unit strings are accepted in #' \code{as_units} (and by extension, \code{`units<-`}). #' @@ -217,6 +258,12 @@ is_udunits_time <- function(s) { #' string, and unit symbol or names must be separated by a space. Each unit #' symbol may optionally be followed by a single number, specifying the power. #' For example \code{"m2 s-2"} is equivalent to \code{"(m^2)*(s^-2)"}. +#' +#' It must be noted that prepended numbers are supported too, but their +#' interpretation slightly varies depending on whether they are separated from +#' the unit string or not. E.g., \code{"1000 m"} is interpreted as magnitude +#' and unit, but \code{"1000m"} is interpreted as a prefixed unit, and it is +#' equivalent to \code{"km"} to all effects. #' #' The third type of unit string format accepted is the special case of #' udunits time duration with a reference origin, for example \code{"hours @@ -226,6 +273,7 @@ is_udunits_time <- function(s) { #' users that work with udunits time data, e.g., with NetCDF files. Users are #' otherwise encouraged to use \code{R}'s date and time functionality provided #' by \code{Date} and \code{POSIXt} classes. +#' as_units.character <- function(x, check_is_valid = TRUE, implicit_exponents = NULL, @@ -302,8 +350,8 @@ units_eval_env$lg <- function(x) base::log(x, base = 10) units_eval_env$lb <- function(x) base::log(x, base = 2) +#' @name units #' @export -#' @rdname as_units #' #' @param check_is_valid throw an error if all the unit symbols are not either #' recognized by udunits2 via \code{ud_is_parseable()}, or a custom @@ -324,9 +372,6 @@ units_eval_env$lb <- function(x) base::log(x, base = 2) #' see which symbols and names are currently recognized by the udunits #' database, see \code{udunits_symbols()}. #' -#' @return A new unit object that can be used in arithmetic, unit conversion or -#' unit assignment. -#' #' @seealso \code{\link{valid_udunits}} as_units.call <- function(x, check_is_valid = TRUE, ...) { @@ -372,13 +417,34 @@ See ?as_units for usage examples.") .as.units(as.numeric(unit), units(unit)) } - +#' @name units #' @export as_units.expression <- as_units.call +#' @name units #' @export as_units.name <- as_units.call +#' @name units +#' @export +as_units.POSIXt = function(x, value, ...) { + u = as.numeric(as.POSIXct(x)) + units(u) = symbolic_unit("seconds since 1970-01-01 00:00:00 +00:00") + if (! missing(value)) + units(u) = symbolic_unit(value) + u +} + +#' @name units +#' @export +as_units.Date = function(x, value, ...) { + u = as.numeric(x) + units(u) = symbolic_unit("days since 1970-01-01") + if (!missing(value)) + units(u) = symbolic_unit(value) + u +} + symbolic_unit <- function(chr, check_is_valid = TRUE) { @@ -398,60 +464,3 @@ symbolic_unit <- function(chr, check_is_valid = TRUE) { .as.units(1, .symbolic_units(chr)) } - - -#' Drop Units -#' -#' Drop units attribute and class. -#' -#' @param x an object with units metadata. -#' -#' @return the numeric without any units attributes, while preserving other -#' attributes like dimensions or other classes. -#' -#' @details Equivalent to \code{units(x) <- NULL}, or the pipe-friendly version -#' \code{set_units(x, NULL)}, but \code{drop_units} will fail if the object has -#' no units metadata. Use the alternatives if you want this operation to succeed -#' regardless of the object type. -#' -#' A \code{data.frame} method is also provided, which checks every column and -#' drops units if any. -#' -#' @export -#' @examples -#' x <- 1 -#' y <- set_units(x, m/s) -#' -#' # this succeeds -#' drop_units(y) -#' set_units(y, NULL) -#' set_units(x, NULL) -#' -#' \dontrun{ -#' # this fails -#' drop_units(x) -#' } -#' -#' df <- data.frame(x=x, y=y) -#' df -#' drop_units(df) -#' -drop_units <- function(x) UseMethod("drop_units") - -#' @name drop_units -#' @export -drop_units.units <- function(x) { - class(x) <- setdiff(class(x), "units") - attr(x, "units") <- NULL - x -} - -#' @name drop_units -#' @export -drop_units.data.frame <- function(x) { - for (i in seq_along(x)) { - if (inherits(x[[i]], "units")) - x[[i]] <- drop_units(x[[i]]) - } - x -} diff --git a/R/set_units.R b/R/set_units.R deleted file mode 100644 index 02df0450..00000000 --- a/R/set_units.R +++ /dev/null @@ -1,48 +0,0 @@ - - -#' set_units -#' -#' A pipe friendly version of \code{units<-} -#' -#' @param x a numeric to be assigned units, or a units object to have units -#' converted -#' -#' @param value a \code{units} object, or something coercible to one with -#' \code{as_units}. Depending on \code{mode}, the unit is constructed from the -#' supplied bare expression or from the supplied value via standard -#' evaluation. -#' -#' @param ... passed on to \code{as_units} -#' -#' @param mode if \code{"symbols"} (the default), then unit is constructed from -#' the expression supplied. Otherwise, if\code{mode = "standard"}, -#' standard evaluation is used for the supplied value This argument can be set -#' via a global option \code{units_options(set_units_mode = "standard")} -#' -#' @export -#' @rdname set_units -#' @seealso \code{\link{as_units}} -set_units <- function(x, value, ..., mode = units_options("set_units_mode")) - UseMethod("set_units") - -#' @export -set_units.numeric <- function(x, value, ..., mode = units_options("set_units_mode")) { - - if (missing(value)) - value <- unitless - else if (mode == "symbols") { - value <- substitute(value) - - if(is.numeric(value) && !identical(value, 1) && !identical(value, 1L)) - stop("The only valid number defining a unit is '1', signifying a unitless unit") - } - - units(x) <- as_units(value, ...) - x -} - -#' @export -set_units.logical <- set_units.numeric - -#' @export -set_units.units <- set_units.numeric diff --git a/man/Ops.units.Rd b/man/Ops.units.Rd index 593b1fd4..31874571 100644 --- a/man/Ops.units.Rd +++ b/man/Ops.units.Rd @@ -18,7 +18,18 @@ or in case of power a number (integer n or 1/n)} object of class \code{units} } \description{ -Ops functions for units objects, including comparison, product and divide, add, subtract +Ops functions for units objects, including comparison, product and divide, +add, subtract. +} +\details{ +Users are advised against performing arithmetical operations with +temperatures in different units. The \pkg{units} package ensure that results +1) are arithmetically correct, and 2) satisfy dimensional analysis, but could +never ensure that results are physically meaningful. Temperature units are +special because there is an absolute unit, Kelvin, and relative ones, Celsius +and Fahrenheit degrees. Arithmetic operations between them are meaningless +from the physical standpoint. Users are thus advised to convert all +temperatures to Kelvin before operating. } \examples{ a <- set_units(1:3, m/s) diff --git a/man/as_units.Rd b/man/as_units.Rd deleted file mode 100644 index e2aa074e..00000000 --- a/man/as_units.Rd +++ /dev/null @@ -1,253 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/conversion.R, R/make_units.R -\name{as_units} -\alias{as_units} -\alias{as_units.default} -\alias{as_units.difftime} -\alias{make_units} -\alias{as_units.character} -\alias{as_units.call} -\title{convert object to a units object} -\usage{ -as_units(x, ...) - -\method{as_units}{default}(x, value = unitless, ...) - -\method{as_units}{difftime}(x, value, ...) - -make_units(bare_expression, check_is_valid = TRUE) - -\method{as_units}{character}(x, check_is_valid = TRUE, - implicit_exponents = NULL, force_single_symbol = FALSE, ...) - -\method{as_units}{call}(x, check_is_valid = TRUE, ...) -} -\arguments{ -\item{x}{object of class units} - -\item{...}{passed on to other methods} - -\item{value}{an object of class units, or something coercible to one with -\code{as_units}} - -\item{bare_expression}{a bare R expression describing units. Must be valid R -syntax (reserved R syntax words like \code{in} must be backticked)} - -\item{check_is_valid}{throw an error if all the unit symbols are not either -recognized by udunits2 via \code{ud_is_parseable()}, or a custom -user defined via \code{install_symbolic_unit()}. If \code{FALSE}, no check -for validity is performed.} - -\item{implicit_exponents}{If the unit string is in product power form (e.g. -\code{"km m-2 s-1"}). Defaults to \code{NULL}, in which case a guess is made -based on the supplied string. Set to \code{TRUE} or \code{FALSE} if the guess is -incorrect.} - -\item{force_single_symbol}{Whether to perform no string parsing and force -treatment of the string as a single symbol.} -} -\value{ -A new unit object that can be used in arithmetic, unit conversion or - unit assignment. -} -\description{ -A number of functions are provided for creating unit objects. -\itemize{ - \item \code{as_units}, a generic with methods for a - character string and for quoted language. Note, direct usage of this function - by users is typically not necessary, as coercion via \code{as_units} is - automatically done with \code{`units<-`} and \code{set_units()}. - - \item \code{make_units()}, constructs units from bare expressions. - \code{make_units(m/s)} is equivalent to \code{as_units(quote(m/s))} - - \item \code{set_units()}, a pipe_friendly version of \code{`units<-`}. By - default it operates with bare expressions like \code{make_unit}, but this - behavior can be disabled by a specifying \code{mode = "standard"} or setting - \code{units_options(set_units_mode = "standard")}. -} -} -\note{ -By default, unit names are automatically substituted with unit names - (e.g., kilogram --> kg). To turn off this behavior, set - \code{units_options(auto_convert_names_to_symbols = FALSE)} -} -\section{Character strings}{ - - - Generally speaking, there are 3 types of unit strings are accepted in - \code{as_units} (and by extension, \code{`units<-`}). - - The first, and likely most common, is a "standard" format unit - specification where the relationship between unit symbols or names is - specified explicitly with arithmetic symbols for division \code{/}, - multiplication \code{*} and power exponents \code{^}, or other mathematical - functions like \code{log()}. In this case, the string is parsed as an R - expression via \code{parse(text = )} after backticking all unit symbols and - names, and then passed on to \code{as_units.call()}. A heuristic is used to - perform backticking, such that any continuous set of characters - uninterrupted by one of \code{()\\*^-} are backticked (unless the character - sequence consists solely of numbers \code{0-9}), with some care to not - double up on pre-existing backticks. This heuristic appears to be quite - robust, and works for units would otherwise not be valid R syntax. For - example, percent (\code{"\%"}), feet (\code{"'"}), inches (\code{"in"}), - and Tesla (\code{"T"}) are all backticked and parsed correctly. - - Nevertheless, for certain complex unit expressions, this backticking heuristic - may give incorrect results. If the string supplied fails to parse as an R - expression, then the string is treated as a single symbolic unit and - \code{symbolic_unit(chr)} is used as a fallback with a warning. In that - case, automatic unit simplification may not work properly when performing - operations on unit objects, but unit conversion and other Math operations - should still give correct results so long as the unit string supplied - returns \code{TRUE} for \code{ud_is_parsable()}. - - The second type of unit string accepted is one with implicit exponents. In - this format, \code{/}, \code{*}, and \code{^}, may not be present in the - string, and unit symbol or names must be separated by a space. Each unit - symbol may optionally be followed by a single number, specifying the power. - For example \code{"m2 s-2"} is equivalent to \code{"(m^2)*(s^-2)"}. - - The third type of unit string format accepted is the special case of - udunits time duration with a reference origin, for example \code{"hours - since 1970-01-01 00:00:00"}. Note, that the handling of time and calendar - operations via the udunits library is subtly different from the way R - handles date and time operations. This functionality is mostly exported for - users that work with udunits time data, e.g., with NetCDF files. Users are - otherwise encouraged to use \code{R}'s date and time functionality provided - by \code{Date} and \code{POSIXt} classes. -} - -\section{Expressions}{ - - - In \code{as_units()}, each of the symbols in the unit expression is treated - individually, such that each symbol must be recognized by the udunits - database (checked by \code{ud_is_parseable()}, \emph{or} be a custom, - user-defined unit symbol that was defined either by - \code{install_symbolic_unit()} or \code{install_conversion_constant()}. To - see which symbols and names are currently recognized by the udunits - database, see \code{udunits_symbols()}. -} - -\examples{ -s = Sys.time() -d = s - (s+1) -as_units(d) -# The easiest way to assign units to a numeric vector is like this: -x <- y <- 1:4 -units(x) <- "m/s" # meters / second - -# Alternatively, the easiest pipe-friendly way to set units: -if(requireNamespace("magrittr", quietly = TRUE)) { - library(magrittr) - y \%>\% set_units(m/s) -} - -# these are different ways of creating the same unit: -# meters per second squared, i.e, acceleration -x1 <- make_units(m/s^2) -x2 <- as_units(quote(m/s^2)) -x2 <- as_units("m/s^2") -x3 <- as_units("m s-2") # in product power form, i.e., implicit exponents = T -x4 <- set_units(1, m/s^2) # by default, mode = "symbols" -x5 <- set_units(1, "m/s^2", mode = "standard") -x6 <- set_units(1, x1, mode = "standard") -x7 <- set_units(1, units(x1), mode = "standard") -x8 <- as_units("m") / as_units("s")^2 - -all_identical <- function(...) { - l <- list(...) - for(i in seq_along(l)[-1]) - if(!identical(l[[1]], l[[i]])) - return(FALSE) - TRUE -} -all_identical(x1, x2, x3, x4, x5, x6, x7, x8) - -# Note, direct usage of these unit creation functions is typically not -# necessary, since coercion is automatically done via as_units(). Again, -# these are all equivalent ways to generate the same result. - -x1 <- x2 <- x3 <- x4 <- x5 <- x6 <- x7 <- x8 <- 1:4 -units(x1) <- "m/s^2" -units(x2) <- "m s-2" -units(x3) <- quote(m/s^2) -units(x4) <- make_units(m/s^2) -units(x5) <- as_units(quote(m/s^2)) -x6 <- set_units(x6, m/s^2) -x7 <- set_units(x7, "m/s^2", mode = "standard") -x8 <- set_units(x8, units(x1), mode = "standard") - -all_identical(x1, x2, x3, x4, x5, x6, x7, x8) - - -# Both unit names or symbols can be used. By default, unit names are -# automatically converted to unit symbols. -make_units(degree_C) -make_units(kilogram) -make_units(ohm) -# Note, if the printing of non-ascii characters is garbled, then you may -# need to specify the encoding on your system manually like this: -# ud_set_encoding("latin1") -# not all unit names get converted to symbols under different encodings - -## Arithmetic operations and units -# conversion between unit objects that were defined as symbols and names will -# work correctly, although unit simplification in printing may not always occur. -x <- 500 * make_units(micrograms/liter) -y <- set_units(200, ug/l) -x + y -x * y # numeric result is correct, but units not simplified completely - -# note, plural form of unit name accepted too ('liters' vs 'liter'), and -# denominator simplification can be performed correctly -x * set_units(5, liters) - -# unit conversion works too -set_units(x, grams/gallon) - -## Creating custom, user defined units -# For example, a microbiologist might work with counts of bacterial cells -# make_units(cells/ml) # by default, throws an ERROR -# First define the unit, then the newly defined unit is accepted. -install_symbolic_unit("cells") -make_units(cells/ml) - -# Note, install_symbolic_unit() does not add any support for unit -# conversion, or arithmetic operations that require unit conversion. See -# ?install_conversion_constant for defining relationships between user -# defined units. - -## set_units() -# set_units is a pipe friendly version of `units<-`. -if(requireNamespace("magrittr", quietly = TRUE)) { - library(magrittr) - 1:5 \%>\% set_units(N/m^2) - # first sets to m, then converts to km - 1:5 \%>\% set_units(m) \%>\% set_units(km) -} - -# set_units has two modes of operation. By default, it operates with -# bare symbols to define the units. -set_units(1:5, m/s) - -# use `mode = "standard"` to use the value of supplied argument, rather than -# the bare symbols of the expression. In this mode, set_units() can be -# thought of as a simple alias for `units<-` that is pipe friendly. -set_units(1:5, "m/s", mode = "standard") -set_units(1:5, make_units(m/s), mode = "standard") - -# the mode of set_units() can be controlled via a global option -# units_options(set_units_mode = "standard") - -# To remove units use -units(x) <- NULL -# or -set_units(x, NULL) -# or -drop_units(y) -} -\seealso{ -\code{\link{valid_udunits}} -} diff --git a/man/drop_units.Rd b/man/drop_units.Rd index 7a2b7cc8..fd53545d 100644 --- a/man/drop_units.Rd +++ b/man/drop_units.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_units.R, R/mixed.R +% Please edit documentation in R/conversion.R, R/mixed.R \name{drop_units} \alias{drop_units} \alias{drop_units.units} diff --git a/man/set_units.Rd b/man/set_units.Rd deleted file mode 100644 index 94e5eaef..00000000 --- a/man/set_units.Rd +++ /dev/null @@ -1,30 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/set_units.R -\name{set_units} -\alias{set_units} -\title{set_units} -\usage{ -set_units(x, value, ..., mode = units_options("set_units_mode")) -} -\arguments{ -\item{x}{a numeric to be assigned units, or a units object to have units -converted} - -\item{value}{a \code{units} object, or something coercible to one with -\code{as_units}. Depending on \code{mode}, the unit is constructed from the -supplied bare expression or from the supplied value via standard -evaluation.} - -\item{...}{passed on to \code{as_units}} - -\item{mode}{if \code{"symbols"} (the default), then unit is constructed from -the expression supplied. Otherwise, if\code{mode = "standard"}, -standard evaluation is used for the supplied value This argument can be set -via a global option \code{units_options(set_units_mode = "standard")}} -} -\description{ -A pipe friendly version of \code{units<-} -} -\seealso{ -\code{\link{as_units}} -} diff --git a/man/units.Rd b/man/units.Rd index fba011ef..967eca7c 100644 --- a/man/units.Rd +++ b/man/units.Rd @@ -1,12 +1,26 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/conversion.R +% Please edit documentation in R/conversion.R, R/make_units.R \name{units} \alias{units} \alias{units<-.numeric} \alias{units<-.units} \alias{units<-.logical} \alias{units.units} -\title{Set measurement units on a numeric vector} +\alias{units.symbolic_units} +\alias{set_units} +\alias{make_units} +\alias{as_units} +\alias{as_units.default} +\alias{as_units.units} +\alias{as_units.symbolic_units} +\alias{as_units.difftime} +\alias{as_units.character} +\alias{as_units.call} +\alias{as_units.expression} +\alias{as_units.name} +\alias{as_units.POSIXt} +\alias{as_units.Date} +\title{Handle measurement units} \usage{ \method{units}{numeric}(x) <- value @@ -15,27 +29,163 @@ \method{units}{logical}(x) <- value \method{units}{units}(x) + +\method{units}{symbolic_units}(x) + +set_units(x, value, ..., mode = units_options("set_units_mode")) + +make_units(bare_expression, check_is_valid = TRUE) + +as_units(x, ...) + +\method{as_units}{default}(x, value = unitless, ...) + +\method{as_units}{units}(x, value, ...) + +\method{as_units}{symbolic_units}(x, value, ...) + +\method{as_units}{difftime}(x, value, ...) + +\method{as_units}{character}(x, check_is_valid = TRUE, + implicit_exponents = NULL, force_single_symbol = FALSE, ...) + +\method{as_units}{call}(x, check_is_valid = TRUE, ...) + +\method{as_units}{expression}(x, check_is_valid = TRUE, ...) + +\method{as_units}{name}(x, check_is_valid = TRUE, ...) + +\method{as_units}{POSIXt}(x, value, ...) + +\method{as_units}{Date}(x, value, ...) } \arguments{ -\item{x}{numeric vector, or object of class \code{units}} +\item{x}{numeric vector, or object of class \code{units}.} -\item{value}{object of class \code{units} or \code{symbolic_units}, or in the case of \code{set_units} expression with symbols that can be resolved in \link{ud_units} (see examples).} +\item{value}{object of class \code{units} or \code{symbolic_units}, or in the +case of \code{set_units} expression with symbols that can be resolved in +\link{ud_units} (see examples).} + +\item{...}{passed on to other methods.} + +\item{mode}{if \code{"symbols"} (the default), then unit is constructed from +the expression supplied. Otherwise, if\code{mode = "standard"}, +standard evaluation is used for the supplied value This argument can be set +via a global option \code{units_options(set_units_mode = "standard")}} + +\item{bare_expression}{a bare R expression describing units. Must be valid R +syntax (reserved R syntax words like \code{in} must be backticked)} + +\item{check_is_valid}{throw an error if all the unit symbols are not either +recognized by udunits2 via \code{ud_is_parseable()}, or a custom +user defined via \code{install_symbolic_unit()}. If \code{FALSE}, no check +for validity is performed.} + +\item{implicit_exponents}{If the unit string is in product power form (e.g. +\code{"km m-2 s-1"}). Defaults to \code{NULL}, in which case a guess is made +based on the supplied string. Set to \code{TRUE} or \code{FALSE} if the guess is +incorrect.} + +\item{force_single_symbol}{Whether to perform no string parsing and force +treatment of the string as a single symbol.} } \value{ -object of class \code{units} +An object of class \code{units}. -the units method retrieves the units attribute, which is of class \code{symbolic_units} +The \code{units} method retrieves the units attribute, which is of +class \code{symbolic_units}. } \description{ -Set measurement units on a numeric vector +A number of functions are provided for handling unit objects. +\itemize{ + \item \code{`units<-`} and \code{units} are the basic functions to set + and retrieve units. + \item \code{as_units}, a generic with methods for a + character string and for quoted language. Note, direct usage of this function + by users is typically not necessary, as coercion via \code{as_units} is + automatically done with \code{`units<-`} and \code{set_units}. + \item \code{make_units}, constructs units from bare expressions. + \code{make_units(m/s)} is equivalent to \code{as_units(quote(m/s))}. + \item \code{set_units}, a pipe-friendly version of \code{`units<-`}. By + default it operates with bare expressions like \code{make_unit}, but this + behavior can be disabled by a specifying \code{mode = "standard"} or setting + \code{units_options(set_units_mode = "standard")}. +} +} +\details{ +If \code{value} is of class \code{units} and has a value unequal to 1, this +value is ignored unless \code{units_options("simplifiy")} is \code{TRUE}. If +\code{simplify} is \code{TRUE}, \code{x} is multiplied by this value. +} +\note{ +By default, unit names are automatically substituted with unit names + (e.g., kilogram --> kg). To turn off this behavior, set + \code{units_options(auto_convert_names_to_symbols = FALSE)} +} +\section{Character strings}{ + + + Generally speaking, there are 3 types of unit strings are accepted in + \code{as_units} (and by extension, \code{`units<-`}). + + The first, and likely most common, is a "standard" format unit + specification where the relationship between unit symbols or names is + specified explicitly with arithmetic symbols for division \code{/}, + multiplication \code{*} and power exponents \code{^}, or other mathematical + functions like \code{log()}. In this case, the string is parsed as an R + expression via \code{parse(text = )} after backticking all unit symbols and + names, and then passed on to \code{as_units.call()}. A heuristic is used to + perform backticking, such that any continuous set of characters + uninterrupted by one of \code{()\\*^-} are backticked (unless the character + sequence consists solely of numbers \code{0-9}), with some care to not + double up on pre-existing backticks. This heuristic appears to be quite + robust, and works for units would otherwise not be valid R syntax. For + example, percent (\code{"\%"}), feet (\code{"'"}), inches (\code{"in"}), + and Tesla (\code{"T"}) are all backticked and parsed correctly. + + Nevertheless, for certain complex unit expressions, this backticking heuristic + may give incorrect results. If the string supplied fails to parse as an R + expression, then the string is treated as a single symbolic unit and + \code{symbolic_unit(chr)} is used as a fallback with a warning. In that + case, automatic unit simplification may not work properly when performing + operations on unit objects, but unit conversion and other Math operations + should still give correct results so long as the unit string supplied + returns \code{TRUE} for \code{ud_is_parsable()}. -Convert units + The second type of unit string accepted is one with implicit exponents. In + this format, \code{/}, \code{*}, and \code{^}, may not be present in the + string, and unit symbol or names must be separated by a space. Each unit + symbol may optionally be followed by a single number, specifying the power. + For example \code{"m2 s-2"} is equivalent to \code{"(m^2)*(s^-2)"}. + + It must be noted that prepended numbers are supported too, but their + interpretation slightly varies depending on whether they are separated from + the unit string or not. E.g., \code{"1000 m"} is interpreted as magnitude + and unit, but \code{"1000m"} is interpreted as a prefixed unit, and it is + equivalent to \code{"km"} to all effects. -retrieve measurement units from \code{units} object + The third type of unit string format accepted is the special case of + udunits time duration with a reference origin, for example \code{"hours + since 1970-01-01 00:00:00"}. Note, that the handling of time and calendar + operations via the udunits library is subtly different from the way R + handles date and time operations. This functionality is mostly exported for + users that work with udunits time data, e.g., with NetCDF files. Users are + otherwise encouraged to use \code{R}'s date and time functionality provided + by \code{Date} and \code{POSIXt} classes. } -\details{ -if \code{value} is of class \code{units} and has a value unequal to 1, this value is ignored unless \code{units_options("simplifiy")} is \code{TRUE}. If \code{simplify} is \code{TRUE}, \code{x} is multiplied by this value. + +\section{Expressions}{ + + + In \code{as_units()}, each of the symbols in the unit expression is treated + individually, such that each symbol must be recognized by the udunits + database (checked by \code{ud_is_parseable()}, \emph{or} be a custom, + user-defined unit symbol that was defined either by + \code{install_symbolic_unit()} or \code{install_conversion_constant()}. To + see which symbols and names are currently recognized by the udunits + database, see \code{udunits_symbols()}. } + \examples{ x = 1:3 class(x) @@ -48,4 +198,124 @@ a # convert to a mixed_units object: units(a) = c("m/s", "km/h", "km/h") a +# The easiest way to assign units to a numeric vector is like this: +x <- y <- 1:4 +units(x) <- "m/s" # meters / second + +# Alternatively, the easiest pipe-friendly way to set units: +if(requireNamespace("magrittr", quietly = TRUE)) { + library(magrittr) + y \%>\% set_units(m/s) +} + +# these are different ways of creating the same unit: +# meters per second squared, i.e, acceleration +x1 <- make_units(m/s^2) +x2 <- as_units(quote(m/s^2)) +x2 <- as_units("m/s^2") +x3 <- as_units("m s-2") # in product power form, i.e., implicit exponents = T +x4 <- set_units(1, m/s^2) # by default, mode = "symbols" +x5 <- set_units(1, "m/s^2", mode = "standard") +x6 <- set_units(1, x1, mode = "standard") +x7 <- set_units(1, units(x1), mode = "standard") +x8 <- as_units("m") / as_units("s")^2 + +all_identical <- function(...) { + l <- list(...) + for(i in seq_along(l)[-1]) + if(!identical(l[[1]], l[[i]])) + return(FALSE) + TRUE +} +all_identical(x1, x2, x3, x4, x5, x6, x7, x8) + +# Note, direct usage of these unit creation functions is typically not +# necessary, since coercion is automatically done via as_units(). Again, +# these are all equivalent ways to generate the same result. + +x1 <- x2 <- x3 <- x4 <- x5 <- x6 <- x7 <- x8 <- 1:4 +units(x1) <- "m/s^2" +units(x2) <- "m s-2" +units(x3) <- quote(m/s^2) +units(x4) <- make_units(m/s^2) +units(x5) <- as_units(quote(m/s^2)) +x6 <- set_units(x6, m/s^2) +x7 <- set_units(x7, "m/s^2", mode = "standard") +x8 <- set_units(x8, units(x1), mode = "standard") + +all_identical(x1, x2, x3, x4, x5, x6, x7, x8) + + +# Both unit names or symbols can be used. By default, unit names are +# automatically converted to unit symbols. +make_units(degree_C) +make_units(kilogram) +make_units(ohm) +# Note, if the printing of non-ascii characters is garbled, then you may +# need to specify the encoding on your system manually like this: +# ud_set_encoding("latin1") +# not all unit names get converted to symbols under different encodings + +## Arithmetic operations and units +# conversion between unit objects that were defined as symbols and names will +# work correctly, although unit simplification in printing may not always occur. +x <- 500 * make_units(micrograms/liter) +y <- set_units(200, ug/l) +x + y +x * y # numeric result is correct, but units not simplified completely + +# note, plural form of unit name accepted too ('liters' vs 'liter'), and +# denominator simplification can be performed correctly +x * set_units(5, liters) + +# unit conversion works too +set_units(x, grams/gallon) + +## Creating custom, user defined units +# For example, a microbiologist might work with counts of bacterial cells +# make_units(cells/ml) # by default, throws an ERROR +# First define the unit, then the newly defined unit is accepted. +install_symbolic_unit("cells") +make_units(cells/ml) + +# Note, install_symbolic_unit() does not add any support for unit +# conversion, or arithmetic operations that require unit conversion. See +# ?install_conversion_constant for defining relationships between user +# defined units. + +## set_units() +# set_units is a pipe friendly version of `units<-`. +if(requireNamespace("magrittr", quietly = TRUE)) { + library(magrittr) + 1:5 \%>\% set_units(N/m^2) + # first sets to m, then converts to km + 1:5 \%>\% set_units(m) \%>\% set_units(km) +} + +# set_units has two modes of operation. By default, it operates with +# bare symbols to define the units. +set_units(1:5, m/s) + +# use `mode = "standard"` to use the value of supplied argument, rather than +# the bare symbols of the expression. In this mode, set_units() can be +# thought of as a simple alias for `units<-` that is pipe friendly. +set_units(1:5, "m/s", mode = "standard") +set_units(1:5, make_units(m/s), mode = "standard") + +# the mode of set_units() can be controlled via a global option +# units_options(set_units_mode = "standard") + +# To remove units use +units(x) <- NULL +# or +set_units(x, NULL) +# or +drop_units(y) +s = Sys.time() +d = s - (s+1) +as_units(d) + +} +\seealso{ +\code{\link{valid_udunits}} }