Skip to content

Commit

Permalink
add basic derivatives of transforms and inverses (r-lib#322)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjskay committed Nov 3, 2023
1 parent b885cf1 commit b2ae794
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 16 deletions.
61 changes: 48 additions & 13 deletions R/trans-numeric.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ asn_trans <- function() {
"asn",
function(x) 2 * asin(sqrt(x)),
function(x) sin(x / 2)^2,
d_transform = function(x) 1 / sqrt(x - x^2),
d_inverse = function(x) sin(x) / 2,
domain = c(0, 1)
)
}
Expand All @@ -21,7 +23,14 @@ asn_trans <- function() {
#' @examples
#' plot(atanh_trans(), xlim = c(-1, 1))
atanh_trans <- function() {
trans_new("atanh", "atanh", "tanh", domain = c(-1, 1))
trans_new(
"atanh",
"atanh",
"tanh",
d_transform = function(x) 1 / (1 - x^2),
d_inverse = function(x) 1 / cosh(x)^2,
domain = c(-1, 1)
)
}

#' Inverse Hyperbolic Sine transformation
Expand Down Expand Up @@ -198,7 +207,9 @@ exp_trans <- function(base = exp(1)) {
trans_new(
paste0("power-", format(base)),
function(x) base^x,
function(x) log(x, base = base)
function(x) log(x, base = base),
d_transform = function(x) base^x * log(base),
d_inverse = function(x) 1 / x / log(base)
)
}

Expand All @@ -208,7 +219,13 @@ exp_trans <- function(base = exp(1)) {
#' @examples
#' plot(identity_trans(), xlim = c(-1, 1))
identity_trans <- function() {
trans_new("identity", "force", "force")
trans_new(
"identity",
"force",
"force",
d_transform = function(x) 1,
d_inverse = function(x) 1
)
}


Expand Down Expand Up @@ -237,11 +254,13 @@ identity_trans <- function() {
#' lines(log_trans(), xlim = c(1, 20), col = "red")
log_trans <- function(base = exp(1)) {
force(base)
trans <- function(x) log(x, base)
inv <- function(x) base^x

trans_new(paste0("log-", format(base)), trans, inv,
log_breaks(base = base),
trans_new(
paste0("log-", format(base)),
function(x) log(x, base),
function(x) base^x,
d_transform = function(x) 1 / x / log(base),
d_inverse = function(x) base^x * log(base),
breaks = log_breaks(base = base),
domain = c(1e-100, Inf)
)
}
Expand All @@ -261,7 +280,11 @@ log2_trans <- function() {
#' @export
log1p_trans <- function() {
trans_new(
"log1p", "log1p", "expm1",
"log1p",
"log1p",
"expm1",
d_transform = function(x) 1 / (1 + x),
d_inverse = "exp",
domain = c(-1 + .Machine$double.eps, Inf)
)
}
Expand All @@ -273,15 +296,18 @@ pseudo_log_trans <- function(sigma = 1, base = exp(1)) {
trans_new(
"pseudo_log",
function(x) asinh(x / (2 * sigma)) / log(base),
function(x) 2 * sigma * sinh(x * log(base))
function(x) 2 * sigma * sinh(x * log(base)),
d_transform = function(x) 1 / (sqrt(4 + x^2/sigma^2) * sigma * log(base)),
d_inverse = function(x) 2 * sigma * cosh(x * log(base)) * log(base)
)
}

#' Probability transformation
#'
#' @param distribution probability distribution. Should be standard R
#' abbreviation so that "p" + distribution is a valid probability density
#' function, and "q" + distribution is a valid quantile function.
#' abbreviation so that "p" + distribution is a valid cumulative distribution
#' function, "q" + distribution is a valid quantile function, and
#' "d" + distribution is a valid probability density function.
#' @param ... other arguments passed on to distribution and quantile functions
#' @export
#' @examples
Expand All @@ -290,11 +316,14 @@ pseudo_log_trans <- function(sigma = 1, base = exp(1)) {
probability_trans <- function(distribution, ...) {
qfun <- match.fun(paste0("q", distribution))
pfun <- match.fun(paste0("p", distribution))
dfun <- match.fun(paste0("d", distribution))

trans_new(
paste0("prob-", distribution),
function(x) qfun(x, ...),
function(x) pfun(x, ...),
d_transform = function(x) 1 / dfun(qfun(x, ...), ...),
d_inverse = function(x) dfun(x, ...),
domain = c(0, 1)
)
}
Expand All @@ -314,7 +343,9 @@ reciprocal_trans <- function() {
trans_new(
"reciprocal",
function(x) 1 / x,
function(x) 1 / x
function(x) 1 / x,
d_transform = function(x) -1 / x^2,
d_inverse = function(x) -1 / x^2
)
}

Expand All @@ -332,6 +363,8 @@ reverse_trans <- function() {
"reverse",
function(x) -x,
function(x) -x,
d_transform = function(x) -1,
d_inverse = function(x) -1,
minor_breaks = regular_minor_breaks(reverse = TRUE)
)
}
Expand All @@ -349,6 +382,8 @@ sqrt_trans <- function() {
"sqrt",
"sqrt",
function(x) ifelse(x < 0, NA_real_, x ^ 2),
d_transform = function(x) 0.5 / sqrt(x),
d_inverse = function(x) 2 * x,
domain = c(0, Inf)
)
}
17 changes: 14 additions & 3 deletions R/trans.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
#' A transformation encapsulates a transformation and its inverse, as well
#' as the information needed to create pleasing breaks and labels. The `breaks()`
#' function is applied on the un-transformed range of the data, and the
#' `format()` function takes the output of the `breaks()` function and return
#' well-formatted labels.
#' `format()` function takes the output of the `breaks()` function and returns
#' well-formatted labels. Transformations may also include the derivatives of the
#' transformation and its inverse, but are not required to.
#'
#' @param name transformation name
#' @param transform function, or name of function, that performs the
#' transformation
#' @param inverse function, or name of function, that performs the
#' inverse of the transformation
#' @param d_transform Optional function, or name of function, that gives the
#' derivative of the transformation. May be `NULL`.
#' @param d_inverse Optional function, or name of function, that gives the
#' derivative of the inverse of the transformation. May be `NULL`.
#' @param breaks default breaks function for this transformation. The breaks
#' function is applied to the un-transformed data.
#' @param minor_breaks default minor breaks function for this transformation.
Expand All @@ -23,17 +28,23 @@
#' @export
#' @keywords internal
#' @aliases trans
trans_new <- function(name, transform, inverse, breaks = extended_breaks(),
trans_new <- function(name, transform, inverse,
d_transform = NULL, d_inverse = NULL,
breaks = extended_breaks(),
minor_breaks = regular_minor_breaks(),
format = format_format(), domain = c(-Inf, Inf)) {
if (is.character(transform)) transform <- match.fun(transform)
if (is.character(inverse)) inverse <- match.fun(inverse)
if (is.character(d_transform)) d_transform <- match.fun(d_transform)
if (is.character(d_inverse)) d_inverse <- match.fun(d_inverse)

structure(
list(
name = name,
transform = transform,
inverse = inverse,
d_transform = d_transform,
d_inverse = d_inverse,
breaks = breaks,
minor_breaks = minor_breaks,
format = format,
Expand Down

0 comments on commit b2ae794

Please sign in to comment.