Rev 87397 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
# File src/library/stats/R/smspline.R# Part of the R package, https://www.R-project.org## Copyright (C) 1995-2023 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/.nknots.smspl <- function(n) {## Number of inner knotsif(n < 50L) nelse trunc({a1 <- log2( 50)a2 <- log2(100)a3 <- log2(140)a4 <- log2(200)if (n < 200L) 2^(a1+(a2-a1)*(n-50)/150)else if (n < 800L) 2^(a2+(a3-a2)*(n-200)/600)else if (n < 3200L)2^(a3+(a4-a3)*(n-800)/2400)else 200 + (n-3200)^0.2})}## n.knots <- function(n) {## message(".nknots.smspl() is now exported; use it instead of n.knots()")## .nknots.smspl(n)## }smooth.spline <-function(x, y = NULL, w = NULL, df, spar = NULL, lambda = NULL, cv = FALSE,all.knots = FALSE, nknots = .nknots.smspl, keep.data = TRUE,df.offset = 0, penalty = 1, control.spar = list(),tol = 1e-6 * IQR(x),keep.stuff = FALSE) # was "fix" till R 3.3.x{contr.sp <- list(low = -1.5, # low = 0. was default till R 1.3.xhigh = 1.5,tol = 1e-4, # tol = 0.001 was default till R 1.3.xeps = 2e-8, # eps = 0.00244 was default till R 1.3.xmaxit = 500, trace = getOption("verbose"))contr.sp[names(control.spar)] <- control.sparctrl.Num <- contr.sp[1:4]if(!all(vapply(ctrl.Num, is.numeric, NA)) ||contr.sp$tol < 0 || contr.sp$eps <= 0 || contr.sp$maxit <= 0)stop("invalid 'control.spar'")xy <- xy.coords(x, y, setLab=FALSE)y <- xy$yx <- xy$xif(!all(is.finite(c(x, y))))stop("missing or infinite values in inputs are not allowed")n <- length(x)if(is.na(n)) stop("invalid number of points")no.wgts <- is.null(w)w <-if(no.wgts) 1 # rep_len(1, n)else {if(n != length(w)) stop("lengths of 'x' and 'w' must match")if(any(w < 0)) stop("all weights should be non-negative")if(all(w == 0)) stop("some weights should be positive")(w * sum(w > 0))/sum(w)} # now sum(w) == #{obs. with weight > 0} == sum(w > 0)## Replace y[], w[] for same x[] (to a precision of 'tol') by their mean :if(!is.finite(tol) || tol <= 0)stop("'tol' must be strictly positive and finite")if(!match(keep.stuff, c(FALSE,TRUE))) stop("invalid 'keep.stuff'")xx <- round((x - mean(x))/tol) # de-mean to avoid possible overflowuns.x <- is.unsorted(x)iOx <- if(uns.x) sort.list(x) else TRUExxs <- xx[iOx] # xx sortednd <- c(TRUE, xxs[-n] < xxs[-1L]) # === !duplicated(xxs)nx <- length(ux <- x[iOx][nd]) # ux := unique & sorted xif(nx <= 3L) stop("need at least four unique 'x' values")if(nx == n) { # speedupox <- if(uns.x) (function(p) { p[p] <- seq_along(p) ; p })(iOx) else TRUEtmp <- cbind(w, w*y, w*y^2)[iOx,]} else {ox <- match(xx, xxs[nd])## Faster, much simplified version of tapply()tapply1 <- function (X, INDEX, FUN = NULL, ..., simplify = TRUE) {sapply(X = unname(split(X, INDEX)), FUN = FUN, ...,simplify = simplify, USE.NAMES = FALSE)}tmp <- matrix(unlist(tapply1(seq_len(n), ox,if(length(w) == 1L) function(i)c(length(i), sum(y[i]), sum(y[i]^2))else function(i)c(sum(w[i]), sum(w[i]*y[i]),sum(w[i]*y[i]^2))),use.names = FALSE),ncol = 3, byrow = TRUE)}wbar <- tmp[, 1L]ybar <- tmp[, 2L]/ifelse(wbar > 0, wbar, 1)yssw <- sum(tmp[, 3L] - wbar*ybar^2) # will be added to RSS for GCVrm(iOx, xx, xxs, nd, tmp)## cv in {NA,FALSE,TRUE} :if(is.na(cv <- as.logical(cv)) && !missing(df))stop("'cv' must not be NA when 'df' is specified")CV <- !is.na(cv) && cvif(CV && nx < n)warning("cross-validation with non-unique 'x' values seems doubtful")r.ux <- ux[nx] - ux[1L]xbar <- (ux - ux[1L])/r.ux # scaled to [0,1]if(is.numeric(all.knots)) {if(is.unsorted(all.knots, strictly = TRUE))stop("Numeric 'all.knots' must be strictly increasing")if(!missing(nknots) && !is.null(nknots))warning("'all.knots' is vector of knots; 'nknots' specification is disregarded")nknots <- length(all.knots)if(0 < all.knots[1] || all.knots[nknots] < 1)stop("numeric 'all.knots' must cover [0,1] (= the transformed data-range)")## otherwise, it seg.faults when .Fortran() is returning (why ?)knot <- c(rep(all.knots[1 ], 3),all.knots,rep(all.knots[nknots], 3))} else {if(all.knots) {if(!missing(nknots) && !is.null(nknots))warning("'all.knots' is TRUE; 'nknots' specification is disregarded")nknots <- nx} else if(is.null(nknots))# <- for back compatibilitynknots <- .nknots.smspl(nx)else { ## all.knots is false; nknots not NULLif(is.function(nknots))nknots <- nknots(nx)else if(!is.numeric(nknots))stop("'nknots' must be numeric (in {1,..,n})")if(nknots < 1)stop("'nknots' must be at least 1")else if(nknots > nx)stop("cannot use more inner knots than unique 'x' values")}knot <- c(rep(xbar[1 ], 3),if(all.knots) xbar else xbar[seq.int(1, nx, length.out = nknots)],rep(xbar[nx], 3))}nk <- nknots + 2L ## == length(knot) - 4spar.is.lambda <- !missing(lambda)if (spar.is.lambda <- !missing(lambda)) {if(!missing(spar)) stop("must not specify both 'spar' and 'lambda'")ispar <- 1L} else## ispar != 1 : compute spar (later)ispar <-if(is.null(spar) || missing(spar)) { ## || spar == 0if(contr.sp$trace) -1L else 0L} else 1Lspar <- if(spar.is.lambda) as.double(lambda)else if(ispar == 1L) as.double(spar) else double(1)## was <- if(missing(spar)) 0 else if(spar < 1.01e-15) 0 else 1## but package forecast passed a length-0 vector.if(length(spar) != 1) stop("'spar' must be of length 1")## icrit {../src/sslvrg.f}:## (0 = no crit, 1 = GCV , 2 = ord.CV , 3 = df-matching)icrit <- if(is.na(cv)) 0L else if(cv) 2L else 1Lif(!is.numeric(df.offset) || df.offset < 0)stop("df.offset must be numeric and >= 0")if(!is.numeric(penalty) || penalty <= 0)stop("penalty must be numeric and > 0")dofoff <- df.offsetif(!missing(df)) { # not when cv was NAif(df > 1 && df <= nx) {icrit <- 3Ldofoff <- df} else warning("not using invalid df; must have 1 < df <= n := #{unique x} = ", nx)}iparms <- c(icrit=icrit, ispar=ispar, iter = as.integer(contr.sp$maxit),spar.is.lambda)ans.names <- c("coef","ty","lev","spar","parms","crit","iparms","ier",if(keep.stuff) "scratch")fit <- .Fortran(C_rbart, # code in ../src/qsbart.fas.double(penalty),as.double(dofoff),x = as.double(xbar),y = as.double(ybar),w = as.double(wbar), # changed in the Fortran codessw = as.double(yssw),as.integer(nx),as.double(knot),as.integer(nk),coef = double(nk),ty = double(nx),lev = double(if(is.na(cv))1L else nx),crit = double(1),iparms = iparms,spar = spar,parms = c(unlist(ctrl.Num), ratio = -1.), # no NA herescratch = double((17L+1L) * nk + 1L),#ld4 = 4L,ldnk = 1L,ier = integer(1L))[ans.names]if(is.na(cv)) lev <- df <- NAelse { # now when dpfa() with 'tol', signals error earlier, happens less often:lev <- fit$levdf <- sum(lev)if(is.na(df))stop("NA lev[]; probably smoothing parameter 'spar' way too large!")}if(fit$ier > 0L ) {offKind <- if(spar.is.lambda) "extreme" # not easy to know if small | largeelse if(sml <- fit$spar < 0.5) "small" else "large"wtxt <- paste("smoothing parameter value too", offKind)if(spar.is.lambda || sml) {## used to give warning too and mean() as below, but that's rubbishstop(wtxt)} else {fit$ty <- rep(mean(y), nx) ## would be df = 1df <- 1warning(wtxt,"\nsetting df = 1 __use with care!__")}}cv.crit <-if(is.na(cv)) NAelse {r <- y - fit$ty[ox] # true residuals; if(nx < n) *not* those used in fittingif(cv) {ww <- wbarww[ww == 0] <- 1r <- r / (1 - (lev[ox] * w)/ww[ox])if(no.wgts) mean(r^2) else weighted.mean(r^2, w)} else # GCV(if(no.wgts) mean(r^2) else weighted.mean(r^2, w)) /(1 - (df.offset + penalty * df)/n)^2}## return :structure(## parms : c(low = , high = , tol = , eps = )list(x = ux, y = fit$ty, w = wbar, yin = ybar, tol = tol,data = if(keep.data) list(x = x, y = y, w = w), no.weights = no.wgts,n = n, # to reliably know (when keep.data is false) if (nx < n)lev = lev,cv = cv,cv.crit = cv.crit, pen.crit = sum(wbar * (ybar - fit$ty)^2),crit = fit$crit,df = df,spar = if(spar.is.lambda) NA else fit$spar,ratio= if(spar.is.lambda) NA else fit$parms[["ratio"]],lambda = fit$parms[["low"]],iparms = c(fit$iparms, errorI = if(fit$ier) fit$ier else NA),#c(icrit= ,ispar= ,iter= )auxM = if(keep.stuff)list(XWy = fit$scratch[ seq_len(nk)],XWX = fit$scratch[nk + seq_len(4*nk)],Sigma= fit$scratch[5*nk+ seq_len(4*nk)],R = fit$scratch[9*nk+ seq_len(4*nk)] ),fit = structure(list(knot = knot, nk = nk, min = ux[1L], range = r.ux,coef = fit$coef),class = "smooth.spline.fit"),call = match.call()),class = "smooth.spline")}fitted.smooth.spline <- function(object, ...) {if(!is.list(dat <- object$data))stop("need result of smooth.spline(keep.data = TRUE)")## note that object$x == unique(sort(object$data$x))object$y[match(dat$x, object$x)]}residuals.smooth.spline <-function (object, type = c("working", "response", "deviance","pearson", "partial"), ...){type <- match.arg(type)if(!is.list(dat <- object$data))stop("need result of smooth.spline(keep.data = TRUE)")r <- dat$y - object$y[match(dat$x, object$x)]## this rest is `as' residuals.lm() :res <- switch(type,working = ,response = r,deviance = ,pearson = if (is.null(dat$w)) r else r * sqrt(dat$w),partial = r)res <- naresid(object$na.action, res)if (type == "partial")stop('type = "partial" is not yet implemented')## res <- res + predict(object, type = "terms")res}hatvalues.smooth.spline <- function (model, ...) {if(!is.list(dat <- model$data))stop("need result of smooth.spline(keep.data = TRUE)")## "expand" leverages:hat <- model$levhat[hat > 1 - 10 * .Machine$double.eps] <- 1 # as in hatvalues.lmhat[match(dat$x, model$x)]}print.smooth.spline <- function(x, digits = getOption("digits"), ...){if(!is.null(cl <- x$call)) {cat("Call:\n")dput(cl, control=NULL)}ip <- x$iparmscat("\nSmoothing Parameter spar=", format(x$spar, digits=digits)," lambda=", format(x$lambda, digits=digits),if(ip["ispar"] != 1L) paste0("(", ip["iter"], " iterations)"))cat("\n")cat("Equivalent Degrees of Freedom (Df):", format(x$df,digits=digits))cat("\n")cat(sprintf("Penalized Criterion (%sRSS): %s\n",if(x$no.weights) "" else "weighted ",format(x$pen.crit, digits=digits)))if(!is.na(cv <- x$cv))cat(if(cv) "PRESS(l.o.o. CV): " else "GCV: ",format(x$cv.crit, digits = digits), "\n", sep = "")invisible(x)}predict.smooth.spline <- function(object, x, deriv = 0, ...){if(missing(x)) {if(deriv == 0)return(object[c("x", "y")])else x <- object$x}fit <- object$fitif(is.null(fit)) stop("not a valid \"smooth.spline\" object")else predict(fit, x, deriv, ...)}predict.smooth.spline.fit <- function(object, x, deriv = 0, ...){if(missing(x))x <- seq.int(from = object$min, to = object$min + object$range,length.out = length(object$coef) - 4L)xs <- (x - object$min)/object$range # x scaled to [0,1]extrap.left <- xs < 0extrap.right <- xs > 1interp <- !(extrap <- extrap.left | extrap.right)n <- sum(interp) # number of xs in [0,1]y <- xsif(any(interp))y[interp] <- .Fortran(C_bvalus,n = as.integer(n),knot = as.double(object$knot),coef = as.double(object$coef),nk = as.integer(object$nk),x = as.double(xs[interp]),s = double(n),order = as.integer(deriv))$sif(any(extrap)) {xrange <- c(object$min, object$min + object$range)if(deriv == 0) {end.object <- Recall(object, xrange)$yend.slopes <- Recall(object, xrange, 1)$y * object$rangeif(any(extrap.left))y[extrap.left] <- end.object[1L] +end.slopes[1L] * (xs[extrap.left] - 0)if(any(extrap.right))y[extrap.right] <- end.object[2L] +end.slopes[2L] * (xs[extrap.right] - 1)} else if(deriv == 1) {end.slopes <- Recall(object, xrange, 1)$y * object$rangey[extrap.left] <- end.slopes[1L]y[extrap.right] <- end.slopes[2L]}else y[extrap] <- 0}if(deriv > 0)y <- y/(object$range^deriv)list(x = x, y = y)}supsmu <-function(x, y, wt = rep(1, n), span = "cv", periodic = FALSE, bass = 0, trace = FALSE){if(span == "cv") span <- 0else if(span < 0 || span > 1) stop("'span' must be between 0 and 1.")n <- length(y)if(!n || !is.numeric(y)) stop("'y' must be numeric vector")if(length(x) != n) stop("number of observations in 'x' and 'y' must match.")if(length(wt) != n)stop("number of weights must match number of observations.")if(periodic) {iper <- 2Lxrange <- range(x)if(xrange[1L] < 0 || xrange[2L] > 1)stop("'x' must be between 0 and 1 for periodic smooth")} else iper <- 1Lokay <- is.finite(x + y + wt)ord <- order(x[okay], y[okay])ord <- cumsum(!okay)[okay][ord] + ordxo <- x[ord]leno <- length(ord)if(leno == 0L)stop("no finite observations")if(diff <- n - leno)warning(sprintf(ngettext(diff,"%d observation with NA, NaN or Inf deleted","%d observations with NAs, NaNs and/or Infs deleted"),diff), domain = NA).Fortran(C_setsmu, as.integer(trace))smo <- .Fortran(C_supsmu,as.integer(leno),as.double(xo),as.double(y[ord]),as.double(wt[ord]),as.integer(iper),as.double(span),as.double(bass),smo=double(leno),double(n*7L), double(1L))$smo## eliminate duplicate xsort values and corresponding smoothed valuesdupx <- duplicated(xo)list(x = xo[!dupx], y = smo[!dupx])}