Nothing
# Do this in a separate file to see the generated help:
#library(devtools)
#document()
#load_all(as.package("../../onlineforecast"))
#?plot_ts
#?plot_ts.data.list
#?plot_ts.data.frame
#' Plot time series of observations and forecasts (lagged to be aligned in time).
#'
#' Generates time series plots depending on the variables matched by each regular expression given in the \code{patterns} argument.
#'
#' The forecasts matrices in the \code{data.list} given in \code{object} will be lagged to be aligned in time (i.e. k-step forecasts will be lagged by k).
#'
#' Use the plotly package if argument \code{usely} is TRUE, see \code{\link{plotly_ts}()}.
#'
#' @title Time series plotting
#' @param object A \code{data.list} or \code{data.frame} with observations and forecasts, note diffe
#' @param patterns A character vector with regular expressions, see examples for use.
#' @param xlim The time range as a character of length 2 and form "YYYY-MM-DD" or POSIX. Date to start and end the plot.
#' @param ylims The \code{ylim} for each plot given in a list.
#' @param xlab A character with the label for the x-axis.
#' @param ylabs A character vector with labels for the y-axes.
#' @param mains A character vector with the main for each plot.
#' @param mainouter A character with the main at the top of the plot (can also be added afterwards with \code{title(main, outer=TRUE)}).
#' @param legendtexts A list with the legend texts for each plot (replaces the names of the variables).
#' @param colormaps A list of colormaps, which will be used in each plot.
#' @param xat POSIXt specifying where the ticks on x-axis should be put.
#' @param usely If TRUE then plotly will be used.
#' @param plotit If FALSE then the plot will not be generated, only data returned.
#' @param p The plot_ts parameters in a list, as generated with the function \code{\link{par_ts}()}.
#' @param ... Parameters passed to \code{\link{par_ts}}, see the list of parameters in \code{?\link{par_ts}}.
#' @seealso
#' \code{\link{par_ts}} for setting plot control parameters.
#'
#' \code{\link{regex}} for regular expressions to select which variables to plot.
#'
#' @return A list with a data.frame with the data for each plot, if usely=TRUE, then a list of the figures (drawn with print(subplot(L, shareX=TRUE, nrows=length(L), titleY = TRUE))).
#'
#' @examples
#'
#' # Time series plots for \code{data.list}, same as for \code{data.frame} except use of \code{kseq}
#' D <- Dbuilding
#' plot_ts(D, c("heatload","Ta"), kseq=c(1,24))
#' # Make two plots (and set the space for the legend)
#' plot_ts(D, c("heatload","Ta"), kseq=c(1,24), legendspace=11)
#' # Only the Ta observations
#' plot_ts(D, c("heatload","Taobs$"), kseq=c(1,24), legendspace=11)
#'
#' # Give labels
#' plot_ts(D, c("heatload","Ta"), kseq=c(1,24), xlab="Time", ylabs=c("Heat (kW)","Temperature (C)"))
#' # Mains (see mainsline in par_ts())
#' plot_ts(D, c("heatload","Ta"), kseq=c(1,24), mains=c("Heatload","Temperature"), mainsline=c(-1,-2))
#'
#' # Format of the xaxis (see par_ts())
#' plot_ts(D, c("heatload","Ta"), kseq=c(1,24), xaxisformat="%Y-%m-%d %H:%m")
#'
#' # Return the data, for other plots etc.
#' L <- plot_ts(D, c("heatload","Ta"), kseq=c(1,24))
#' names(L[[1]])
#' names(L[[2]])
#'
#'
#' @rdname plot_ts
#' @export
plot_ts <- function(object, patterns=".*", xlim = NA, ylims = NA, xlab = "", ylabs = NA,
mains = "", mainouter="", legendtexts = NA, colormaps = NA, xat = NA, usely = FALSE, plotit = TRUE, p = NA, ...){
UseMethod("plot_ts")
}
#' @param kseq For \code{class(object)=="data.list"} an integer vector, default = NA. Control which forecast horizons to include in the plots. If NA all the horizons will be included.
#' @rdname plot_ts
#' @export
plot_ts.data.list <- function(object, patterns=".*", xlim = NA, ylims = NA, xlab = "", ylabs = NA,
mains = "", mainouter="", legendtexts = NA, colormaps = NA, xat = NA, usely=FALSE, plotit = TRUE, p=NA, kseq = NA, ...) {
# Take par_ts setup parameters from options if there
p <- par_ts(fromoptions=TRUE, p=p, ...)
#
DL <- object
# Do a bit of checking
if(is.null(DL$t)){ stop("No 't' in the data.list.")}
# Should a subset be taken according to xlim?
if(!is.na(xlim[1]) | length(xlim) > 1){
if(is.na(xlim[1])) { xlim[1] <- DL$t[1] }
if(length(xlim) == 1) { xlim[2] <- as.character(DL$t[length(DL$t)]) }
DL <- subset(DL, in_range(xlim[1], DL$t, xlim[2]))
}
# More checking
if(length(DL$t) == 0){ stop(pst("No data in the time range. ",xlim[1]," to ",xlim[2]))}
# set kseq if not specified
if( is.na(kseq[1]) ){
tmp <- unique(unlist(lapply(DL, function(x){names(x)})))
tmp <- tmp[grep("[k|h][[:digit:]]+$", tmp)]
if( length(tmp) > 0 ){ kseq <- sort(as.integer(gsub("[k|h]","",tmp))) }
}
# Generate a data.frame with the series to be plotted
X <- lapply_cbind_df(patterns, function(pattern) {
# Find the variables to plot
nms <- grep(pattern, names(DL), value = TRUE)
#
if(length(nms) == 0){
warning("No names where found matching the pattern '",pattern,"'")
tmp <- as.data.frame(matrix(NA,nrow=length(DL$t),ncol=1))
names(tmp)[1] <- pattern
return(tmp)
}else{
# Go through the names in nms
do.call("cbind", lapply(nms, function(nm){
if(is.null(dim(DL[[nm]]))) {
# It is a vector, just return it
X <- data.frame(DL[[nm]])
names(X) <- nm
return(X)
} else {
# Its a matrix
# Find the columns with 'k' and digits
# Note the convention:
# - starting with 'k' it's a forecast for t+k, and must be lagged to sync
# - if it starts with 'h' then it's an observation for the k'th horizon and for
helper <- function(prefix){
i <- which(nams(DL[[nm]]) %in% pst(prefix,kseq))
if(length(i) > 0) {
X <- DL[[nm]][ ,i]
# Started with k, then it's forecasts and must be lagged to sync
if( prefix == "k" ){
ks <- as.integer(gsub("k","",nams(DL[[nm]])[i]))
X <- lagdf(X, lagseq=ks)
}
# Fix if it is a vector
if(is.null(dim(X))) {
X <- as.data.frame(X)
names(X) <- nams(DL[[nm]])[i]
}
nams(X) <- pst(nm, "_", nams(X))
return(X)
}else{
return(NULL)
}
}
X <- helper("k")
if(is.null(X[1])){
X <- helper("h")
}
if(is.null(X[1])){
# Not started with "k" or "h": Just take all columns
X <- as.data.frame(DL[[nm]])
names(X) <- pst(nm,"_",names(X))
}
return(X)
}
}))
}
})
#
if(any(duplicated(nams(X)))){
X <- X[ ,unique(nams(X))]
}
# Add "t" for the x-axis
X$t <- DL$t
# Since we added "_k" or "_h" to the matrix variables, we have to
# strip it off, and pass on as the names to search for with patterns
namesdata <- unlist(getse(strsplit(nams(X), "_k|_h"), 1))
# Use the plot_ts function which takes the data.frame
plot_ts.data.frame(X, patterns, ylims = ylims, xlab = xlab, ylabs = ylabs, mains = mains, mainouter = mainouter,
legendtexts = legendtexts, colormaps=colormaps, xat = xat, usely=usely, plotit = plotit, p=p, namesdata=namesdata, ...)
}
# Plot all with prefix
#' @param namesdata For \code{class(object)=="data.frame"} a character vector. Names of columns in object to be searched in, instead of \code{names(object)}.
#' @rdname plot_ts
#' @importFrom graphics par title
#' @export
plot_ts.data.frame <- function(object, patterns=".*", xlim = NA, ylims = NA, xlab = "", ylabs = NA,
mains = NA, mainouter="", legendtexts = NA, colormaps = NA, xat = NA,
usely=FALSE, plotit = TRUE, p = NA, namesdata=NA, ...) {
# Take par_ts setup parameters from options if there
p <- par_ts(fromoptions=TRUE, p=p, ...)
#
data <- object
# Do a bit of checking
if(nrow(data) == 0){ stop("No rows in the data.frame.")}
if(is.null(data[ ,p$xnm])){ warning("No 't' or xnm found. If time is not in 't', then specify it in xnm (either as argument or in options(\"par_ts\").")}
#
if(!is.null(data[ ,p$xnm])){
if(is.na(xlim[1])) { xlim[1] <- data[1,p$xnm]-1 } # if the xlim min is na, then take all points, so -1 since time point is always end of sampling (assumed in in_range)
if(length(xlim)==1) { xlim[2] <- data[nrow(data),p$xnm] }
data <- data[in_range(xlim[1], data[ ,p$xnm], xlim[2]), ]
}
# More checking
if(nrow(data) == 0){ stop(pst("No data in the time range. ",xlim[1]," to ",xlim[2]))}
# Extend all individual plots vars, if not set
if(is.na(mains[1]) & length(mains)==1){ mains <- rep(NA,length(patterns)) }
mainsline <- p$mainsline
if(is.na(mainsline[1]) & length(mainsline)==1){ mainsline <- rep(NA,length(patterns)) }
if(is.na(ylims[1]) & length(ylims)==1){ ylims <- as.list(rep(NA,length(patterns))) }
if(is.na(ylabs[1]) & length(ylabs)==1){ ylabs <- rep(NA,length(patterns)) }
if(is.na(legendtexts[1]) & length(legendtexts)==1){ legendtexts <- as.list(rep(NA,length(patterns))) }
if(is.na(colormaps[1]) & length(colormaps)==1){ colormaps <- as.list(rep(NA,length(patterns))) }
#
if(usely){
# with plotly
if(!requireNamespace("plotly", quietly = TRUE)){
# loadNamespace("plotly")
# }else{
stop("The plotly package must be installed.")
}
#
Liseq <- list()
for(ii in 1:length(patterns)) {
Liseq[[ii]] <- plot_ts_iseq(data, patterns[ii], p$xnm, namesdata)
}
nlines <- length(unlist(Liseq))
# Longest name
maxchar <- max(nchar(names(data)[unique(unlist(Liseq))]))
#colormap <- p$colorramp(nlines)
#
L <- list()
# This is hacky, but take the operator from the namespce directly (cannot plotly::%>% below. And don't want to plotly in imports, since it's big and don't want to force installation)
'%>%' <- plotly::'%>%'
#
for(ii in 1:length(patterns)) {
iseq <- Liseq[[ii]]
#
fig <- plotly::plot_ly(x=data[ ,p$xnm])
for(i in 1:length(iseq)){
fig <- fig %>% plotly::add_lines(y = data[ ,iseq[i]],
name = names(data)[iseq[i]],
# color = colormap[iii]
)#, legendgroup = paste0('group',ii))
}
if(ii < length(patterns)){
# Add empty to make legend gap
fig <- fig %>% plotly::add_lines(y = rep("NA",nrow(data)), name=strrep("-",maxchar), line=list(color="white"))
}
# Add ylabs?
if(!is.na(ylabs)[1]){
fig <- fig %>% plotly::layout(yaxis=list(title=ylabs[ii]))
}
fig <- fig %>% plotly::layout(xaxis=list(title=xlab))
# Keep it
L[[ii]] <- fig
}
# Center legend
L[[ii]] <- L[[ii]] %>% plotly::layout(legend = list(x = 100, y = 0.5))
# Draw it
if(plotit){
print(plotly::subplot(L, shareX=TRUE, nrows=length(L), titleY = TRUE))
}
# Return if needed
invisible(L)
}else{
#
oldpar <- setpar("ts", mfrow=c(length(patterns),1), cex=p$cex)
par(xpd=TRUE, mar = c(0, 4, 0.5, p$legendspace))
on.exit(par(oldpar))
#
L <- lapply(1:length(patterns), function(i){
df <- plot_ts_series(data, patterns[i], iplot=i, ylim=ylims[[i]], xlab=xlab, legendtext = legendtexts[[i]],
main=mains[i], mainline=mainsline[i], colormap=colormaps[[i]], xat = xat, plotit=plotit, p=p, namesdata=namesdata,
xaxis=(i==length(patterns)), ...)
title(mainouter, outer=TRUE)
if (!is.na(ylabs[1])){
title(ylab = ylabs[i], yaxt = "s")
}
return(df)
})
invisible(L)
}
}
#' @rdname plot_ts
#' @importFrom graphics par title
#' @export
plot_ts.matrix <- plot_ts.data.frame
#' @rdname plot_ts
#' @param data The data.frame from where to find columns to plot
#' @param xnm The name of the column
#' @param pattern The regex pattern
plot_ts_iseq <- function(data, pattern, xnm, namesdata){
iseq <- integer(0)
# Use these names when finding columns to plot
if(is.na(namesdata)[1]){
nms <- nams(data)
}else{
nms <- namesdata
}
# Find indexes of patterns in data
# Do the for loop, to secure the order of the patterns between "|"s
for (pf in strsplit(pattern, "\\|")[[1]]) {
iseq <- c(iseq, grep(pf, nms))
}
# Only take unique (keeps the order)
iseq <- unique(iseq)
#
# Remove p$xnm if in nms
iseq <- iseq[!nms[iseq] == xnm]
#
return(iseq)
}
# Plot all columns found with regex pattern
#' @importFrom graphics plot lines axis title axis.POSIXct mtext par legend
#' @rdname plot_ts
#' @param iplot The plot index
#' @param ylim ylim in the plot
#' @param main main title
#' @param mainline line for the main title
#' @param colormap the colormap for the lines
#' @param legendtext Text for the legend
#' @param xaxis Boolean determining to include the xaxis
plot_ts_series <- function(data, pattern, iplot = 1,
ylim = NA, xlab = "", main = "", mainline = -1.2, colormap = NA, legendtext = NA,
xat = NA, plotit = TRUE, p = NA, namesdata = NA, xaxis = TRUE, ...) {
#
# Take par_ts setup parameters from options or defaults
p <- par_ts(fromoptions=TRUE, p=p, ...)
#
iseq <- plot_ts_iseq(data, pattern, p$xnm, namesdata)
# Check if p$xnm is in the data
if (any(names(data) == p$xnm)) {
x <- data[, p$xnm]
} else {
x <- 1:nrow(data)
}
#
if(plotit){
#
xlim <- c(min(x)+diff(range(x))*0.02, max(x))
if(any(is.na(xlim))){ stop("Could not calculate range of x, probably there is NA in t!") }
#
if (all(is.na(iseq)) | length(iseq)==0){
# No series to plot
legendtext <- pst(pattern," not found")
colormap <- 1
ylim <- c(0,1)
yat <- NA
}else{
# Limits on y-axis
if (is.na(ylim[1])) {
# For some weird reason range doesn't work with a data.frame of logicals, it works only on a vector of logicals
if(all(sapply(iseq, function(i){ is.logical(data[, i]) }))){
# All are logicals
ylim <- c(0,1)
}else{
ylim <- range(data[, iseq], na.rm = TRUE)
if(any(is.na(ylim)) | any(is.infinite(ylim))){
legendtext <- pst(pattern," all NA")
colormap <- 1
ylim <- c(0,1)
yat <- NA
}
}
}
#
if(is.na(colormap[1])){
colormap <- p$colorramp(length(iseq))
}
#
# EXTEND THE YLIM: to make room for multiple plots
ylim <- ylim + c(-1,1) * diff(ylim) * p$ylimextend
# for axis
ylimmod <- ylim + c(-1,1) * diff(ylim) * p$yaxisextend
#
# Make the legend text
if (p$legendrangeshow & is.na(legendtext[1])){
rngtext <- do.call("rbind", lapply(1:length(iseq), function(i) {
tmp <- sapply(range(data[, iseq[i]], na.rm = TRUE), function(x){
if( x <= 10 ){ return(signif(x, digits = 2)) }else{ return(round(x)) } })
gsub("\\s+"," ",paste(tmp, collapse = " to "), perl=TRUE)
}))
legendtext <- paste0(nams(data)[iseq], ": ", rngtext)
}else{
legendtext <- paste0(nams(data)[iseq])
}
}
#
plot(x, x, type = "n", xlim = xlim, ylim = ylim, xaxs="r", yaxt = "n", bty = "n",
xlab = "", ylab = "")
# For grid
xb <- c(xlim[1]-0.04*diff(xlim), xlim[2]+0.01*diff(xlim))
# yb <- c(ylim[1]-0.04*diff(ylim), ylim[2]+0.01*diff(ylim))
if(all(sapply(data[ ,iseq], class) == "logical")){
# Its all logical
yb <- c(0,1)
}else{
yb <- range(data[ ,iseq], na.rm=TRUE) #c(ylim[1]-0.04*diff(ylim), max(data[ ,iseq],na.rm=TRUE))#, ylim[2]+0.01*diff(ylim))
}
# BACKGROUND
# polygon(c(xb[1],xb,rev(xb)), c(yb,rev(yb),yb[1]), col="grey95", lty=0)
# GRID
if(is.na(xat)){
xat <- pretty(x)
irm <- which(xat < min(x) | xat > max(x))
if(length(irm)){ xat <- xat[-irm] }
}
if(!"yat" %in% ls()){
# Where to put y grid and labels
yat <- pretty(ylimmod, 3)
yat <- yat[(ylim[1]-0.04*diff(ylim)) < yat]
yat <- yat[yat <= ylim[2]]
sapply(yat, function(y){ lines(xb,c(y,y), col="lightgrey", lty="dotted") })
axis(2, yat, lwd=0, lwd.ticks=1)
lines(rep(xb[1],2), range(yat))
}
# horizontal grid
ygrid <- c(ylim[1]-0.04*diff(ylim), max(yb,yat))
sapply(xat, function(x){ lines(c(x,x),ygrid,col="lightgrey",lty="dotted") })
#
# PLOT LINES
if (!all(is.na(iseq))){
for (i in 1:length(iseq)) {
p$plotfun(x, data[, iseq[i]], col = colormap[i])
}
}
title(main = main, line=mainline)
#
# Make the xaxis
if( xaxis ){
if (any(nams(data) == p$xnm)) {
if (class(data[ ,p$xnm])[1] != "POSIXct") {
axis(1, data[ ,p$xnm], xaxt = "s")
} else {
# makes too few ticks: axis.POSIXct(1, data[ ,p$xnm], format = xaxisformat, xaxt = "s")
if(is.na(xat[1])){ xat <- pretty(data[ ,p$xnm]) }
# Format, per default NA, so make it here
xaxisformat <- p$xaxisformat
if(is.na(xaxisformat)){
if( all(as.numeric(xat,unit="secs") %% (24*3600) == 0) ){
xaxisformat <- "%Y-%m-%d"
}else{
xaxisformat <- "%Y-%m-%d %H:%M"
}
}
axis.POSIXct(1, data[ ,p$xnm], at = xat, format = xaxisformat, xaxt = "s", lwd=1, lwd.ticks=1)
}
} else {
axis(1, 1:nrow(data), xaxt = "s", lwd=0, lwd.ticks=1)
}
mtext(xlab, 1, line=par()$mgp[1]-0.5)
}
#
legend(x=xlim[2]+2*0.01*diff(xlim), y=ylim[2], legend=legendtext, lty = 1, col = colormap, cex = p$legendcex, bg="white", box.lwd=0.5)
}
#
invisible(cbind(t=x, data[, iseq]))
}
#' Plot forecasts, residuals, cumulated residuals and RLS coefficients
#'
#' A useful plot for residual analysis and model validation of an RLS fitted forecast model.
#'
#' All parameters, except those described below, are simply passed to \code{\link{plot_ts}()}.
#'
#' @title Plots for an rls_fit.
#' @param fit An \code{rls_fit}.
#' @param patterns See \code{\link{plot_ts}}. The default pattern finds the generated series in the function, '!!RLSinputs!!' will be replaced with the names of the RLS inputs (regression stage inputs).
#' @return The plotted data in a \code{data.list}.
#'
#' @seealso \code{\link{plot_ts}}.
#' @examples
#'
#' # Fit a model (see vignette 'setup-and-use-model'
#' D <- Dbuilding
#' D$scoreperiod <- in_range("2010-12-22", D$t)
#' model <- forecastmodel$new()
#' model$output = "heatload"
#' model$add_inputs(Ta = "Ta",
#' mu = "one()")
#' model$add_regprm("rls_prm(lambda=0.9)")
#' model$kseq <- c(3,18)
#' fit1 <- rls_fit(NA, model, D, returnanalysis = TRUE)
#'
#' # Plot it
#' plot_ts(fit1)
#'
#' # Return the data
#' Dplot <- plot_ts(fit1)
#'
#' # The RLS coefficients are now in a nice format
#' head(Dplot$mu)
#'
#' @rdname plot_ts
#' @export
plot_ts.rls_fit <- function(object, patterns = c("^y$|^Yhat$","^Residuals$","CumAbsResiduals$",pst("^",names(fit$Lfitval[[1]]),"$")),
xlim = NA, ylims = NA, xlab = "", ylabs = NA, mains = "", mainouter="", legendtexts = NA,
colormaps = NA, xat = NA, usely=FALSE, plotit=TRUE, p=NA, kseq = NA, ...){
fit <- object
# Calculate the residuals
Residuals <- residuals(fit)
# Prepares a data.list for the plot
if(is.na(xlim[1])){
if(!("scoreperiod" %in% names(fit$data))){
isubset <- c(1,length(fit$data$t))
}else{
# Default is to plot the scoreperiod if there
isubset <- which(fit$data$scoreperiod)
}
isubset <- min(isubset,na.rm=TRUE):max(isubset,na.rm=TRUE)#1:length(fit$data$t)
}else{
isubset <- in_range(xlim[1], fit$data$t, xlim[2])
}
if(is.na(kseq[1])){
kseq <- fit$model$kseq
}
#
CumAbsResiduals <- lapply_cbind_df(kseq, function(k){
tmp <- abs(Residuals[isubset,pst("h",k)])
tmp[is.na(tmp)] <- 0
cumsum(tmp)
})
names(CumAbsResiduals) <- pst("h",kseq)
#
# Convert the parameter estimates
nmsinput <- names(fit$Lfitval[[1]])
tmp <- lapply(1:length(nmsinput), function(i){
# Name of the input
nm <- names(fit$Lfitval[[1]])[i]
# Take the fitues each horizon for the input
ik <- which(names(fit$Lfitval) %in% pst("k",kseq))
X <- lapply_cbind_df(ik, function(ii){
fit$Lfitval[[ii]][isubset,nm]
})
names(X) <- gsub("k","h",names(fit$Lfitval)[ik])
return(X)
})
names(tmp) <- nmsinput
#
data <- list(y=fit$data[[fit$model$output]][isubset], Yhat=fit$Yhat[isubset,pst("k",kseq)], Residuals=Residuals[isubset,pst("h",kseq)], CumAbsResiduals=CumAbsResiduals)
data <- c(data,tmp)
data$t <- fit$data$t[isubset]
class(data) <- "data.list"
if(plotit){
#
if(is.na(ylabs[1])){
ylabs <- c("Model output","Residuals","Cum. residuals",pst("Coef: ",nmsinput))
}
# The input names
#patterns[patterns == "!!RLSinputs!!"] <- pst("^",nmsinput,"$"))
# Make a plot of the RLS coefficients for each horizon
plot_ts(data, patterns, xlim = xlim, ylims = ylims, xlab = xlab, ylabs = ylabs,
mains = mains, mainouter=mainouter, legendtexts = legendtexts, colormaps=colormaps, xat = xat, usely=usely, p=p, kseq = kseq, ...)
}
# Return the data
invisible(data)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.