blob: 204f8138a3c1df332187aa09eddc9ecf8fe53be5 [file] [log] [blame]
# File src/library/splines/R/splines.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2018 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/
bs <- function(x, df = NULL, knots = NULL, degree = 3, intercept = FALSE,
Boundary.knots = range(x))
{
ord <- 1L + (degree <- as.integer(degree))
if(ord <= 1) stop("'degree' must be integer >= 1")
nx <- names(x)
x <- as.vector(x)
nax <- is.na(x)
if(nas <- any(nax))
x <- x[!nax]
outside <- if(!missing(Boundary.knots)) {
Boundary.knots <- sort(Boundary.knots)
(ol <- x < Boundary.knots[1L]) | (or <- x > Boundary.knots[2L])
} else FALSE
if(!is.null(df) && is.null(knots)) {
nIknots <- df - ord + (1L - intercept) # == #{inner knots}
if(nIknots < 0L) {
nIknots <- 0L
warning(gettextf("'df' was too small; have used %d",
ord - (1L - intercept)), domain = NA)
}
knots <-
if(nIknots > 0L) {
knots <- seq.int(from = 0, to = 1,
length.out = nIknots + 2L)[-c(1L, nIknots + 2L)]
quantile(x[!outside], knots)
}
}
Aknots <- sort(c(rep(Boundary.knots, ord), knots))
if(any(outside)) {
warning("some 'x' values beyond boundary knots may cause ill-conditioned bases")
derivs <- 0:degree
scalef <- gamma(1L:ord)# factorials
basis <- array(0, c(length(x), length(Aknots) - degree - 1L))
e <- 1/4 # in theory anything in (0,1); was (implicitly) 0 in R <= 3.2.2
if(any(ol)) {
## left pivot inside, i.e., a bit to the right of the boundary knot
k.pivot <- (1-e)*Boundary.knots[1L] + e*Aknots[ord+1]
xl <- cbind(1, outer(x[ol] - k.pivot, 1L:degree, "^"))
tt <- splineDesign(Aknots, rep(k.pivot, ord), ord, derivs)
basis[ol, ] <- xl %*% (tt/scalef)
}
if(any(or)) {
## right pivot inside, i.e., a bit to the left of the boundary knot:
k.pivot <- (1-e)*Boundary.knots[2L] + e*Aknots[length(Aknots)-ord]
xr <- cbind(1, outer(x[or] - k.pivot, 1L:degree, "^"))
tt <- splineDesign(Aknots, rep(k.pivot, ord), ord, derivs)
basis[or, ] <- xr %*% (tt/scalef)
}
if(any(inside <- !outside))
basis[inside, ] <- splineDesign(Aknots, x[inside], ord)
}
else basis <- splineDesign(Aknots, x, ord)
if(!intercept)
basis <- basis[, -1L , drop = FALSE]
n.col <- ncol(basis)
if(nas) {
nmat <- matrix(NA, length(nax), n.col)
nmat[!nax, ] <- basis
basis <- nmat
}
dimnames(basis) <- list(nx, 1L:n.col)
a <- list(degree = degree, knots = if(is.null(knots)) numeric(0L) else knots,
Boundary.knots = Boundary.knots, intercept = intercept)
attributes(basis) <- c(attributes(basis), a)
class(basis) <- c("bs", "basis", "matrix")
basis
}
ns <- function(x, df = NULL, knots = NULL, intercept = FALSE,
Boundary.knots = range(x))
{
nx <- names(x)
x <- as.vector(x)
nax <- is.na(x)
if(nas <- any(nax))
x <- x[!nax]
outside <- if(!missing(Boundary.knots)) {
Boundary.knots <- sort(Boundary.knots)
(ol <- x < Boundary.knots[1L]) | (or <- x > Boundary.knots[2L])
}
else {
if(length(x) == 1L) ## && missing(Boundary.knots) : special treatment
Boundary.knots <- x*c(7,9)/8 # symmetrically around x
FALSE # rep(FALSE, length = length(x))
}
if(!is.null(df) && is.null(knots)) {
## df = number(interior knots) + 1 + intercept
nIknots <- df - 1L - intercept
if(nIknots < 0L) {
nIknots <- 0L
warning(gettextf("'df' was too small; have used %d",
1L + intercept), domain = NA)
}
knots <-
if(nIknots > 0L) {
knots <- seq.int(from = 0, to = 1,
length.out = nIknots + 2L)[-c(1L, nIknots + 2L)]
quantile(x[!outside], knots)
}
} else nIknots <- length(knots)
Aknots <- sort(c(rep(Boundary.knots, 4L), knots))
if(any(outside)) {
basis <- array(0, c(length(x), nIknots + 4L))
if(any(ol)) {
k.pivot <- Boundary.knots[1L]
xl <- cbind(1, x[ol] - k.pivot)
tt <- splineDesign(Aknots, rep(k.pivot, 2L), 4, c(0, 1))
basis[ol, ] <- xl %*% tt
}
if(any(or)) {
k.pivot <- Boundary.knots[2L]
xr <- cbind(1, x[or] - k.pivot)
tt <- splineDesign(Aknots, rep(k.pivot, 2L), 4, c(0, 1))
basis[or, ] <- xr %*% tt
}
if(any(inside <- !outside))
basis[inside, ] <- splineDesign(Aknots, x[inside], 4)
}
else basis <- splineDesign(Aknots, x, ord = 4L)
const <- splineDesign(Aknots, Boundary.knots, ord = 4L, derivs = c(2L, 2L))
if(!intercept) {
const <- const[, -1 , drop = FALSE]
basis <- basis[, -1 , drop = FALSE]
}
qr.const <- qr(t(const))
basis <- as.matrix((t(qr.qty(qr.const, t(basis))))[, - (1L:2L), drop = FALSE])
n.col <- ncol(basis)
if(nas) {
nmat <- matrix(NA, length(nax), n.col)
nmat[!nax, ] <- basis
basis <- nmat
}
dimnames(basis) <- list(nx, 1L:n.col)
a <- list(degree = 3L, knots = if(is.null(knots)) numeric() else knots,
Boundary.knots = Boundary.knots, intercept = intercept)
attributes(basis) <- c(attributes(basis), a)
class(basis) <- c("ns", "basis", "matrix")
basis
}
predict.bs <- function(object, newx, ...)
{
if(missing(newx))
return(object)
a <- c(list(x = newx), attributes(object)[
c("degree", "knots", "Boundary.knots", "intercept")])
do.call("bs", a)
}
predict.ns <- function(object, newx, ...)
{
if(missing(newx))
return(object)
a <- c(list(x = newx), attributes(object)[
c("knots", "Boundary.knots", "intercept")])
do.call("ns", a)
}
### FIXME: Also need summary.basis() and probably print.basis() method!
makepredictcall.ns <- function(var, call)
{
## check must work correctly when call is a symbol, both for quote(ns) and quote(t1):
if(as.character(call)[1L] == "ns" || (is.call(call) && identical(eval(call[[1L]]), ns))) {
at <- attributes(var)[c("knots", "Boundary.knots", "intercept")]
call <- call[1L:2L]
call[names(at)] <- at
}
call
}
makepredictcall.bs <- function(var, call)
{
if(as.character(call)[1L] == "bs" || (is.call(call) && identical(eval(call[[1L]]), bs))) {
at <- attributes(var)[c("degree", "knots", "Boundary.knots", "intercept")]
call <- call[1L:2L]
call[names(at)] <- at
}
call
}
## this is *not* used anymore by our own code [but has been exported "forever"]
spline.des <- function(knots, x, ord = 4, derivs = integer(length(x)),
outer.ok = FALSE, sparse = FALSE)
{
if(is.unsorted(knots <- as.numeric(knots)))
knots <- sort.int(knots)
list(knots = knots, order = ord, derivs = derivs,
design = splineDesign(knots, x, ord, derivs,
outer.ok = outer.ok, sparse = sparse))
}
## splineDesign() is in ./splineClasses.R