Newer
Older
# # Do this in a separate file to see the generated help:
# library(devtools)
# document()
# load_all(as.package("../../onlineforecast"))
# ?bspline
#' Compute base splines of a variable using the R function \code{splines::bs}, use in the transform stage.
#'
#' Simply wraps the \code{splines::bs}, such that it can be used in the transformation stage.
#'
#' See the help for all arguments with \code{?splines::bs}. NOTE that two arguments have different default values.
#'
#' See the example \url{https://onlineforecasting.org/examples/solar-power-forecasting.html} where the function is used in a model.
#'
#' @family Transform stage functions
#'
#' @param X data.frame (as part of data.list) with horizons as columns named \code{kxx} (i.e. one for each horizon)
#' @param Boundary.knots The value is NA: then the boundaries are set to the range of each horizons (columns in X). See \code{?splines::bs}
#' @param intercept See \code{?splines::bs}.
#' @param df See \code{?splines::bs}
#' @param knots See \code{?splines::bs}
#' @param degree See \code{?splines::bs}
#' @param bknots Is just a short for Boundary.knots and replace Boundary.knots (if Boundary.knots is not given)
#' @param periodic Default FALSE. If TRUE, then \code{pbs::pbs} is called and periodic splines are generated.
#' @return List of data frames with the computed base splines, each with columns for the same horizons as in X
#' @examples
#'
#' # How to make a diurnal curve using splines
#' # Select first 54 hours from the load data
#' D <- subset(Dbuilding, 1:76, kseq=1:4)
#' # Make the hour of the day as a forecast input
#' D$tday <- make_tday(D$t, kseq=1:4)
#' D$tday
#' # Calculate the base splines for each column in tday
#' L <- bspline(D$tday)
#' # Now L holds a data.frame for each base spline
#' str(L)
#' # Hence this will result in four inputs for the regression model
#' # Plot (note that the splines period starts at tday=0)
#' plot(D$t, L$bs1$k1, type="s")
#' for(i in 2:length(L)){
#' lines(D$t, L[[i]]$k1, col=i, type="s")
#' }
#'
#' # In a model formulation it will be:
#' model <- forecastmodel$new()
#' model$add_inputs(mutday = "bspline(tday)")
#' # Such that at the transform stage will give the same as above
#' model$transform_data(D)
#'
#' # Periodic splines are useful for modelling a diurnal harmonical functions
#' L <- bspline(D$tday, bknots=c(0,24), df=4, periodic=TRUE)
#' # or
#' L <- pbspline(D$tday, bknots=c(0,24), df=4)
#' # Note, how it has to have high enough df, else it generates an error
#'
#' # Plot
#' plot(D$t, L$bs1$k1, type="s")
#' for(i in 2:length(L)){
bspline <- function(X, Boundary.knots = NA, intercept = FALSE, df = NULL, knots = NULL, degree = 3, bknots = NA, periodic = FALSE) {
# bknots is just a short for Boundary.knots and replace if Boundary.knots are not given.
# If a list, then call on each element
if (class(X) == "list") {
# Call again for each element
val <- lapply(1:length(X), function(i) {
bspline(X[[i]], df = df, knots = knots, degree = degree, intercept = intercept,
Boundary.knots = Boundary.knots, periodic = periodic)
# X must be a data.frame or matrix
if(!(class(X) %in% c("data.frame","matrix"))){ stop("X must be a data.frame or matrix") }
# First find the horizons, they are used in the end
nms <- nams(X)
# All columns must be like "k12"
#if(length(grep("^[k|h][[:digit:]]+$", nms)) != length(nms)){ stop("All column names must indicate a horizon, so start with k and then an integer") }
# Run for each horizon and calculate the basis splines
L <- lapply(1:ncol(X), function(i) {
if (is.na(Boundary.knots[1])) {
Boundary.knots <- range(X[, i], na.rm=TRUE)
}
if(periodic){
# Periodic splines (annoyingly it use 'missing()' to conclude if the argument was given, not 'is.null()' as bs does)
if(is.null(knots)){
# Call without knots
spls <- pbs::pbs(X[, i], Boundary.knots = Boundary.knots, degree = degree, df = df,
intercept = intercept)
}else{
# Call without df
spls <- pbs::pbs(X[, i], Boundary.knots = Boundary.knots, degree = degree,
knots = knots, intercept = intercept)
}
}else{
spls <- splines::bs(X[, i], Boundary.knots = Boundary.knots, degree = degree, df = df,
knots = knots, intercept = intercept)
}
return(spls)
})
# Now we have a bs value in L for each horizon
# Separate each basespline in a data.frame with all horizons
L <- lapply(1:ncol(L[[1]]), function(i) {
tmp <- lapply(L, function(x) {
x[ ,i]
})
tmp <- data.frame(do.call("cbind", tmp))
nams(tmp) <- nms
return(tmp)
})
# Set the extra name
nams(L) <- pst("bs", 1:length(L))
return(L)
}
#' Wrapper for \code{bspline} with \code{periodic=TRUE}
#'
#' Simply a wrapper.
#'
#' @title Wrapper for \code{bspline} with \code{periodic=TRUE}
#' @param X see \code{?bspline}
#' @param Boundary.knots see \code{?bspline}
#' @param intercept see \code{?bspline}
#' @param df see \code{?bspline}
#' @param knots see \code{?bspline}
#' @param degree see \code{?bspline}
#' @param bknots see \code{?bspline}
#' @family Transform stage functions
#'
#' @export
pbspline <- function(X, Boundary.knots = NA, intercept = FALSE, df = NULL, knots = NULL, degree = 3, bknots = NA){
bspline(X, Boundary.knots = Boundary.knots, degree = degree, df = df,
knots = knots, intercept = intercept, bknots = bknots, periodic = TRUE)
}