blob: f92527dec43b62ff9fe5582dc69d1369546d7d0d [file] [log] [blame]
# File src/library/stats/R/splinefun.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 The R Core Team
#
# 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!
#### also consider ``compatibility'' with 'approx' and 'approxfun'
splinefun <-
function(x, y = NULL,
method = c("fmm", "periodic", "natural", "monoH.FC", "hyman"),
ties = mean)
{
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")
method <- match.arg(method)
if(method == "periodic" && y[1L] != y[nx]) {
warning("spline: first and last y values differ - using y[1L] for both")
y[nx] <- y[1L]
}
if(method == "monoH.FC") {
n1 <- nx - 1L
## - - - "Data preprocessing" - - -
dy <- y[-1L] - y[-nx] # = diff(y)
dx <- x[-1L] - x[-nx] # = diff(x)
Sx <- dy / dx # Sx[k] = \Delta_k = (y_{k+1} - y_k)/(x_{k+1} - x_k), k=1:n1
m <- c(Sx[1L], (Sx[-1L] + Sx[-n1])/2, Sx[n1]) ## 1.
## use C, as we need to "serially" progress from left to right:
m <- .Call(C_monoFC_m, m, Sx)
## Hermite spline with (x,y,m) :
return(splinefunH0(x0 = x, y0 = y, m = m, dx = dx))
}
## else
iMeth <- match(method, c("periodic", "natural", "fmm",
"monoH.FC", "hyman"))
if(iMeth == 5L) {
dy <- diff(y)
if(!(all(dy >= 0) || all(dy <= 0)))
stop("'y' must be increasing or decreasing")
}
z <- .Call(C_SplineCoef, min(3L, iMeth), x, y)
if(iMeth == 5L) z <- spl_coef_conv(hyman_filter(z))
rm(x, y, nx, method, iMeth, ties)
function(x, deriv = 0L) {
deriv <- as.integer(deriv)
if (deriv < 0L || deriv > 3L)
stop("'deriv' must be between 0 and 3")
if (deriv > 0L) {
## For deriv >= 2, using approx() should be faster, but doing it correctly
## for all three methods is not worth the programmer's time...
z0 <- double(z$n)
z[c("y", "b", "c")] <-
switch(deriv,
list(y = z$b , b = 2*z$c, c = 3*z$d), # deriv = 1
list(y = 2*z$c, b = 6*z$d, c = z0), # deriv = 2
list(y = 6*z$d, b = z0, c = z0)) # deriv = 3
z[["d"]] <- z0
}
## yout[j] := y[i] + dx*(b[i] + dx*(c[i] + dx* d_i))
## where dx := (u[j]-x[i]); i such that x[i] <= u[j] <= x[i+1},
## u[j]:= xout[j] (unless sometimes for periodic spl.)
## and d_i := d[i] unless for natural splines at left
res <- .splinefun(x, z)
## deal with points to the left of first knot if natural
## splines are used (Bug PR#13132)
if( deriv > 0 && z$method==2 && any(ind <- x<=z$x[1L]) )
res[ind] <- ifelse(deriv == 1, z$y[1L], 0)
res
}
}
## avoid capturing internal calls
.splinefun <- function(x, z) .Call(C_SplineEval, x, z)
## hidden : The exported user function is splinefunH()
splinefunH0 <- function(x0, y0, m, dx = x0[-1L] - x0[-length(x0)])
{
function(x, deriv=0, extrapol = c("linear","cubic"))
{
extrapol <- match.arg(extrapol)
deriv <- as.integer(deriv)
if (deriv < 0 || deriv > 3)
stop("'deriv' must be between 0 and 3")
i <- findInterval(x, x0, all.inside = (extrapol == "cubic"))
if(deriv == 0)
interp <- function(x, i) {
h <- dx[i]
t <- (x - x0[i]) / h
## Compute the 4 Hermite (cubic) polynomials h00, h01,h10, h11
t1 <- t-1
h01 <- t*t*(3 - 2*t)
h00 <- 1 - h01
tt1 <- t*t1
h10 <- tt1 * t1
h11 <- tt1 * t
y0[i] * h00 + h*m[i] * h10 +
y0[i+1]* h01 + h*m[i+1]* h11
}
else if(deriv == 1)
interp <- function(x, i) {
h <- dx[i]
t <- (x - x0[i]) / h
## 1st derivative of Hermite polynomials h00, h01,h10, h11
t1 <- t-1
h01 <- -6*t*t1 # h00 = - h01
h10 <- (3*t - 1) * t1
h11 <- (3*t - 2) * t
(y0[i+1] - y0[i])/h * h01 + m[i] * h10 + m[i+1]* h11
}
else if (deriv == 2)
interp <- function(x, i) {
h <- dx[i]
t <- (x - x0[i]) / h
## 2nd derivative of Hermite polynomials h00, h01,h10, h11
h01 <- 6*(1-2*t) # h00 = - h01
h10 <- 2*(3*t - 2)
h11 <- 2*(3*t - 1)
((y0[i+1] - y0[i])/h * h01 + m[i] * h10 + m[i+1]* h11) / h
}
else # deriv == 3
interp <- function(x, i) {
h <- dx[i]
## 3rd derivative of Hermite polynomials h00, h01,h10, h11
h01 <- -12 # h00 = - h01
h10 <- 6
h11 <- 6
((y0[i+1] - y0[i])/h * h01 + m[i] * h10 + m[i+1]* h11) / h
}
if(extrapol == "linear" &&
any(iXtra <- (iL <- (i == 0)) | (iR <- (i == (n <- length(x0)))))) {
## do linear extrapolation
r <- x
if(any(iL)) r[iL] <- if(deriv == 0) y0[1L] + m[1L]*(x[iL] - x0[1L]) else
if(deriv == 1) m[1L] else 0
if(any(iR)) r[iR] <- if(deriv == 0) y0[n] + m[n]*(x[iR] - x0[n]) else
if(deriv == 1) m[n] else 0
## For internal values, compute "as normal":
ini <- !iXtra
r[ini] <- interp(x[ini], i[ini])
r
}
else { ## use cubic Hermite polynomials, even for extrapolation
interp(x, i)
}
}
}
splinefunH <- function(x, y, m)
{
## Purpose: "Cubic Hermite Spline"
## ----------------------------------------------------------------------
## Arguments: (x,y): points; m: slope at points, all of equal length
## ----------------------------------------------------------------------
## Author: Martin Maechler, Date: 9 Jan 2008
n <- length(x)
stopifnot(is.numeric(x), is.numeric(y), is.numeric(m),
length(y) == n, length(m) == n)
if(is.unsorted(x)) {
i <- sort.list(x)
x <- x[i]
y <- y[i]
m <- m[i]
}
dx <- x[-1L] - x[-n]
if(anyNA(dx) || any(dx == 0))
stop("'x' must be *strictly* increasing (non - NA)")
splinefunH0(x, y, m, dx=dx)
}