blob: cacc39eb08516e75dcf7f4c2a854d550e65a4635 [file] [log] [blame]
# File src/library/stats/R/spline.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 The R Core Team
# 2002 Simon N. Wood
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
#### 'spline' and 'splinefun' are very similar --- keep in sync!
#### --------- has more
#### also consider ``compatibility'' with 'approx' and 'approxfun'
spline <-
function(x, y = NULL, n = 3*length(x), method = "fmm",
xmin = min(x), xmax = max(x), xout, ties = mean)
{
method <- pmatch(method, c("periodic", "natural", "fmm", "hyman"))
if(is.na(method)) stop("invalid interpolation method")
x <- regularize.values(x, y, ties, missing(ties)) # -> (x,y) numeric of same length
y <- x$y
x <- x$x
nx <- length(x) # large vectors ==> non-integer
if(is.na(nx)) stop(gettextf("invalid value of %s", "length(x)"), domain = NA)
if(nx == 0) stop("zero non-NA points")
if(method == 1L && y[1L] != y[nx]) { # periodic
warning("spline: first and last y values differ - using y[1] for both")
y[nx] <- y[1L]
}
if(method == 4L) {
dy <- diff(y)
if(!(all(dy >= 0) || all(dy <= 0)))
stop("'y' must be increasing or decreasing")
}
if(missing(xout)) xout <- seq.int(xmin, xmax, length.out = n)
else n <- length(xout)
if (n <= 0L) stop("'spline' requires n >= 1")
xout <- as.double(xout)
z <- .Call(C_SplineCoef, min(3L, method), x, y)
if(method == 4L) z <- spl_coef_conv(hyman_filter(z))
list(x = xout, y = .Call(C_SplineEval, xout, z))
}
### Filters cubic spline function to yield co-monotonicity in accordance
### with Hyman (1983) SIAM J. Sci. Stat. Comput. 4(4):645-654, z$x is knot
### position z$y is value at knot z$b is gradient at knot. See also
### Dougherty, Edelman and Hyman 1989 Mathematics of Computation 52:471-494.
### Contributed by Simon N. Wood, improved by R-core.
### https://stat.ethz.ch/pipermail/r-help/2002-September/024890.html
hyman_filter <- function(z)
{
n <- length(z$x)
ss <- diff(z$y) / diff(z$x)
S0 <- c(ss[1L], ss)
S1 <- c(ss, ss[n-1L])
t1 <- pmin(abs(S0), abs(S1))
sig <- z$b
ind <- S0*S1 > 0
sig[ind] <- S1[ind]
ind <- sig >= 0
if(sum(ind)) z$b[ind] <- pmin(pmax(0, z$b[ind]), 3*t1[ind])
ind <- !ind
if(sum(ind)) z$b[ind] <- pmax(pmin(0, z$b[ind]), -3*t1[ind])
z
}
### Takes an object z containing equal-length vectors
### z$x, z$y, z$b, z$c, z$d defining a cubic spline interpolating
### z$x, z$y and forces z$c and z$d to be consistent with z$y and
### z$b (gradient of spline). This is intended for use in conjunction
### with Hyman's monotonicity filter.
### Note that R's spline routine has s''(x)/2 as c and s'''(x)/6 as d.
### Contributed by Simon N. Wood, improved by R-core.
spl_coef_conv <- function(z)
{
n <- length(z$x)
h <- diff(z$x); y <- -diff(z$y)
b0 <- z$b[-n]; b1 <- z$b[-1L]
cc <- -(3*y + (2*b0 + b1)*h) / h^2
c1 <- (3*y[n-1L] + (b0[n-1L] + 2*b1[n-1L])*h[n-1L]) / h[n-1L]^2
z$c <- c(cc, c1)
dd <- (2*y/h + b0 + b1) / h^2
z$d <- c(dd, dd[n-1L])
z
}