Merge of v0.3 into master
|
|
@ -1,21 +1,23 @@
|
||||||
Package: prophet
|
Package: prophet
|
||||||
Title: Automatic Forecasting Procedure
|
Title: Automatic Forecasting Procedure
|
||||||
Version: 0.2.1.9000
|
Version: 0.3
|
||||||
Date: 2017-11-08
|
Date: 2018-06-01
|
||||||
Authors@R: c(
|
Authors@R: c(
|
||||||
person("Sean", "Taylor", email = "sjt@fb.com", role = c("cre", "aut")),
|
person("Sean", "Taylor", email = "sjt@fb.com", role = c("cre", "aut")),
|
||||||
person("Ben", "Letham", email = "bletham@fb.com", role = "aut")
|
person("Ben", "Letham", email = "bletham@fb.com", role = "aut")
|
||||||
)
|
)
|
||||||
Description: Implements a procedure for forecasting time series data based on
|
Description: Implements a procedure for forecasting time series data based on
|
||||||
an additive model where non-linear trends are fit with yearly and weekly
|
an additive model where non-linear trends are fit with yearly, weekly, and
|
||||||
seasonality, plus holidays. It works best with daily periodicity data with
|
daily seasonality, plus holiday effects. It works best with time series
|
||||||
at least one year of historical data. Prophet is robust to missing data,
|
that have strong seasonal effects and several seasons of historical data.
|
||||||
shifts in the trend, and large outliers.
|
Prophet is robust to missing data and shifts in the trend, and typically
|
||||||
|
handles outliers well.
|
||||||
Depends:
|
Depends:
|
||||||
R (>= 3.2.3),
|
R (>= 3.2.3),
|
||||||
Rcpp (>= 0.12.0)
|
Rcpp (>= 0.12.0)
|
||||||
Imports:
|
Imports:
|
||||||
dplyr (>= 0.5.0),
|
dplyr (>= 0.5.0),
|
||||||
|
dygraphs (>= 1.1.1.4),
|
||||||
extraDistr,
|
extraDistr,
|
||||||
ggplot2,
|
ggplot2,
|
||||||
grid,
|
grid,
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@
|
||||||
|
|
||||||
S3method(plot,prophet)
|
S3method(plot,prophet)
|
||||||
S3method(predict,prophet)
|
S3method(predict,prophet)
|
||||||
|
export(add_changepoints_to_plot)
|
||||||
export(add_regressor)
|
export(add_regressor)
|
||||||
export(add_seasonality)
|
export(add_seasonality)
|
||||||
export(cross_validation)
|
export(cross_validation)
|
||||||
export(fit.prophet)
|
export(fit.prophet)
|
||||||
export(make_future_dataframe)
|
export(make_future_dataframe)
|
||||||
|
export(performance_metrics)
|
||||||
|
export(plot_cross_validation_metric)
|
||||||
export(plot_forecast_component)
|
export(plot_forecast_component)
|
||||||
export(predictive_samples)
|
export(predictive_samples)
|
||||||
export(prophet)
|
export(prophet)
|
||||||
export(prophet_plot_components)
|
export(prophet_plot_components)
|
||||||
export(simulated_historical_forecasts)
|
|
||||||
import(Rcpp)
|
import(Rcpp)
|
||||||
importFrom(dplyr,"%>%")
|
importFrom(dplyr,"%>%")
|
||||||
|
|
|
||||||
|
|
@ -11,102 +11,50 @@ globalVariables(c(
|
||||||
|
|
||||||
#' Generate cutoff dates
|
#' Generate cutoff dates
|
||||||
#'
|
#'
|
||||||
#' @param df Dataframe with historical data
|
#' @param df Dataframe with historical data.
|
||||||
#' @param horizon timediff forecast horizon
|
#' @param horizon timediff forecast horizon.
|
||||||
#' @param k integer number of forecast points
|
#' @param initial timediff initial window.
|
||||||
#' @param period timediff Simulated forecasts are done with this period.
|
#' @param period timediff Simulated forecasts are done with this period.
|
||||||
#'
|
#'
|
||||||
#' @return Array of datetimes
|
#' @return Array of datetimes.
|
||||||
#'
|
#'
|
||||||
#' @keywords internal
|
#' @keywords internal
|
||||||
generate_cutoffs <- function(df, horizon, k, period) {
|
generate_cutoffs <- function(df, horizon, initial, period) {
|
||||||
# Last cutoff is (latest date in data) - (horizon).
|
# Last cutoff is (latest date in data) - (horizon).
|
||||||
cutoff <- max(df$ds) - horizon
|
cutoff <- max(df$ds) - horizon
|
||||||
if (cutoff < min(df$ds)) {
|
|
||||||
stop('Less data than horizon.')
|
|
||||||
}
|
|
||||||
tzone <- attr(cutoff, "tzone") # Timezone is wiped by putting in array
|
tzone <- attr(cutoff, "tzone") # Timezone is wiped by putting in array
|
||||||
result <- c(cutoff)
|
result <- c(cutoff)
|
||||||
if (k > 1) {
|
while (result[length(result)] >= min(df$ds) + initial) {
|
||||||
for (i in 2:k) {
|
cutoff <- cutoff - period
|
||||||
cutoff <- cutoff - period
|
# If data does not exist in data range (cutoff, cutoff + horizon]
|
||||||
# If data does not exist in data range (cutoff, cutoff + horizon]
|
if (!any((df$ds > cutoff) & (df$ds <= cutoff + horizon))) {
|
||||||
if (!any((df$ds > cutoff) & (df$ds <= cutoff + horizon))) {
|
|
||||||
# Next cutoff point is 'closest date before cutoff in data - horizon'
|
# Next cutoff point is 'closest date before cutoff in data - horizon'
|
||||||
closest.date <- max(df$ds[df$ds <= cutoff])
|
closest.date <- max(df$ds[df$ds <= cutoff])
|
||||||
cutoff <- closest.date - horizon
|
cutoff <- closest.date - horizon
|
||||||
}
|
|
||||||
if (cutoff < min(df$ds)) {
|
|
||||||
warning('Not enough data for requested number of cutoffs! Using ', i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result <- c(result, cutoff)
|
|
||||||
}
|
}
|
||||||
|
result <- c(result, cutoff)
|
||||||
|
}
|
||||||
|
result <- utils::head(result, -1)
|
||||||
|
if (length(result) == 0) {
|
||||||
|
stop(paste(
|
||||||
|
'Less data than horizon after initial window.',
|
||||||
|
'Make horizon or initial shorter.'
|
||||||
|
))
|
||||||
}
|
}
|
||||||
# Reset timezones
|
# Reset timezones
|
||||||
attr(result, "tzone") <- tzone
|
attr(result, "tzone") <- tzone
|
||||||
|
message(paste(
|
||||||
|
'Making', length(result), 'forecasts with cutoffs between',
|
||||||
|
result[length(result)], 'and', result[1]
|
||||||
|
))
|
||||||
return(rev(result))
|
return(rev(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#' Simulated historical forecasts.
|
|
||||||
#'
|
|
||||||
#' Make forecasts from k historical cutoff points, working backwards from
|
|
||||||
#' (end - horizon) with a spacing of period between each cutoff.
|
|
||||||
#'
|
|
||||||
#' @param model Fitted Prophet model.
|
|
||||||
#' @param horizon Integer size of the horizon
|
|
||||||
#' @param units String unit of the horizon, e.g., "days", "secs".
|
|
||||||
#' @param k integer number of forecast points
|
|
||||||
#' @param period Integer amount of time between cutoff dates. Same units as
|
|
||||||
#' horizon. If not provided, will use 0.5 * horizon.
|
|
||||||
#'
|
|
||||||
#' @return A dataframe with the forecast, actual value, and cutoff date.
|
|
||||||
#'
|
|
||||||
#' @export
|
|
||||||
simulated_historical_forecasts <- function(model, horizon, units, k,
|
|
||||||
period = NULL) {
|
|
||||||
df <- model$history
|
|
||||||
horizon <- as.difftime(horizon, units = units)
|
|
||||||
if (is.null(period)) {
|
|
||||||
period <- horizon / 2
|
|
||||||
} else {
|
|
||||||
period <- as.difftime(period, units = units)
|
|
||||||
}
|
|
||||||
cutoffs <- generate_cutoffs(df, horizon, k, period)
|
|
||||||
predicts <- data.frame()
|
|
||||||
for (i in 1:length(cutoffs)) {
|
|
||||||
cutoff <- cutoffs[i]
|
|
||||||
# Copy the model
|
|
||||||
m <- prophet_copy(model, cutoff)
|
|
||||||
# Train model
|
|
||||||
history.c <- dplyr::filter(df, ds <= cutoff)
|
|
||||||
m <- fit.prophet(m, history.c)
|
|
||||||
# Calculate yhat
|
|
||||||
df.predict <- dplyr::filter(df, ds > cutoff, ds <= cutoff + horizon)
|
|
||||||
# Get the columns for the future dataframe
|
|
||||||
columns <- c('ds')
|
|
||||||
if (m$growth == 'logistic') {
|
|
||||||
columns <- c(columns, 'cap')
|
|
||||||
if (m$logistic.floor) {
|
|
||||||
columns <- c(columns, 'floor')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns <- c(columns, names(m$extra_regressors))
|
|
||||||
future <- df[columns]
|
|
||||||
yhat <- stats::predict(m, future)
|
|
||||||
# Merge yhat, y, and cutoff.
|
|
||||||
df.c <- dplyr::inner_join(df.predict, yhat, by = "ds")
|
|
||||||
df.c <- dplyr::select(df.c, ds, y, yhat, yhat_lower, yhat_upper)
|
|
||||||
df.c$cutoff <- cutoff
|
|
||||||
predicts <- rbind(predicts, df.c)
|
|
||||||
}
|
|
||||||
return(predicts)
|
|
||||||
}
|
|
||||||
|
|
||||||
#' Cross-validation for time series.
|
#' Cross-validation for time series.
|
||||||
#'
|
#'
|
||||||
#' Computes forecasts from historical cutoff points. Beginning from initial,
|
#' Computes forecasts from historical cutoff points. Beginning from
|
||||||
#' makes cutoffs with a spacing of period up to (end - horizon).
|
#' (end - horizon), works backwards making cutoffs with a spacing of period
|
||||||
|
#' until initial is reached.
|
||||||
#'
|
#'
|
||||||
#' When period is equal to the time interval of the data, this is the
|
#' When period is equal to the time interval of the data, this is the
|
||||||
#' technique described in https://robjhyndman.com/hyndsight/tscv/ .
|
#' technique described in https://robjhyndman.com/hyndsight/tscv/ .
|
||||||
|
|
@ -124,8 +72,9 @@ simulated_historical_forecasts <- function(model, horizon, units, k,
|
||||||
#' @export
|
#' @export
|
||||||
cross_validation <- function(
|
cross_validation <- function(
|
||||||
model, horizon, units, period = NULL, initial = NULL) {
|
model, horizon, units, period = NULL, initial = NULL) {
|
||||||
te <- max(model$history$ds)
|
df <- model$history
|
||||||
ts <- min(model$history$ds)
|
te <- max(df$ds)
|
||||||
|
ts <- min(df$ds)
|
||||||
if (is.null(period)) {
|
if (is.null(period)) {
|
||||||
period <- 0.5 * horizon
|
period <- 0.5 * horizon
|
||||||
}
|
}
|
||||||
|
|
@ -135,12 +84,235 @@ cross_validation <- function(
|
||||||
horizon.dt <- as.difftime(horizon, units = units)
|
horizon.dt <- as.difftime(horizon, units = units)
|
||||||
initial.dt <- as.difftime(initial, units = units)
|
initial.dt <- as.difftime(initial, units = units)
|
||||||
period.dt <- as.difftime(period, units = units)
|
period.dt <- as.difftime(period, units = units)
|
||||||
k <- ceiling(
|
|
||||||
as.double((te - horizon.dt) - (ts + initial.dt), units='secs') /
|
cutoffs <- generate_cutoffs(df, horizon.dt, initial.dt, period.dt)
|
||||||
as.double(period.dt, units = 'secs')
|
predicts <- data.frame()
|
||||||
)
|
for (i in 1:length(cutoffs)) {
|
||||||
if (k < 1) {
|
cutoff <- cutoffs[i]
|
||||||
stop('Not enough data for specified horizon, period, and initial.')
|
# Copy the model
|
||||||
|
m <- prophet_copy(model, cutoff)
|
||||||
|
# Train model
|
||||||
|
history.c <- dplyr::filter(df, ds <= cutoff)
|
||||||
|
if (nrow(history.c) < 2) {
|
||||||
|
stop('Less than two datapoints before cutoff. Increase initial window.')
|
||||||
|
}
|
||||||
|
m <- fit.prophet(m, history.c)
|
||||||
|
# Calculate yhat
|
||||||
|
df.predict <- dplyr::filter(df, ds > cutoff, ds <= cutoff + horizon.dt)
|
||||||
|
# Get the columns for the future dataframe
|
||||||
|
columns <- 'ds'
|
||||||
|
if (m$growth == 'logistic') {
|
||||||
|
columns <- c(columns, 'cap')
|
||||||
|
if (m$logistic.floor) {
|
||||||
|
columns <- c(columns, 'floor')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columns <- c(columns, names(m$extra_regressors))
|
||||||
|
future <- df.predict[columns]
|
||||||
|
yhat <- stats::predict(m, future)
|
||||||
|
# Merge yhat, y, and cutoff.
|
||||||
|
df.c <- dplyr::inner_join(df.predict, yhat, by = "ds")
|
||||||
|
df.c <- dplyr::select(df.c, ds, y, yhat, yhat_lower, yhat_upper)
|
||||||
|
df.c$cutoff <- cutoff
|
||||||
|
predicts <- rbind(predicts, df.c)
|
||||||
}
|
}
|
||||||
return(simulated_historical_forecasts(model, horizon, units, k, period))
|
return(predicts)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Copy Prophet object.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model object.
|
||||||
|
#' @param cutoff Date, possibly as string. Changepoints are only retained if
|
||||||
|
#' changepoints <= cutoff.
|
||||||
|
#'
|
||||||
|
#' @return An unfitted Prophet model object with the same parameters as the
|
||||||
|
#' input model.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
prophet_copy <- function(m, cutoff = NULL) {
|
||||||
|
if (is.null(m$history)) {
|
||||||
|
stop("This is for copying a fitted Prophet object.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m$specified.changepoints) {
|
||||||
|
changepoints <- m$changepoints
|
||||||
|
if (!is.null(cutoff)) {
|
||||||
|
cutoff <- set_date(cutoff)
|
||||||
|
changepoints <- changepoints[changepoints <= cutoff]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changepoints <- NULL
|
||||||
|
}
|
||||||
|
# Auto seasonalities are set to FALSE because they are already set in
|
||||||
|
# m$seasonalities.
|
||||||
|
m2 <- prophet(
|
||||||
|
growth = m$growth,
|
||||||
|
changepoints = changepoints,
|
||||||
|
n.changepoints = m$n.changepoints,
|
||||||
|
changepoint.range = m$changepoint.range,
|
||||||
|
yearly.seasonality = FALSE,
|
||||||
|
weekly.seasonality = FALSE,
|
||||||
|
daily.seasonality = FALSE,
|
||||||
|
holidays = m$holidays,
|
||||||
|
seasonality.mode = m$seasonality.mode,
|
||||||
|
seasonality.prior.scale = m$seasonality.prior.scale,
|
||||||
|
changepoint.prior.scale = m$changepoint.prior.scale,
|
||||||
|
holidays.prior.scale = m$holidays.prior.scale,
|
||||||
|
mcmc.samples = m$mcmc.samples,
|
||||||
|
interval.width = m$interval.width,
|
||||||
|
uncertainty.samples = m$uncertainty.samples,
|
||||||
|
fit = FALSE
|
||||||
|
)
|
||||||
|
m2$extra_regressors <- m$extra_regressors
|
||||||
|
m2$seasonalities <- m$seasonalities
|
||||||
|
return(m2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Compute performance metrics from cross-validation results.
|
||||||
|
#'
|
||||||
|
#' Computes a suite of performance metrics on the output of cross-validation.
|
||||||
|
#' By default the following metrics are included:
|
||||||
|
#' 'mse': mean squared error
|
||||||
|
#' 'rmse': root mean squared error
|
||||||
|
#' 'mae': mean absolute error
|
||||||
|
#' 'mape': mean percent error
|
||||||
|
#' 'coverage': coverage of the upper and lower intervals
|
||||||
|
#'
|
||||||
|
#' A subset of these can be specified by passing a list of names as the
|
||||||
|
#' `metrics` argument.
|
||||||
|
#'
|
||||||
|
#' Metrics are calculated over a rolling window of cross validation
|
||||||
|
#' predictions, after sorting by horizon. The size of that window (number of
|
||||||
|
#' simulated forecast points) is determined by the rolling_window argument,
|
||||||
|
#' which specifies a proportion of simulated forecast points to include in
|
||||||
|
#' each window. rolling_window=0 will compute it separately for each simulated
|
||||||
|
#' forecast point (i.e., 'mse' will actually be squared error with no mean).
|
||||||
|
#' The default of rolling_window=0.1 will use 10% of the rows in df in each
|
||||||
|
#' window. rolling_window=1 will compute the metric across all simulated
|
||||||
|
#' forecast points. The results are set to the right edge of the window.
|
||||||
|
#'
|
||||||
|
#' The output is a dataframe containing column 'horizon' along with columns
|
||||||
|
#' for each of the metrics computed.
|
||||||
|
#'
|
||||||
|
#' @param df The dataframe returned by cross_validation.
|
||||||
|
#' @param metrics An array of performance metrics to compute. If not provided,
|
||||||
|
#' will use c('mse', 'rmse', 'mae', 'mape', 'coverage').
|
||||||
|
#' @param rolling_window Proportion of data to use in each rolling window for
|
||||||
|
#' computing the metrics. Should be in [0, 1].
|
||||||
|
#'
|
||||||
|
#' @return A dataframe with a column for each metric, and column 'horizon'.
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
performance_metrics <- function(df, metrics = NULL, rolling_window = 0.1) {
|
||||||
|
valid_metrics <- c('mse', 'rmse', 'mae', 'mape', 'coverage')
|
||||||
|
if (is.null(metrics)) {
|
||||||
|
metrics <- valid_metrics
|
||||||
|
}
|
||||||
|
if (length(metrics) != length(unique(metrics))) {
|
||||||
|
stop('Input metrics must be an array of unique values.')
|
||||||
|
}
|
||||||
|
if (!all(metrics %in% valid_metrics)) {
|
||||||
|
stop(
|
||||||
|
paste('Valid values for metrics are:', paste(metrics, collapse = ", "))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
df_m <- df
|
||||||
|
df_m$horizon <- df_m$ds - df_m$cutoff
|
||||||
|
df_m <- df_m[order(df_m$horizon),]
|
||||||
|
# Window size
|
||||||
|
w <- as.integer(rolling_window * nrow(df_m))
|
||||||
|
w <- max(w, 1)
|
||||||
|
w <- min(w, nrow(df_m))
|
||||||
|
cols <- c('horizon')
|
||||||
|
for (metric in metrics) {
|
||||||
|
df_m[[metric]] <- get(metric)(df_m, w)
|
||||||
|
cols <- c(cols, metric)
|
||||||
|
}
|
||||||
|
df_m <- df_m[cols]
|
||||||
|
return(stats::na.omit(df_m))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Compute a rolling mean of x
|
||||||
|
#'
|
||||||
|
#' Right-aligned. Padded with NAs on the front so the output is the same
|
||||||
|
#' size as x.
|
||||||
|
#'
|
||||||
|
#' @param x Array.
|
||||||
|
#' @param w Integer window size (number of elements).
|
||||||
|
#'
|
||||||
|
#' @return Rolling mean of x with window size w.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
rolling_mean <- function(x, w) {
|
||||||
|
s <- cumsum(c(0, x))
|
||||||
|
prefix <- rep(NA, w - 1)
|
||||||
|
return(c(prefix, (s[(w + 1):length(s)] - s[1:(length(s) - w)]) / w))
|
||||||
|
}
|
||||||
|
|
||||||
|
# The functions below specify performance metrics for cross-validation results.
|
||||||
|
# Each takes as input the output of cross_validation, and returns the statistic
|
||||||
|
# as an array, given a window size for rolling aggregation.
|
||||||
|
|
||||||
|
#' Mean squared error
|
||||||
|
#'
|
||||||
|
#' @param df Cross-validation results dataframe.
|
||||||
|
#' @param w Aggregation window size.
|
||||||
|
#'
|
||||||
|
#' @return Array of mean squared errors.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
mse <- function(df, w) {
|
||||||
|
se <- (df$y - df$yhat) ** 2
|
||||||
|
return(rolling_mean(se, w))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Root mean squared error
|
||||||
|
#'
|
||||||
|
#' @param df Cross-validation results dataframe.
|
||||||
|
#' @param w Aggregation window size.
|
||||||
|
#'
|
||||||
|
#' @return Array of root mean squared errors.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
rmse <- function(df, w) {
|
||||||
|
return(sqrt(mse(df, w)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Mean absolute error
|
||||||
|
#'
|
||||||
|
#' @param df Cross-validation results dataframe.
|
||||||
|
#' @param w Aggregation window size.
|
||||||
|
#'
|
||||||
|
#' @return Array of mean absolute errors.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
mae <- function(df, w) {
|
||||||
|
ae <- abs(df$y - df$yhat)
|
||||||
|
return(rolling_mean(ae, w))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Mean absolute percent error
|
||||||
|
#'
|
||||||
|
#' @param df Cross-validation results dataframe.
|
||||||
|
#' @param w Aggregation window size.
|
||||||
|
#'
|
||||||
|
#' @return Array of mean absolute percent errors.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
mape <- function(df, w) {
|
||||||
|
ape <- abs((df$y - df$yhat) / df$y)
|
||||||
|
return(rolling_mean(ape, w))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Coverage
|
||||||
|
#'
|
||||||
|
#' @param df Cross-validation results dataframe.
|
||||||
|
#' @param w Aggregation window size.
|
||||||
|
#'
|
||||||
|
#' @return Array of coverages
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
coverage <- function(df, w) {
|
||||||
|
is_covered <- (df$y >= df$yhat_lower) & (df$y <= df$yhat_upper)
|
||||||
|
return(rolling_mean(is_covered, w))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
508
R/R/plot.R
Normal file
|
|
@ -0,0 +1,508 @@
|
||||||
|
#' Merge history and forecast for plotting.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet object.
|
||||||
|
#' @param fcst Data frame returned by prophet predict.
|
||||||
|
#'
|
||||||
|
#' @importFrom dplyr "%>%"
|
||||||
|
#' @keywords internal
|
||||||
|
df_for_plotting <- function(m, fcst) {
|
||||||
|
# Make sure there is no y in fcst
|
||||||
|
fcst$y <- NULL
|
||||||
|
df <- m$history %>%
|
||||||
|
dplyr::select(ds, y) %>%
|
||||||
|
dplyr::full_join(fcst, by = "ds") %>%
|
||||||
|
dplyr::arrange(ds)
|
||||||
|
return(df)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot the prophet forecast.
|
||||||
|
#'
|
||||||
|
#' @param x Prophet object.
|
||||||
|
#' @param fcst Data frame returned by predict(m, df).
|
||||||
|
#' @param uncertainty Boolean indicating if the uncertainty interval for yhat
|
||||||
|
#' should be plotted. Must be present in fcst as yhat_lower and yhat_upper.
|
||||||
|
#' @param plot_cap Boolean indicating if the capacity should be shown in the
|
||||||
|
#' figure, if available.
|
||||||
|
#' @param xlabel Optional label for x-axis
|
||||||
|
#' @param ylabel Optional label for y-axis
|
||||||
|
#' @param ... additional arguments
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @examples
|
||||||
|
#' \dontrun{
|
||||||
|
#' history <- data.frame(ds = seq(as.Date('2015-01-01'), as.Date('2016-01-01'), by = 'd'),
|
||||||
|
#' y = sin(1:366/200) + rnorm(366)/10)
|
||||||
|
#' m <- prophet(history)
|
||||||
|
#' future <- make_future_dataframe(m, periods = 365)
|
||||||
|
#' forecast <- predict(m, future)
|
||||||
|
#' plot(m, forecast)
|
||||||
|
#' }
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
plot.prophet <- function(x, fcst, uncertainty = TRUE, plot_cap = TRUE,
|
||||||
|
xlabel = 'ds', ylabel = 'y', ...) {
|
||||||
|
df <- df_for_plotting(x, fcst)
|
||||||
|
gg <- ggplot2::ggplot(df, ggplot2::aes(x = ds, y = y)) +
|
||||||
|
ggplot2::labs(x = xlabel, y = ylabel)
|
||||||
|
if (exists('cap', where = df) && plot_cap) {
|
||||||
|
gg <- gg + ggplot2::geom_line(
|
||||||
|
ggplot2::aes(y = cap), linetype = 'dashed', na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (x$logistic.floor && exists('floor', where = df) && plot_cap) {
|
||||||
|
gg <- gg + ggplot2::geom_line(
|
||||||
|
ggplot2::aes(y = floor), linetype = 'dashed', na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (uncertainty && exists('yhat_lower', where = df)) {
|
||||||
|
gg <- gg +
|
||||||
|
ggplot2::geom_ribbon(ggplot2::aes(ymin = yhat_lower, ymax = yhat_upper),
|
||||||
|
alpha = 0.2,
|
||||||
|
fill = "#0072B2",
|
||||||
|
na.rm = TRUE)
|
||||||
|
}
|
||||||
|
gg <- gg +
|
||||||
|
ggplot2::geom_point(na.rm=TRUE) +
|
||||||
|
ggplot2::geom_line(ggplot2::aes(y = yhat), color = "#0072B2",
|
||||||
|
na.rm = TRUE) +
|
||||||
|
ggplot2::theme(aspect.ratio = 3 / 5)
|
||||||
|
return(gg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot the components of a prophet forecast.
|
||||||
|
#' Prints a ggplot2 with whichever are available of: trend, holidays, weekly
|
||||||
|
#' seasonality, yearly seasonality, and additive and multiplicative extra
|
||||||
|
#' regressors.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet object.
|
||||||
|
#' @param fcst Data frame returned by predict(m, df).
|
||||||
|
#' @param uncertainty Boolean indicating if the uncertainty interval should be
|
||||||
|
#' plotted for the trend, from fcst columns trend_lower and trend_upper.
|
||||||
|
#' @param plot_cap Boolean indicating if the capacity should be shown in the
|
||||||
|
#' figure, if available.
|
||||||
|
#' @param weekly_start Integer specifying the start day of the weekly
|
||||||
|
#' seasonality plot. 0 (default) starts the week on Sunday. 1 shifts by 1 day
|
||||||
|
#' to Monday, and so on.
|
||||||
|
#' @param yearly_start Integer specifying the start day of the yearly
|
||||||
|
#' seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts by 1 day
|
||||||
|
#' to Jan 2, and so on.
|
||||||
|
#' @param render_plot Boolean indicating if the plots should be rendered.
|
||||||
|
#' Set to FALSE if you want the function to only return the list of panels.
|
||||||
|
#'
|
||||||
|
#' @return Invisibly return a list containing the plotted ggplot objects
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
#' @importFrom dplyr "%>%"
|
||||||
|
prophet_plot_components <- function(
|
||||||
|
m, fcst, uncertainty = TRUE, plot_cap = TRUE, weekly_start = 0,
|
||||||
|
yearly_start = 0, render_plot = TRUE
|
||||||
|
) {
|
||||||
|
# Plot the trend
|
||||||
|
panels <- list(
|
||||||
|
plot_forecast_component(m, fcst, 'trend', uncertainty, plot_cap))
|
||||||
|
# Plot holiday components, if present.
|
||||||
|
if (!is.null(m$holidays) && ('holidays' %in% colnames(fcst))) {
|
||||||
|
panels[[length(panels) + 1]] <- plot_forecast_component(
|
||||||
|
m, fcst, 'holidays', uncertainty, FALSE)
|
||||||
|
}
|
||||||
|
# Plot weekly seasonality, if present
|
||||||
|
if ("weekly" %in% colnames(fcst)) {
|
||||||
|
panels[[length(panels) + 1]] <- plot_weekly(m, uncertainty, weekly_start)
|
||||||
|
}
|
||||||
|
# Plot yearly seasonality, if present
|
||||||
|
if ("yearly" %in% colnames(fcst)) {
|
||||||
|
panels[[length(panels) + 1]] <- plot_yearly(m, uncertainty, yearly_start)
|
||||||
|
}
|
||||||
|
# Plot other seasonalities
|
||||||
|
for (name in names(m$seasonalities)) {
|
||||||
|
if (!(name %in% c('weekly', 'yearly')) &&
|
||||||
|
(name %in% colnames(fcst))) {
|
||||||
|
panels[[length(panels) + 1]] <- plot_seasonality(m, name, uncertainty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Plot extra regressors
|
||||||
|
regressors <- list(additive = FALSE, multiplicative = FALSE)
|
||||||
|
for (name in names(m$extra_regressors)) {
|
||||||
|
regressors[[m$extra_regressors[[name]]$mode]] <- TRUE
|
||||||
|
}
|
||||||
|
for (mode in c('additive', 'multiplicative')) {
|
||||||
|
if ((regressors[[mode]]) &
|
||||||
|
(paste0('extra_regressors_', mode) %in% colnames(fcst))
|
||||||
|
) {
|
||||||
|
panels[[length(panels) + 1]] <- plot_forecast_component(
|
||||||
|
m, fcst, paste0('extra_regressors_', mode), uncertainty, FALSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_plot) {
|
||||||
|
# Make the plot.
|
||||||
|
grid::grid.newpage()
|
||||||
|
grid::pushViewport(grid::viewport(layout = grid::grid.layout(length(panels),
|
||||||
|
1)))
|
||||||
|
for (i in seq_along(panels)) {
|
||||||
|
print(panels[[i]], vp = grid::viewport(layout.pos.row = i,
|
||||||
|
layout.pos.col = 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(invisible(panels))
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot a particular component of the forecast.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model
|
||||||
|
#' @param fcst Dataframe output of `predict`.
|
||||||
|
#' @param name String name of the component to plot (column of fcst).
|
||||||
|
#' @param uncertainty Boolean to plot uncertainty intervals.
|
||||||
|
#' @param plot_cap Boolean indicating if the capacity should be shown in the
|
||||||
|
#' figure, if available.
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
plot_forecast_component <- function(
|
||||||
|
m, fcst, name, uncertainty = TRUE, plot_cap = FALSE
|
||||||
|
) {
|
||||||
|
gg.comp <- ggplot2::ggplot(
|
||||||
|
fcst, ggplot2::aes_string(x = 'ds', y = name, group = 1)) +
|
||||||
|
ggplot2::geom_line(color = "#0072B2", na.rm = TRUE)
|
||||||
|
if (exists('cap', where = fcst) && plot_cap) {
|
||||||
|
gg.comp <- gg.comp + ggplot2::geom_line(
|
||||||
|
ggplot2::aes(y = cap), linetype = 'dashed', na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (exists('floor', where = fcst) && plot_cap) {
|
||||||
|
gg.comp <- gg.comp + ggplot2::geom_line(
|
||||||
|
ggplot2::aes(y = floor), linetype = 'dashed', na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (uncertainty) {
|
||||||
|
gg.comp <- gg.comp +
|
||||||
|
ggplot2::geom_ribbon(
|
||||||
|
ggplot2::aes_string(
|
||||||
|
ymin = paste0(name, '_lower'), ymax = paste0(name, '_upper')
|
||||||
|
),
|
||||||
|
alpha = 0.2,
|
||||||
|
fill = "#0072B2",
|
||||||
|
na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (name %in% m$component.modes$multiplicative) {
|
||||||
|
gg.comp <- gg.comp + ggplot2::scale_y_continuous(labels = scales::percent)
|
||||||
|
}
|
||||||
|
return(gg.comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Prepare dataframe for plotting seasonal components.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet object.
|
||||||
|
#' @param ds Array of dates for column ds.
|
||||||
|
#'
|
||||||
|
#' @return A dataframe with seasonal components on ds.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
seasonality_plot_df <- function(m, ds) {
|
||||||
|
df_list <- list(ds = ds, cap = 1, floor = 0)
|
||||||
|
for (name in names(m$extra_regressors)) {
|
||||||
|
df_list[[name]] <- 0
|
||||||
|
}
|
||||||
|
df <- as.data.frame(df_list)
|
||||||
|
df <- setup_dataframe(m, df)$df
|
||||||
|
return(df)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot the weekly component of the forecast.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model object
|
||||||
|
#' @param uncertainty Boolean to plot uncertainty intervals.
|
||||||
|
#' @param weekly_start Integer specifying the start day of the weekly
|
||||||
|
#' seasonality plot. 0 (default) starts the week on Sunday. 1 shifts by 1 day
|
||||||
|
#' to Monday, and so on.
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
plot_weekly <- function(m, uncertainty = TRUE, weekly_start = 0) {
|
||||||
|
# Compute weekly seasonality for a Sun-Sat sequence of dates.
|
||||||
|
days <- seq(set_date('2017-01-01'), by='d', length.out=7) + as.difftime(
|
||||||
|
weekly_start, units = "days")
|
||||||
|
df.w <- seasonality_plot_df(m, days)
|
||||||
|
seas <- predict_seasonal_components(m, df.w)
|
||||||
|
seas$dow <- factor(weekdays(df.w$ds), levels=weekdays(df.w$ds))
|
||||||
|
|
||||||
|
gg.weekly <- ggplot2::ggplot(seas, ggplot2::aes(x = dow, y = weekly,
|
||||||
|
group = 1)) +
|
||||||
|
ggplot2::geom_line(color = "#0072B2", na.rm = TRUE) +
|
||||||
|
ggplot2::labs(x = "Day of week")
|
||||||
|
if (uncertainty) {
|
||||||
|
gg.weekly <- gg.weekly +
|
||||||
|
ggplot2::geom_ribbon(ggplot2::aes(ymin = weekly_lower,
|
||||||
|
ymax = weekly_upper),
|
||||||
|
alpha = 0.2,
|
||||||
|
fill = "#0072B2",
|
||||||
|
na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (m$seasonalities$weekly$mode == 'multiplicative') {
|
||||||
|
gg.weekly <- (
|
||||||
|
gg.weekly + ggplot2::scale_y_continuous(labels = scales::percent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(gg.weekly)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot the yearly component of the forecast.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model object.
|
||||||
|
#' @param uncertainty Boolean to plot uncertainty intervals.
|
||||||
|
#' @param yearly_start Integer specifying the start day of the yearly
|
||||||
|
#' seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts by 1 day
|
||||||
|
#' to Jan 2, and so on.
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
plot_yearly <- function(m, uncertainty = TRUE, yearly_start = 0) {
|
||||||
|
# Compute yearly seasonality for a Jan 1 - Dec 31 sequence of dates.
|
||||||
|
days <- seq(set_date('2017-01-01'), by='d', length.out=365) + as.difftime(
|
||||||
|
yearly_start, units = "days")
|
||||||
|
df.y <- seasonality_plot_df(m, days)
|
||||||
|
seas <- predict_seasonal_components(m, df.y)
|
||||||
|
seas$ds <- df.y$ds
|
||||||
|
|
||||||
|
gg.yearly <- ggplot2::ggplot(seas, ggplot2::aes(x = ds, y = yearly,
|
||||||
|
group = 1)) +
|
||||||
|
ggplot2::geom_line(color = "#0072B2", na.rm = TRUE) +
|
||||||
|
ggplot2::labs(x = "Day of year") +
|
||||||
|
ggplot2::scale_x_datetime(labels = scales::date_format('%B %d'))
|
||||||
|
if (uncertainty) {
|
||||||
|
gg.yearly <- gg.yearly +
|
||||||
|
ggplot2::geom_ribbon(ggplot2::aes(ymin = yearly_lower,
|
||||||
|
ymax = yearly_upper),
|
||||||
|
alpha = 0.2,
|
||||||
|
fill = "#0072B2",
|
||||||
|
na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (m$seasonalities$yearly$mode == 'multiplicative') {
|
||||||
|
gg.yearly <- (
|
||||||
|
gg.yearly + ggplot2::scale_y_continuous(labels = scales::percent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(gg.yearly)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot a custom seasonal component.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model object.
|
||||||
|
#' @param name String name of the seasonality.
|
||||||
|
#' @param uncertainty Boolean to plot uncertainty intervals.
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @keywords internal
|
||||||
|
plot_seasonality <- function(m, name, uncertainty = TRUE) {
|
||||||
|
# Compute seasonality from Jan 1 through a single period.
|
||||||
|
start <- set_date('2017-01-01')
|
||||||
|
period <- m$seasonalities[[name]]$period
|
||||||
|
end <- start + period * 24 * 3600
|
||||||
|
plot.points <- 200
|
||||||
|
days <- seq(from=start, to=end, length.out=plot.points)
|
||||||
|
df.y <- seasonality_plot_df(m, days)
|
||||||
|
seas <- predict_seasonal_components(m, df.y)
|
||||||
|
seas$ds <- df.y$ds
|
||||||
|
gg.s <- ggplot2::ggplot(
|
||||||
|
seas, ggplot2::aes_string(x = 'ds', y = name, group = 1)) +
|
||||||
|
ggplot2::geom_line(color = "#0072B2", na.rm = TRUE)
|
||||||
|
if (period <= 2) {
|
||||||
|
fmt.str <- '%T'
|
||||||
|
} else if (period < 14) {
|
||||||
|
fmt.str <- '%m/%d %R'
|
||||||
|
} else {
|
||||||
|
fmt.str <- '%m/%d'
|
||||||
|
}
|
||||||
|
gg.s <- gg.s +
|
||||||
|
ggplot2::scale_x_datetime(labels = scales::date_format(fmt.str))
|
||||||
|
if (uncertainty) {
|
||||||
|
gg.s <- gg.s +
|
||||||
|
ggplot2::geom_ribbon(
|
||||||
|
ggplot2::aes_string(
|
||||||
|
ymin = paste0(name, '_lower'), ymax = paste0(name, '_upper')
|
||||||
|
),
|
||||||
|
alpha = 0.2,
|
||||||
|
fill = "#0072B2",
|
||||||
|
na.rm = TRUE)
|
||||||
|
}
|
||||||
|
if (m$seasonalities[[name]]$mode == 'multiplicative') {
|
||||||
|
gg.s <- gg.s + ggplot2::scale_y_continuous(labels = scales::percent)
|
||||||
|
}
|
||||||
|
return(gg.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Get layers to overlay significant changepoints on prophet forecast plot.
|
||||||
|
#'
|
||||||
|
#' @param m Prophet model object.
|
||||||
|
#' @param threshold Numeric, changepoints where abs(delta) >= threshold are
|
||||||
|
#' significant. (Default 0.01)
|
||||||
|
#' @param cp_color Character, line color. (Default "red")
|
||||||
|
#' @param cp_linetype Character or integer, line type. (Default "dashed")
|
||||||
|
#' @param trend Logical, if FALSE, do not draw trend line. (Default TRUE)
|
||||||
|
#' @param ... Other arguments passed on to layers.
|
||||||
|
#'
|
||||||
|
#' @return A list of ggplot2 layers.
|
||||||
|
#'
|
||||||
|
#' @examples
|
||||||
|
#' \dontrun{
|
||||||
|
#' plot(m, fcst) + add_changepoints_to_plot(m)
|
||||||
|
#' }
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
add_changepoints_to_plot <- function(m, threshold = 0.01, cp_color = "red",
|
||||||
|
cp_linetype = "dashed", trend = TRUE, ...) {
|
||||||
|
layers <- list()
|
||||||
|
if (trend) {
|
||||||
|
trend_layer <- ggplot2::geom_line(
|
||||||
|
ggplot2::aes_string("ds", "trend"), color = cp_color, ...)
|
||||||
|
layers <- append(layers, trend_layer)
|
||||||
|
}
|
||||||
|
signif_changepoints <- m$changepoints[abs(m$params$delta) >= threshold]
|
||||||
|
cp_layer <- ggplot2::geom_vline(
|
||||||
|
xintercept = as.integer(signif_changepoints), color = cp_color,
|
||||||
|
linetype = cp_linetype, ...)
|
||||||
|
layers <- append(layers, cp_layer)
|
||||||
|
return(layers)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot the prophet forecast.
|
||||||
|
#'
|
||||||
|
#' @param x Prophet object.
|
||||||
|
#' @param fcst Data frame returned by predict(m, df).
|
||||||
|
#' @param uncertainty Boolean indicating if the uncertainty interval for yhat
|
||||||
|
#' should be plotted. Must be present in fcst as yhat_lower and yhat_upper.
|
||||||
|
#' @param ... additional arguments
|
||||||
|
#' @importFrom dplyr "%>%"
|
||||||
|
#' @return A dygraph plot.
|
||||||
|
#'
|
||||||
|
#' @examples
|
||||||
|
#' \dontrun{
|
||||||
|
#' history <- data.frame(
|
||||||
|
#' ds = seq(as.Date('2015-01-01'), as.Date('2016-01-01'), by = 'd'),
|
||||||
|
#' y = sin(1:366/200) + rnorm(366)/10)
|
||||||
|
#' m <- prophet(history)
|
||||||
|
#' future <- make_future_dataframe(m, periods = 365)
|
||||||
|
#' forecast <- predict(m, future)
|
||||||
|
#' dyplot.prophet(m, forecast)
|
||||||
|
#' }
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
dyplot.prophet <- function(x, fcst, uncertainty=TRUE,
|
||||||
|
...)
|
||||||
|
{
|
||||||
|
forecast.label='Predicted'
|
||||||
|
actual.label='Actual'
|
||||||
|
# create data.frame for plotting
|
||||||
|
df <- df_for_plotting(x, fcst)
|
||||||
|
|
||||||
|
# build variables to include, or not, the uncertainty data
|
||||||
|
if(uncertainty && exists("yhat_lower", where = df))
|
||||||
|
{
|
||||||
|
colsToKeep <- c('y', 'yhat', 'yhat_lower', 'yhat_upper')
|
||||||
|
forecastCols <- c('yhat_lower', 'yhat', 'yhat_upper')
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
colsToKeep <- c('y', 'yhat')
|
||||||
|
forecastCols <- c('yhat')
|
||||||
|
}
|
||||||
|
# convert to xts for easier date handling by dygraph
|
||||||
|
dfTS <- xts::xts(df %>% dplyr::select_(.dots=colsToKeep), order.by = df$ds)
|
||||||
|
|
||||||
|
# base plot
|
||||||
|
dyBase <- dygraphs::dygraph(dfTS)
|
||||||
|
|
||||||
|
presAnnotation <- function(dygraph, x, text) {
|
||||||
|
dygraph %>%
|
||||||
|
dygraphs::dyAnnotation(x, text, text, attachAtBottom = TRUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
dyBase <- dyBase %>%
|
||||||
|
# plot actual values
|
||||||
|
dygraphs::dySeries(
|
||||||
|
'y', label=actual.label, color='black', drawPoints=TRUE, strokeWidth=0
|
||||||
|
) %>%
|
||||||
|
# plot forecast and ribbon
|
||||||
|
dygraphs::dySeries(forecastCols, label=forecast.label, color='blue') %>%
|
||||||
|
# allow zooming
|
||||||
|
dygraphs::dyRangeSelector() %>%
|
||||||
|
# make unzoom button
|
||||||
|
dygraphs::dyUnzoom()
|
||||||
|
if (!is.null(x$holidays)) {
|
||||||
|
for (i in 1:nrow(x$holidays)) {
|
||||||
|
# make a gray line
|
||||||
|
dyBase <- dyBase %>% dygraphs::dyEvent(
|
||||||
|
x$holidays$ds[i],color = "rgb(200,200,200)", strokePattern = "solid")
|
||||||
|
dyBase <- dyBase %>% dygraphs::dyAnnotation(
|
||||||
|
x$holidays$ds[i], x$holidays$holiday[i], x$holidays$holiday[i],
|
||||||
|
attachAtBottom = TRUE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(dyBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' Plot a performance metric vs. forecast horizon from cross validation.
|
||||||
|
|
||||||
|
#' Cross validation produces a collection of out-of-sample model predictions
|
||||||
|
#' that can be compared to actual values, at a range of different horizons
|
||||||
|
#' (distance from the cutoff). This computes a specified performance metric
|
||||||
|
#' for each prediction, and aggregated over a rolling window with horizon.
|
||||||
|
#'
|
||||||
|
#' This uses fbprophet.diagnostics.performance_metrics to compute the metrics.
|
||||||
|
#' Valid values of metric are 'mse', 'rmse', 'mae', 'mape', and 'coverage'.
|
||||||
|
#'
|
||||||
|
#' rolling_window is the proportion of data included in the rolling window of
|
||||||
|
#' aggregation. The default value of 0.1 means 10% of data are included in the
|
||||||
|
#' aggregation for computing the metric.
|
||||||
|
#'
|
||||||
|
#' As a concrete example, if metric='mse', then this plot will show the
|
||||||
|
#' squared error for each cross validation prediction, along with the MSE
|
||||||
|
#' averaged over rolling windows of 10% of the data.
|
||||||
|
#'
|
||||||
|
#' @param df_cv The output from fbprophet.diagnostics.cross_validation.
|
||||||
|
#' @param metric Metric name, one of 'mse', 'rmse', 'mae', 'mape', 'coverage'.
|
||||||
|
#' @param rolling_window Proportion of data to use for rolling average of
|
||||||
|
#' metric. In [0, 1]. Defaults to 0.1.
|
||||||
|
#'
|
||||||
|
#' @return A ggplot2 plot.
|
||||||
|
#'
|
||||||
|
#' @export
|
||||||
|
plot_cross_validation_metric <- function(df_cv, metric, rolling_window=0.1) {
|
||||||
|
df_none <- performance_metrics(df_cv, metrics = metric, rolling_window = 0)
|
||||||
|
df_h <- performance_metrics(
|
||||||
|
df_cv, metrics = metric, rolling_window = rolling_window
|
||||||
|
)
|
||||||
|
|
||||||
|
# Better plotting of difftime
|
||||||
|
# Target ~10 ticks
|
||||||
|
tick_w <- max(as.double(df_none$horizon, units = 'secs')) / 10.
|
||||||
|
# Find the largest time resolution that has <1 unit per bin
|
||||||
|
dts <- c('days', 'hours', 'mins', 'secs')
|
||||||
|
dt_conversions <- c(
|
||||||
|
24 * 60 * 60,
|
||||||
|
60 * 60,
|
||||||
|
60,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
for (i in seq_along(dts)) {
|
||||||
|
if (as.difftime(1, units = dts[i]) < as.difftime(tick_w, units = 'secs')) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
df_none$x_plt <- (
|
||||||
|
as.double(df_none$horizon, units = 'secs') / dt_conversions[i]
|
||||||
|
)
|
||||||
|
df_h$x_plt <- as.double(df_h$horizon, units = 'secs') / dt_conversions[i]
|
||||||
|
|
||||||
|
gg <- (
|
||||||
|
ggplot2::ggplot(df_none, ggplot2::aes_string(x = 'x_plt', y = metric)) +
|
||||||
|
ggplot2::labs(x = paste0('Horizon (', dts[i], ')'), y = metric) +
|
||||||
|
ggplot2::geom_point(color = 'gray') +
|
||||||
|
ggplot2::geom_line(
|
||||||
|
data = df_h, ggplot2::aes_string(x = 'x_plt', y = metric), color = 'blue'
|
||||||
|
) +
|
||||||
|
ggplot2::theme(aspect.ratio = 3 / 5)
|
||||||
|
)
|
||||||
|
|
||||||
|
return(gg)
|
||||||
|
}
|
||||||
774
R/R/prophet.R
11
R/R/zzz.R
|
|
@ -6,9 +6,10 @@
|
||||||
## of patent rights can be found in the PATENTS file in the same directory.
|
## of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
.onLoad <- function(libname, pkgname) {
|
.onLoad <- function(libname, pkgname) {
|
||||||
.prophet.stan.models <- list(
|
.prophet.stan.model <- get_prophet_stan_model()
|
||||||
"linear"=get_prophet_stan_model("linear"),
|
assign(
|
||||||
"logistic"=get_prophet_stan_model("logistic"))
|
".prophet.stan.model",
|
||||||
assign(".prophet.stan.models", .prophet.stan.models,
|
.prophet.stan.model,
|
||||||
envir=parent.env(environment()))
|
envir=parent.env(environment())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
126
R/inst/stan/prophet.stan
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
functions {
|
||||||
|
matrix get_changepoint_matrix(vector t, vector t_change, int T, int S) {
|
||||||
|
// Assumes t and t_change are sorted.
|
||||||
|
matrix[T, S] A;
|
||||||
|
row_vector[S] a_row;
|
||||||
|
int cp_idx;
|
||||||
|
|
||||||
|
// Start with an empty matrix.
|
||||||
|
A = rep_matrix(0, T, S);
|
||||||
|
a_row = rep_row_vector(0, S);
|
||||||
|
cp_idx = 1;
|
||||||
|
|
||||||
|
// Fill in each row of A.
|
||||||
|
for (i in 1:T) {
|
||||||
|
while ((cp_idx <= S) && (t[i] >= t_change[cp_idx])) {
|
||||||
|
a_row[cp_idx] = 1;
|
||||||
|
cp_idx = cp_idx + 1;
|
||||||
|
}
|
||||||
|
A[i] = a_row;
|
||||||
|
}
|
||||||
|
return A;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logistic trend functions
|
||||||
|
|
||||||
|
vector logistic_gamma(real k, real m, vector delta, vector t_change, int S) {
|
||||||
|
vector[S] gamma; // adjusted offsets, for piecewise continuity
|
||||||
|
vector[S + 1] k_s; // actual rate in each segment
|
||||||
|
real m_pr;
|
||||||
|
|
||||||
|
// Compute the rate in each segment
|
||||||
|
k_s = append_row(k, k + cumulative_sum(delta));
|
||||||
|
|
||||||
|
// Piecewise offsets
|
||||||
|
m_pr = m; // The offset in the previous segment
|
||||||
|
for (i in 1:S) {
|
||||||
|
gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
|
||||||
|
m_pr = m_pr + gamma[i]; // update for the next segment
|
||||||
|
}
|
||||||
|
return gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector logistic_trend(
|
||||||
|
real k,
|
||||||
|
real m,
|
||||||
|
vector delta,
|
||||||
|
vector t,
|
||||||
|
vector cap,
|
||||||
|
matrix A,
|
||||||
|
vector t_change,
|
||||||
|
int S
|
||||||
|
) {
|
||||||
|
vector[S] gamma;
|
||||||
|
|
||||||
|
gamma = logistic_gamma(k, m, delta, t_change, S);
|
||||||
|
return cap .* inv_logit((k + A * delta) .* (t - (m + A * gamma)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear trend function
|
||||||
|
|
||||||
|
vector linear_trend(
|
||||||
|
real k,
|
||||||
|
real m,
|
||||||
|
vector delta,
|
||||||
|
vector t,
|
||||||
|
matrix A,
|
||||||
|
vector t_change
|
||||||
|
) {
|
||||||
|
return (k + A * delta) .* t + (m + A * (-t_change .* delta));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data {
|
||||||
|
int T; // Number of time periods
|
||||||
|
int<lower=1> K; // Number of regressors
|
||||||
|
vector[T] t; // Time
|
||||||
|
vector[T] cap; // Capacities for logistic trend
|
||||||
|
vector[T] y; // Time series
|
||||||
|
int S; // Number of changepoints
|
||||||
|
vector[S] t_change; // Times of trend changepoints
|
||||||
|
matrix[T,K] X; // Regressors
|
||||||
|
vector[K] sigmas; // Scale on seasonality prior
|
||||||
|
real<lower=0> tau; // Scale on changepoints prior
|
||||||
|
int trend_indicator; // 0 for linear, 1 for logistic
|
||||||
|
vector[K] s_a; // Indicator of additive features
|
||||||
|
vector[K] s_m; // Indicator of multiplicative features
|
||||||
|
}
|
||||||
|
|
||||||
|
transformed data {
|
||||||
|
matrix[T, S] A;
|
||||||
|
A = get_changepoint_matrix(t, t_change, T, S);
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
real k; // Base trend growth rate
|
||||||
|
real m; // Trend offset
|
||||||
|
vector[S] delta; // Trend rate adjustments
|
||||||
|
real<lower=0> sigma_obs; // Observation noise
|
||||||
|
vector[K] beta; // Regressor coefficients
|
||||||
|
}
|
||||||
|
|
||||||
|
model {
|
||||||
|
//priors
|
||||||
|
k ~ normal(0, 5);
|
||||||
|
m ~ normal(0, 5);
|
||||||
|
delta ~ double_exponential(0, tau);
|
||||||
|
sigma_obs ~ normal(0, 0.5);
|
||||||
|
beta ~ normal(0, sigmas);
|
||||||
|
|
||||||
|
// Likelihood
|
||||||
|
if (trend_indicator == 0) {
|
||||||
|
y ~ normal(
|
||||||
|
linear_trend(k, m, delta, t, A, t_change)
|
||||||
|
.* (1 + X * (beta .* s_m))
|
||||||
|
+ X * (beta .* s_a),
|
||||||
|
sigma_obs
|
||||||
|
);
|
||||||
|
} else if (trend_indicator == 1) {
|
||||||
|
y ~ normal(
|
||||||
|
logistic_trend(k, m, delta, t, cap, A, t_change, S)
|
||||||
|
.* (1 + X * (beta .* s_m))
|
||||||
|
+ X * (beta .* s_a),
|
||||||
|
sigma_obs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
data {
|
|
||||||
int T; // Sample size
|
|
||||||
int<lower=1> K; // Number of seasonal vectors
|
|
||||||
vector[T] t; // Day
|
|
||||||
vector[T] y; // Time-series
|
|
||||||
int S; // Number of changepoints
|
|
||||||
matrix[T, S] A; // Split indicators
|
|
||||||
real t_change[S]; // Index of changepoints
|
|
||||||
matrix[T,K] X; // season vectors
|
|
||||||
vector[K] sigmas; // scale on seasonality prior
|
|
||||||
real<lower=0> tau; // scale on changepoints prior
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters {
|
|
||||||
real k; // Base growth rate
|
|
||||||
real m; // offset
|
|
||||||
vector[S] delta; // Rate adjustments
|
|
||||||
real<lower=0> sigma_obs; // Observation noise (incl. seasonal variation)
|
|
||||||
vector[K] beta; // seasonal vector
|
|
||||||
}
|
|
||||||
|
|
||||||
transformed parameters {
|
|
||||||
vector[S] gamma; // adjusted offsets, for piecewise continuity
|
|
||||||
|
|
||||||
for (i in 1:S) {
|
|
||||||
gamma[i] = -t_change[i] * delta[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model {
|
|
||||||
//priors
|
|
||||||
k ~ normal(0, 5);
|
|
||||||
m ~ normal(0, 5);
|
|
||||||
delta ~ double_exponential(0, tau);
|
|
||||||
sigma_obs ~ normal(0, 0.5);
|
|
||||||
beta ~ normal(0, sigmas);
|
|
||||||
|
|
||||||
// Likelihood
|
|
||||||
y ~ normal((k + A * delta) .* t + (m + A * gamma) + X * beta, sigma_obs);
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
data {
|
|
||||||
int T; // Sample size
|
|
||||||
int<lower=1> K; // Number of seasonal vectors
|
|
||||||
vector[T] t; // Day
|
|
||||||
vector[T] cap; // Capacities
|
|
||||||
vector[T] y; // Time-series
|
|
||||||
int S; // Number of changepoints
|
|
||||||
matrix[T, S] A; // Split indicators
|
|
||||||
real t_change[S]; // Index of changepoints
|
|
||||||
matrix[T,K] X; // season vectors
|
|
||||||
vector[K] sigmas; // scale on seasonality prior
|
|
||||||
real<lower=0> tau; // scale on changepoints prior
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters {
|
|
||||||
real k; // Base growth rate
|
|
||||||
real m; // offset
|
|
||||||
vector[S] delta; // Rate adjustments
|
|
||||||
real<lower=0> sigma_obs; // Observation noise (incl. seasonal variation)
|
|
||||||
vector[K] beta; // seasonal vector
|
|
||||||
}
|
|
||||||
|
|
||||||
transformed parameters {
|
|
||||||
vector[S] gamma; // adjusted offsets, for piecewise continuity
|
|
||||||
vector[S + 1] k_s; // actual rate in each segment
|
|
||||||
real m_pr;
|
|
||||||
|
|
||||||
// Compute the rate in each segment
|
|
||||||
k_s[1] = k;
|
|
||||||
for (i in 1:S) {
|
|
||||||
k_s[i + 1] = k_s[i] + delta[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Piecewise offsets
|
|
||||||
m_pr = m; // The offset in the previous segment
|
|
||||||
for (i in 1:S) {
|
|
||||||
gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
|
|
||||||
m_pr = m_pr + gamma[i]; // update for the next segment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model {
|
|
||||||
//priors
|
|
||||||
k ~ normal(0, 5);
|
|
||||||
m ~ normal(0, 5);
|
|
||||||
delta ~ double_exponential(0, tau);
|
|
||||||
sigma_obs ~ normal(0, 0.1);
|
|
||||||
beta ~ normal(0, sigmas);
|
|
||||||
|
|
||||||
// Likelihood
|
|
||||||
y ~ normal(cap ./ (1 + exp(-(k + A * delta) .* (t - (m + A * gamma)))) + X * beta, sigma_obs);
|
|
||||||
}
|
|
||||||
35
R/man/add_changepoints_to_plot.Rd
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/plot.R
|
||||||
|
\name{add_changepoints_to_plot}
|
||||||
|
\alias{add_changepoints_to_plot}
|
||||||
|
\title{Get layers to overlay significant changepoints on prophet forecast plot.}
|
||||||
|
\usage{
|
||||||
|
add_changepoints_to_plot(m, threshold = 0.01, cp_color = "red",
|
||||||
|
cp_linetype = "dashed", trend = TRUE, ...)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{m}{Prophet model object.}
|
||||||
|
|
||||||
|
\item{threshold}{Numeric, changepoints where abs(delta) >= threshold are
|
||||||
|
significant. (Default 0.01)}
|
||||||
|
|
||||||
|
\item{cp_color}{Character, line color. (Default "red")}
|
||||||
|
|
||||||
|
\item{cp_linetype}{Character or integer, line type. (Default "dashed")}
|
||||||
|
|
||||||
|
\item{trend}{Logical, if FALSE, do not draw trend line. (Default TRUE)}
|
||||||
|
|
||||||
|
\item{...}{Other arguments passed on to layers.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
A list of ggplot2 layers.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Get layers to overlay significant changepoints on prophet forecast plot.
|
||||||
|
}
|
||||||
|
\examples{
|
||||||
|
\dontrun{
|
||||||
|
plot(m, fcst) + add_changepoints_to_plot(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
\alias{add_regressor}
|
\alias{add_regressor}
|
||||||
\title{Add an additional regressor to be used for fitting and predicting.}
|
\title{Add an additional regressor to be used for fitting and predicting.}
|
||||||
\usage{
|
\usage{
|
||||||
add_regressor(m, name, prior.scale = NULL, standardize = "auto")
|
add_regressor(m, name, prior.scale = NULL, standardize = "auto",
|
||||||
|
mode = NULL)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{m}{Prophet object.}
|
\item{m}{Prophet object.}
|
||||||
|
|
@ -17,6 +18,9 @@ holidays.prior.scale will be used.}
|
||||||
\item{standardize}{Bool, specify whether this regressor will be standardized
|
\item{standardize}{Bool, specify whether this regressor will be standardized
|
||||||
prior to fitting. Can be 'auto' (standardize if not binary), True, or
|
prior to fitting. Can be 'auto' (standardize if not binary), True, or
|
||||||
False.}
|
False.}
|
||||||
|
|
||||||
|
\item{mode}{Optional, 'additive' or 'multiplicative'. Defaults to
|
||||||
|
m$seasonality.mode.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
The prophet model with the regressor added.
|
The prophet model with the regressor added.
|
||||||
|
|
@ -28,4 +32,8 @@ regressor will be standardized unless it is binary. The regression
|
||||||
coefficient is given a prior with the specified scale parameter.
|
coefficient is given a prior with the specified scale parameter.
|
||||||
Decreasing the prior scale will add additional regularization. If no
|
Decreasing the prior scale will add additional regularization. If no
|
||||||
prior scale is provided, holidays.prior.scale will be used.
|
prior scale is provided, holidays.prior.scale will be used.
|
||||||
|
Mode can be specified as either 'additive' or 'multiplicative'. If not
|
||||||
|
specified, m$seasonality.mode will be used. 'additive' means the effect of
|
||||||
|
the regressor will be added to the trend, 'multiplicative' means it will
|
||||||
|
multiply the trend.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
\title{Add a seasonal component with specified period, number of Fourier
|
\title{Add a seasonal component with specified period, number of Fourier
|
||||||
components, and prior scale.}
|
components, and prior scale.}
|
||||||
\usage{
|
\usage{
|
||||||
add_seasonality(m, name, period, fourier.order, prior.scale = NULL)
|
add_seasonality(m, name, period, fourier.order, prior.scale = NULL,
|
||||||
|
mode = NULL)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{m}{Prophet object.}
|
\item{m}{Prophet object.}
|
||||||
|
|
@ -16,7 +17,9 @@ add_seasonality(m, name, period, fourier.order, prior.scale = NULL)
|
||||||
|
|
||||||
\item{fourier.order}{Int number of Fourier components to use.}
|
\item{fourier.order}{Int number of Fourier components to use.}
|
||||||
|
|
||||||
\item{prior.scale}{Float prior scale for this component.}
|
\item{prior.scale}{Optional float prior scale for this component.}
|
||||||
|
|
||||||
|
\item{mode}{Optional 'additive' or 'multiplicative'.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
The prophet model with the seasonality added.
|
The prophet model with the seasonality added.
|
||||||
|
|
@ -30,4 +33,9 @@ seasonalities are 10 and 3 respectively.
|
||||||
Increasing prior scale will allow this seasonality component more
|
Increasing prior scale will allow this seasonality component more
|
||||||
flexibility, decreasing will dampen it. If not provided, will use the
|
flexibility, decreasing will dampen it. If not provided, will use the
|
||||||
seasonality.prior.scale provided on Prophet initialization (defaults to 10).
|
seasonality.prior.scale provided on Prophet initialization (defaults to 10).
|
||||||
|
|
||||||
|
Mode can be specified as either 'additive' or 'multiplicative'. If not
|
||||||
|
specified, m$seasonality.mode will be used (defaults to 'additive').
|
||||||
|
Additive means the seasonality will be added to the trend, multiplicative
|
||||||
|
means it will multiply the trend.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
\alias{compile_stan_model}
|
\alias{compile_stan_model}
|
||||||
\title{Compile Stan model}
|
\title{Compile Stan model}
|
||||||
\usage{
|
\usage{
|
||||||
compile_stan_model(model)
|
compile_stan_model()
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{model}{String 'linear' or 'logistic' to specify a linear or logistic
|
\item{model}{String 'linear' or 'logistic' to specify a linear or logistic
|
||||||
|
|
|
||||||
20
R/man/coverage.Rd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{coverage}
|
||||||
|
\alias{coverage}
|
||||||
|
\title{Coverage}
|
||||||
|
\usage{
|
||||||
|
coverage(df, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{Cross-validation results dataframe.}
|
||||||
|
|
||||||
|
\item{w}{Aggregation window size.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Array of coverages
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Coverage
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
|
|
@ -23,8 +23,9 @@ horizon. If not provided, 0.5 * horizon is used.}
|
||||||
A dataframe with the forecast, actual value, and cutoff date.
|
A dataframe with the forecast, actual value, and cutoff date.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Computes forecasts from historical cutoff points. Beginning from initial,
|
Computes forecasts from historical cutoff points. Beginning from
|
||||||
makes cutoffs with a spacing of period up to (end - horizon).
|
(end - horizon), works backwards making cutoffs with a spacing of period
|
||||||
|
until initial is reached.
|
||||||
}
|
}
|
||||||
\details{
|
\details{
|
||||||
When period is equal to the time interval of the data, this is the
|
When period is equal to the time interval of the data, this is the
|
||||||
|
|
|
||||||
36
R/man/dyplot.prophet.Rd
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/plot.R
|
||||||
|
\name{dyplot.prophet}
|
||||||
|
\alias{dyplot.prophet}
|
||||||
|
\title{Plot the prophet forecast.}
|
||||||
|
\usage{
|
||||||
|
dyplot.prophet(x, fcst, uncertainty = TRUE, ...)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{x}{Prophet object.}
|
||||||
|
|
||||||
|
\item{fcst}{Data frame returned by predict(m, df).}
|
||||||
|
|
||||||
|
\item{uncertainty}{Boolean indicating if the uncertainty interval for yhat
|
||||||
|
should be plotted. Must be present in fcst as yhat_lower and yhat_upper.}
|
||||||
|
|
||||||
|
\item{...}{additional arguments}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
A dygraph plot.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Plot the prophet forecast.
|
||||||
|
}
|
||||||
|
\examples{
|
||||||
|
\dontrun{
|
||||||
|
history <- data.frame(
|
||||||
|
ds = seq(as.Date('2015-01-01'), as.Date('2016-01-01'), by = 'd'),
|
||||||
|
y = sin(1:366/200) + rnorm(366)/10)
|
||||||
|
m <- prophet(history)
|
||||||
|
future <- make_future_dataframe(m, periods = 365)
|
||||||
|
forecast <- predict(m, future)
|
||||||
|
dyplot.prophet(m, forecast)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,19 +4,19 @@
|
||||||
\alias{generate_cutoffs}
|
\alias{generate_cutoffs}
|
||||||
\title{Generate cutoff dates}
|
\title{Generate cutoff dates}
|
||||||
\usage{
|
\usage{
|
||||||
generate_cutoffs(df, horizon, k, period)
|
generate_cutoffs(df, horizon, initial, period)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{df}{Dataframe with historical data}
|
\item{df}{Dataframe with historical data.}
|
||||||
|
|
||||||
\item{horizon}{timediff forecast horizon}
|
\item{horizon}{timediff forecast horizon.}
|
||||||
|
|
||||||
\item{k}{integer number of forecast points}
|
\item{initial}{timediff initial window.}
|
||||||
|
|
||||||
\item{period}{timediff Simulated forecasts are done with this period.}
|
\item{period}{timediff Simulated forecasts are done with this period.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
Array of datetimes
|
Array of datetimes.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Generate cutoff dates
|
Generate cutoff dates
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
% Generated by roxygen2: do not edit by hand
|
|
||||||
% Please edit documentation in R/prophet.R
|
|
||||||
\name{get_changepoint_matrix}
|
|
||||||
\alias{get_changepoint_matrix}
|
|
||||||
\title{Gets changepoint matrix for history dataframe.}
|
|
||||||
\usage{
|
|
||||||
get_changepoint_matrix(m)
|
|
||||||
}
|
|
||||||
\arguments{
|
|
||||||
\item{m}{Prophet object.}
|
|
||||||
}
|
|
||||||
\value{
|
|
||||||
array of indexes.
|
|
||||||
}
|
|
||||||
\description{
|
|
||||||
Gets changepoint matrix for history dataframe.
|
|
||||||
}
|
|
||||||
\keyword{internal}
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
\alias{get_prophet_stan_model}
|
\alias{get_prophet_stan_model}
|
||||||
\title{Load compiled Stan model}
|
\title{Load compiled Stan model}
|
||||||
\usage{
|
\usage{
|
||||||
get_prophet_stan_model(model)
|
get_prophet_stan_model()
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{model}{String 'linear' or 'logistic' to specify a linear or logistic
|
\item{model}{String 'linear' or 'logistic' to specify a linear or logistic
|
||||||
|
|
|
||||||
20
R/man/mae.Rd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{mae}
|
||||||
|
\alias{mae}
|
||||||
|
\title{Mean absolute error}
|
||||||
|
\usage{
|
||||||
|
mae(df, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{Cross-validation results dataframe.}
|
||||||
|
|
||||||
|
\item{w}{Aggregation window size.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Array of mean absolute errors.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Mean absolute error
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
|
|
@ -18,6 +18,10 @@ List with items
|
||||||
seasonal.features: Dataframe with regressor features,
|
seasonal.features: Dataframe with regressor features,
|
||||||
prior.scales: Array of prior scales for each colum of the features
|
prior.scales: Array of prior scales for each colum of the features
|
||||||
dataframe.
|
dataframe.
|
||||||
|
component.cols: Dataframe with indicators for which regression components
|
||||||
|
correspond to which columns.
|
||||||
|
modes: List with keys 'additive' and 'multiplicative' with arrays of
|
||||||
|
component names for each mode of seasonality.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Dataframe with seasonality features.
|
Dataframe with seasonality features.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ make_holiday_features(m, dates)
|
||||||
A list with entries
|
A list with entries
|
||||||
holiday.features: dataframe with a column for each holiday.
|
holiday.features: dataframe with a column for each holiday.
|
||||||
prior.scales: array of prior scales for each holiday column.
|
prior.scales: array of prior scales for each holiday column.
|
||||||
|
holiday.names: array of names of all holidays.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Construct a matrix of holiday features.
|
Construct a matrix of holiday features.
|
||||||
|
|
|
||||||
20
R/man/mape.Rd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{mape}
|
||||||
|
\alias{mape}
|
||||||
|
\title{Mean absolute percent error}
|
||||||
|
\usage{
|
||||||
|
mape(df, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{Cross-validation results dataframe.}
|
||||||
|
|
||||||
|
\item{w}{Aggregation window size.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Array of mean absolute percent errors.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Mean absolute percent error
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
20
R/man/mse.Rd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{mse}
|
||||||
|
\alias{mse}
|
||||||
|
\title{Mean squared error}
|
||||||
|
\usage{
|
||||||
|
mse(df, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{Cross-validation results dataframe.}
|
||||||
|
|
||||||
|
\item{w}{Aggregation window size.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Array of mean squared errors.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Mean squared error
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
46
R/man/performance_metrics.Rd
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{performance_metrics}
|
||||||
|
\alias{performance_metrics}
|
||||||
|
\title{Compute performance metrics from cross-validation results.}
|
||||||
|
\usage{
|
||||||
|
performance_metrics(df, metrics = NULL, rolling_window = 0.1)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{The dataframe returned by cross_validation.}
|
||||||
|
|
||||||
|
\item{metrics}{An array of performance metrics to compute. If not provided,
|
||||||
|
will use c('mse', 'rmse', 'mae', 'mape', 'coverage').}
|
||||||
|
|
||||||
|
\item{rolling_window}{Proportion of data to use in each rolling window for
|
||||||
|
computing the metrics. Should be in [0, 1].}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
A dataframe with a column for each metric, and column 'horizon'.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Computes a suite of performance metrics on the output of cross-validation.
|
||||||
|
By default the following metrics are included:
|
||||||
|
'mse': mean squared error
|
||||||
|
'rmse': root mean squared error
|
||||||
|
'mae': mean absolute error
|
||||||
|
'mape': mean percent error
|
||||||
|
'coverage': coverage of the upper and lower intervals
|
||||||
|
}
|
||||||
|
\details{
|
||||||
|
A subset of these can be specified by passing a list of names as the
|
||||||
|
`metrics` argument.
|
||||||
|
|
||||||
|
Metrics are calculated over a rolling window of cross validation
|
||||||
|
predictions, after sorting by horizon. The size of that window (number of
|
||||||
|
simulated forecast points) is determined by the rolling_window argument,
|
||||||
|
which specifies a proportion of simulated forecast points to include in
|
||||||
|
each window. rolling_window=0 will compute it separately for each simulated
|
||||||
|
forecast point (i.e., 'mse' will actually be squared error with no mean).
|
||||||
|
The default of rolling_window=0.1 will use 10% of the rows in df in each
|
||||||
|
window. rolling_window=1 will compute the metric across all simulated
|
||||||
|
forecast points. The results are set to the right edge of the window.
|
||||||
|
|
||||||
|
The output is a dataframe containing column 'horizon' along with columns
|
||||||
|
for each of the metrics computed.
|
||||||
|
}
|
||||||
36
R/man/plot_cross_validation_metric.Rd
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/plot.R
|
||||||
|
\name{plot_cross_validation_metric}
|
||||||
|
\alias{plot_cross_validation_metric}
|
||||||
|
\title{Plot a performance metric vs. forecast horizon from cross validation.
|
||||||
|
Cross validation produces a collection of out-of-sample model predictions
|
||||||
|
that can be compared to actual values, at a range of different horizons
|
||||||
|
(distance from the cutoff). This computes a specified performance metric
|
||||||
|
for each prediction, and aggregated over a rolling window with horizon.}
|
||||||
|
\usage{
|
||||||
|
plot_cross_validation_metric(df_cv, metric, rolling_window = 0.1)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df_cv}{The output from fbprophet.diagnostics.cross_validation.}
|
||||||
|
|
||||||
|
\item{metric}{Metric name, one of 'mse', 'rmse', 'mae', 'mape', 'coverage'.}
|
||||||
|
|
||||||
|
\item{rolling_window}{Proportion of data to use for rolling average of
|
||||||
|
metric. In [0, 1]. Defaults to 0.1.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
A ggplot2 plot.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
This uses fbprophet.diagnostics.performance_metrics to compute the metrics.
|
||||||
|
Valid values of metric are 'mse', 'rmse', 'mae', 'mape', and 'coverage'.
|
||||||
|
}
|
||||||
|
\details{
|
||||||
|
rolling_window is the proportion of data included in the rolling window of
|
||||||
|
aggregation. The default value of 0.1 means 10% of data are included in the
|
||||||
|
aggregation for computing the metric.
|
||||||
|
|
||||||
|
As a concrete example, if metric='mse', then this plot will show the
|
||||||
|
squared error for each cross validation prediction, along with the MSE
|
||||||
|
averaged over rolling windows of 10% of the data.
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
\alias{plot_forecast_component}
|
\alias{plot_forecast_component}
|
||||||
\title{Plot a particular component of the forecast.}
|
\title{Plot a particular component of the forecast.}
|
||||||
\usage{
|
\usage{
|
||||||
plot_forecast_component(fcst, name, uncertainty = TRUE, plot_cap = FALSE)
|
plot_forecast_component(m, fcst, name, uncertainty = TRUE, plot_cap = FALSE)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
|
\item{m}{Prophet model}
|
||||||
|
|
||||||
\item{fcst}{Dataframe output of `predict`.}
|
\item{fcst}{Dataframe output of `predict`.}
|
||||||
|
|
||||||
\item{name}{String name of the component to plot (column of fcst).}
|
\item{name}{String name of the component to plot (column of fcst).}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,8 @@ predictive_samples(m, df)
|
||||||
(column cap) if logistic growth.}
|
(column cap) if logistic growth.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
A list with items "trend", "seasonal", and "yhat" containing
|
A list with items "trend" and "yhat" containing
|
||||||
posterior predictive samples for that component. "seasonal" is the sum
|
posterior predictive samples for that component.
|
||||||
of seasonalities, holidays, and added regressors.
|
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Sample from the posterior predictive distribution.
|
Sample from the posterior predictive distribution.
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@
|
||||||
\title{Prophet forecaster.}
|
\title{Prophet forecaster.}
|
||||||
\usage{
|
\usage{
|
||||||
prophet(df = NULL, growth = "linear", changepoints = NULL,
|
prophet(df = NULL, growth = "linear", changepoints = NULL,
|
||||||
n.changepoints = 25, yearly.seasonality = "auto",
|
n.changepoints = 25, changepoint.range = 0.8,
|
||||||
weekly.seasonality = "auto", daily.seasonality = "auto",
|
yearly.seasonality = "auto", weekly.seasonality = "auto",
|
||||||
holidays = NULL, seasonality.prior.scale = 10,
|
daily.seasonality = "auto", holidays = NULL,
|
||||||
|
seasonality.mode = "additive", seasonality.prior.scale = 10,
|
||||||
holidays.prior.scale = 10, changepoint.prior.scale = 0.05,
|
holidays.prior.scale = 10, changepoint.prior.scale = 0.05,
|
||||||
mcmc.samples = 0, interval.width = 0.8, uncertainty.samples = 1000,
|
mcmc.samples = 0, interval.width = 0.8, uncertainty.samples = 1000,
|
||||||
fit = TRUE, ...)
|
fit = TRUE, ...)
|
||||||
|
|
@ -29,7 +30,11 @@ automatically.}
|
||||||
\item{n.changepoints}{Number of potential changepoints to include. Not used
|
\item{n.changepoints}{Number of potential changepoints to include. Not used
|
||||||
if input `changepoints` is supplied. If `changepoints` is not supplied,
|
if input `changepoints` is supplied. If `changepoints` is not supplied,
|
||||||
then n.changepoints potential changepoints are selected uniformly from the
|
then n.changepoints potential changepoints are selected uniformly from the
|
||||||
first 80 percent of df$ds.}
|
first `changepoint.range` proportion of df$ds.}
|
||||||
|
|
||||||
|
\item{changepoint.range}{Proportion of history in which trend changepoints
|
||||||
|
will be estimated. Defaults to 0.8 for the first 80%. Not used if
|
||||||
|
`changepoints` is specified.}
|
||||||
|
|
||||||
\item{yearly.seasonality}{Fit yearly seasonality. Can be 'auto', TRUE,
|
\item{yearly.seasonality}{Fit yearly seasonality. Can be 'auto', TRUE,
|
||||||
FALSE, or a number of Fourier terms to generate.}
|
FALSE, or a number of Fourier terms to generate.}
|
||||||
|
|
@ -46,6 +51,8 @@ range of days around the date to be included as holidays. lower_window=-2
|
||||||
will include 2 days prior to the date as holidays. Also optionally can have
|
will include 2 days prior to the date as holidays. Also optionally can have
|
||||||
a column prior_scale specifying the prior scale for each holiday.}
|
a column prior_scale specifying the prior scale for each holiday.}
|
||||||
|
|
||||||
|
\item{seasonality.mode}{'additive' (default) or 'multiplicative'.}
|
||||||
|
|
||||||
\item{seasonality.prior.scale}{Parameter modulating the strength of the
|
\item{seasonality.prior.scale}{Parameter modulating the strength of the
|
||||||
seasonality model. Larger values allow the model to fit larger seasonal
|
seasonality model. Larger values allow the model to fit larger seasonal
|
||||||
fluctuations, smaller values dampen the seasonality. Can be specified for
|
fluctuations, smaller values dampen the seasonality. Can be specified for
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
% Generated by roxygen2: do not edit by hand
|
% Generated by roxygen2: do not edit by hand
|
||||||
% Please edit documentation in R/prophet.R
|
% Please edit documentation in R/diagnostics.R
|
||||||
\name{prophet_copy}
|
\name{prophet_copy}
|
||||||
\alias{prophet_copy}
|
\alias{prophet_copy}
|
||||||
\title{Copy Prophet object.}
|
\title{Copy Prophet object.}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
\name{prophet_plot_components}
|
\name{prophet_plot_components}
|
||||||
\alias{prophet_plot_components}
|
\alias{prophet_plot_components}
|
||||||
\title{Plot the components of a prophet forecast.
|
\title{Plot the components of a prophet forecast.
|
||||||
Prints a ggplot2 with panels for trend, weekly and yearly seasonalities if
|
Prints a ggplot2 with whichever are available of: trend, holidays, weekly
|
||||||
present, and holidays if present.}
|
seasonality, yearly seasonality, and additive and multiplicative extra
|
||||||
|
regressors.}
|
||||||
\usage{
|
\usage{
|
||||||
prophet_plot_components(m, fcst, uncertainty = TRUE, plot_cap = TRUE,
|
prophet_plot_components(m, fcst, uncertainty = TRUE, plot_cap = TRUE,
|
||||||
weekly_start = 0, yearly_start = 0)
|
weekly_start = 0, yearly_start = 0, render_plot = TRUE)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{m}{Prophet object.}
|
\item{m}{Prophet object.}
|
||||||
|
|
@ -27,12 +28,16 @@ to Monday, and so on.}
|
||||||
\item{yearly_start}{Integer specifying the start day of the yearly
|
\item{yearly_start}{Integer specifying the start day of the yearly
|
||||||
seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts by 1 day
|
seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts by 1 day
|
||||||
to Jan 2, and so on.}
|
to Jan 2, and so on.}
|
||||||
|
|
||||||
|
\item{render_plot}{Boolean indicating if the plots should be rendered.
|
||||||
|
Set to FALSE if you want the function to only return the list of panels.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
Invisibly return a list containing the plotted ggplot objects
|
Invisibly return a list containing the plotted ggplot objects
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Plot the components of a prophet forecast.
|
Plot the components of a prophet forecast.
|
||||||
Prints a ggplot2 with panels for trend, weekly and yearly seasonalities if
|
Prints a ggplot2 with whichever are available of: trend, holidays, weekly
|
||||||
present, and holidays if present.
|
seasonality, yearly seasonality, and additive and multiplicative extra
|
||||||
|
regressors.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
R/man/regressor_column_matrix.Rd
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/prophet.R
|
||||||
|
\name{regressor_column_matrix}
|
||||||
|
\alias{regressor_column_matrix}
|
||||||
|
\title{Dataframe indicating which columns of the feature matrix correspond to
|
||||||
|
which seasonality/regressor components.}
|
||||||
|
\usage{
|
||||||
|
regressor_column_matrix(m, seasonal.features, modes)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{m}{Prophet object.}
|
||||||
|
|
||||||
|
\item{seasonal.features}{Constructed seasonal features dataframe.}
|
||||||
|
|
||||||
|
\item{modes}{List with keys 'additive' and 'multiplicative' with arrays of
|
||||||
|
component names for each mode of seasonality.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
List with items
|
||||||
|
component.cols: A binary indicator dataframe with columns seasonal
|
||||||
|
components and rows columns in seasonal.features. Entry is 1 if that
|
||||||
|
column is used in that component.
|
||||||
|
modes: Updated input with combination components.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Includes combination components, like 'additive_terms'. These combination
|
||||||
|
components will be added to the 'modes' input.
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
20
R/man/rmse.Rd
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{rmse}
|
||||||
|
\alias{rmse}
|
||||||
|
\title{Root mean squared error}
|
||||||
|
\usage{
|
||||||
|
rmse(df, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{df}{Cross-validation results dataframe.}
|
||||||
|
|
||||||
|
\item{w}{Aggregation window size.}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Array of root mean squared errors.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Root mean squared error
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
21
R/man/rolling_mean.Rd
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/diagnostics.R
|
||||||
|
\name{rolling_mean}
|
||||||
|
\alias{rolling_mean}
|
||||||
|
\title{Compute a rolling mean of x}
|
||||||
|
\usage{
|
||||||
|
rolling_mean(x, w)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{x}{Array.}
|
||||||
|
|
||||||
|
\item{w}{Integer window size (number of elements).}
|
||||||
|
}
|
||||||
|
\value{
|
||||||
|
Rolling mean of x with window size w.
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Right-aligned. Padded with NAs on the front so the output is the same
|
||||||
|
size as x.
|
||||||
|
}
|
||||||
|
\keyword{internal}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
\alias{sample_model}
|
\alias{sample_model}
|
||||||
\title{Simulate observations from the extrapolated generative model.}
|
\title{Simulate observations from the extrapolated generative model.}
|
||||||
\usage{
|
\usage{
|
||||||
sample_model(m, df, seasonal.features, iteration)
|
sample_model(m, df, seasonal.features, iteration, s_a, s_m)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{m}{Prophet object.}
|
\item{m}{Prophet object.}
|
||||||
|
|
@ -14,9 +14,13 @@ sample_model(m, df, seasonal.features, iteration)
|
||||||
\item{seasonal.features}{Data frame of seasonal features}
|
\item{seasonal.features}{Data frame of seasonal features}
|
||||||
|
|
||||||
\item{iteration}{Int sampling iteration to use parameters from.}
|
\item{iteration}{Int sampling iteration to use parameters from.}
|
||||||
|
|
||||||
|
\item{s_a}{Indicator vector for additive components}
|
||||||
|
|
||||||
|
\item{s_m}{Indicator vector for multiplicative components}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
List of trend, seasonality, and yhat, each a vector like df$t.
|
List of trend and yhat, each a vector like df$t.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Simulate observations from the extrapolated generative model.
|
Simulate observations from the extrapolated generative model.
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ sample_posterior_predictive(m, df)
|
||||||
\item{df}{Prediction dataframe.}
|
\item{df}{Prediction dataframe.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
List with posterior predictive samples for each component.
|
List with posterior predictive samples for the forecast yhat and
|
||||||
|
for the trend component.
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
Prophet posterior predictive samples.
|
Prophet posterior predictive samples.
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
% Generated by roxygen2: do not edit by hand
|
|
||||||
% Please edit documentation in R/diagnostics.R
|
|
||||||
\name{simulated_historical_forecasts}
|
|
||||||
\alias{simulated_historical_forecasts}
|
|
||||||
\title{Simulated historical forecasts.}
|
|
||||||
\usage{
|
|
||||||
simulated_historical_forecasts(model, horizon, units, k, period = NULL)
|
|
||||||
}
|
|
||||||
\arguments{
|
|
||||||
\item{model}{Fitted Prophet model.}
|
|
||||||
|
|
||||||
\item{horizon}{Integer size of the horizon}
|
|
||||||
|
|
||||||
\item{units}{String unit of the horizon, e.g., "days", "secs".}
|
|
||||||
|
|
||||||
\item{k}{integer number of forecast points}
|
|
||||||
|
|
||||||
\item{period}{Integer amount of time between cutoff dates. Same units as
|
|
||||||
horizon. If not provided, will use 0.5 * horizon.}
|
|
||||||
}
|
|
||||||
\value{
|
|
||||||
A dataframe with the forecast, actual value, and cutoff date.
|
|
||||||
}
|
|
||||||
\description{
|
|
||||||
Make forecasts from k historical cutoff points, working backwards from
|
|
||||||
(end - horizon) with a spacing of period between each cutoff.
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
|
|
||||||
|
|
||||||
packageStartupMessage('Compiling models (this will take a minute...)')
|
packageStartupMessage('Compiling model (this will take a minute...)')
|
||||||
|
|
||||||
dest <- file.path(R_PACKAGE_DIR, paste0('libs', R_ARCH))
|
dest <- file.path(R_PACKAGE_DIR, paste0('libs', R_ARCH))
|
||||||
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
|
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
|
||||||
|
|
||||||
packageStartupMessage(paste('Writing models to:', dest))
|
packageStartupMessage(paste('Writing model to:', dest))
|
||||||
packageStartupMessage(paste('Compiling using binary:', R.home('bin')))
|
packageStartupMessage(paste('Compiling using binary:', R.home('bin')))
|
||||||
|
|
||||||
logistic.growth.src <- file.path(R_PACKAGE_SOURCE, 'inst', 'stan', 'prophet_logistic_growth.stan')
|
model.src <- file.path(R_PACKAGE_SOURCE, 'inst', 'stan', 'prophet.stan')
|
||||||
logistic.growth.binary <- file.path(dest, 'prophet_logistic_growth.RData')
|
model.binary <- file.path(dest, 'prophet_stan_model.RData')
|
||||||
logistic.growth.stanc <- rstan::stanc(logistic.growth.src)
|
model.stanc <- rstan::stanc(model.src)
|
||||||
logistic.growth.stanm <- rstan::stan_model(stanc_ret = logistic.growth.stanc,
|
model.stanm <- rstan::stan_model(
|
||||||
model_name = 'logistic_growth')
|
stanc_ret = model.stanc,
|
||||||
save('logistic.growth.stanm', file = logistic.growth.binary)
|
model_name = 'prophet_model'
|
||||||
|
)
|
||||||
|
save('model.stanm', file = model.binary)
|
||||||
|
|
||||||
linear.growth.src <- file.path(R_PACKAGE_SOURCE, 'inst', 'stan', 'prophet_linear_growth.stan')
|
packageStartupMessage('------ Model successfully compiled!')
|
||||||
linear.growth.binary <- file.path(dest, 'prophet_linear_growth.RData')
|
|
||||||
linear.growth.stanc <- rstan::stanc(linear.growth.src)
|
|
||||||
linear.growth.stanm <- rstan::stan_model(stanc_ret = linear.growth.stanc,
|
|
||||||
model_name = 'linear_growth')
|
|
||||||
save('linear.growth.stanm', file = linear.growth.binary)
|
|
||||||
|
|
||||||
packageStartupMessage('------ Models successfully compiled!')
|
|
||||||
packageStartupMessage('You can ignore any compiler warnings above.')
|
packageStartupMessage('You can ignore any compiler warnings above.')
|
||||||
|
|
|
||||||
|
|
@ -4,77 +4,9 @@ context("Prophet diagnostics tests")
|
||||||
## Makes R CMD CHECK happy due to dplyr syntax below
|
## Makes R CMD CHECK happy due to dplyr syntax below
|
||||||
globalVariables(c("y", "yhat"))
|
globalVariables(c("y", "yhat"))
|
||||||
|
|
||||||
DATA <- head(read.csv('data.csv'), 100)
|
DATA_all <- read.csv('data.csv')
|
||||||
DATA$ds <- as.Date(DATA$ds)
|
DATA_all$ds <- as.Date(DATA_all$ds)
|
||||||
|
DATA <- head(DATA_all, 100)
|
||||||
test_that("simulated_historical_forecasts", {
|
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
|
||||||
m <- prophet(DATA)
|
|
||||||
k <- 2
|
|
||||||
for (p in c(1, 10)) {
|
|
||||||
for (h in c(1, 3)) {
|
|
||||||
df.shf <- simulated_historical_forecasts(
|
|
||||||
m, horizon = h, units = 'days', k = k, period = p)
|
|
||||||
# All cutoff dates should be less than ds dates
|
|
||||||
expect_true(all(df.shf$cutoff < df.shf$ds))
|
|
||||||
# The unique size of output cutoff should be equal to 'k'
|
|
||||||
expect_equal(length(unique(df.shf$cutoff)), k)
|
|
||||||
expect_equal(max(df.shf$ds - df.shf$cutoff),
|
|
||||||
as.difftime(h, units = 'days'))
|
|
||||||
dc <- diff(df.shf$cutoff)
|
|
||||||
dc <- min(dc[dc > 0])
|
|
||||||
expect_true(dc >= as.difftime(p, units = 'days'))
|
|
||||||
# Each y in df_shf and DATA with same ds should be equal
|
|
||||||
df.merged <- dplyr::left_join(df.shf, m$history, by="ds")
|
|
||||||
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test_that("simulated_historical_forecasts_logistic", {
|
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
|
||||||
df <- DATA
|
|
||||||
df$cap <- 40
|
|
||||||
m <- prophet(df, growth='logistic')
|
|
||||||
df.shf <- simulated_historical_forecasts(
|
|
||||||
m, horizon = 3, units = 'days', k = 2, period = 3)
|
|
||||||
# All cutoff dates should be less than ds dates
|
|
||||||
expect_true(all(df.shf$cutoff < df.shf$ds))
|
|
||||||
# The unique size of output cutoff should be equal to 'k'
|
|
||||||
expect_equal(length(unique(df.shf$cutoff)), 2)
|
|
||||||
# Each y in df_shf and DATA with same ds should be equal
|
|
||||||
df.merged <- dplyr::left_join(df.shf, m$history, by="ds")
|
|
||||||
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test_that("simulated_historical_forecasts_extra_regressors", {
|
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
|
||||||
df <- DATA
|
|
||||||
df$extra <- seq(0, nrow(df) - 1)
|
|
||||||
m <- prophet()
|
|
||||||
m <- add_seasonality(m, name = 'monthly', period = 30.5, fourier.order = 5)
|
|
||||||
m <- add_regressor(m, 'extra')
|
|
||||||
m <- fit.prophet(m, df)
|
|
||||||
df.shf <- simulated_historical_forecasts(
|
|
||||||
m, horizon = 3, units = 'days', k = 2, period = 3)
|
|
||||||
# All cutoff dates should be less than ds dates
|
|
||||||
expect_true(all(df.shf$cutoff < df.shf$ds))
|
|
||||||
# The unique size of output cutoff should be equal to 'k'
|
|
||||||
expect_equal(length(unique(df.shf$cutoff)), 2)
|
|
||||||
# Each y in df_shf and DATA with same ds should be equal
|
|
||||||
df.merged <- dplyr::left_join(df.shf, m$history, by="ds")
|
|
||||||
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test_that("simulated_historical_forecasts_default_value_check", {
|
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
|
||||||
m <- prophet(DATA)
|
|
||||||
df.shf1 <- simulated_historical_forecasts(
|
|
||||||
m, horizon = 10, units = 'days', k = 1)
|
|
||||||
df.shf2 <- simulated_historical_forecasts(
|
|
||||||
m, horizon = 10, units = 'days', k = 1, period = 5)
|
|
||||||
expect_equal(sum(dplyr::select(df.shf1 - df.shf2, y, yhat)), 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
test_that("cross_validation", {
|
test_that("cross_validation", {
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
|
@ -84,14 +16,59 @@ test_that("cross_validation", {
|
||||||
ts <- min(DATA$ds)
|
ts <- min(DATA$ds)
|
||||||
horizon <- as.difftime(4, units = "days")
|
horizon <- as.difftime(4, units = "days")
|
||||||
period <- as.difftime(10, units = "days")
|
period <- as.difftime(10, units = "days")
|
||||||
k <- 5
|
initial <- as.difftime(115, units = "days")
|
||||||
df.cv <- cross_validation(
|
df.cv <- cross_validation(
|
||||||
m, horizon = 4, units = "days", period = 10, initial = 90)
|
m, horizon = 4, units = "days", period = 10, initial = 115)
|
||||||
expect_equal(length(unique(df.cv$cutoff)), k)
|
expect_equal(length(unique(df.cv$cutoff)), 3)
|
||||||
expect_equal(max(df.cv$ds - df.cv$cutoff), horizon)
|
expect_equal(max(df.cv$ds - df.cv$cutoff), horizon)
|
||||||
|
expect_true(min(df.cv$cutoff) >= ts + initial)
|
||||||
dc <- diff(df.cv$cutoff)
|
dc <- diff(df.cv$cutoff)
|
||||||
dc <- min(dc[dc > 0])
|
dc <- min(dc[dc > 0])
|
||||||
expect_true(dc >= period)
|
expect_true(dc >= period)
|
||||||
|
expect_true(all(df.cv$cutoff < df.cv$ds))
|
||||||
|
# Each y in df.cv and DATA with same ds should be equal
|
||||||
|
df.merged <- dplyr::left_join(df.cv, m$history, by="ds")
|
||||||
|
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
||||||
|
df.cv <- cross_validation(
|
||||||
|
m, horizon = 4, units = "days", period = 10, initial = 135)
|
||||||
|
expect_equal(length(unique(df.cv$cutoff)), 1)
|
||||||
|
expect_error(
|
||||||
|
cross_validation(
|
||||||
|
m, horizon = 10, units = "days", period = 10, initial = 140)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("cross_validation_logistic", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
df <- DATA
|
||||||
|
df$cap <- 40
|
||||||
|
m <- prophet(df, growth = 'logistic')
|
||||||
|
df.cv <- cross_validation(
|
||||||
|
m, horizon = 1, units = "days", period = 1, initial = 140)
|
||||||
|
expect_equal(length(unique(df.cv$cutoff)), 2)
|
||||||
|
expect_true(all(df.cv$cutoff < df.cv$ds))
|
||||||
|
df.merged <- dplyr::left_join(df.cv, m$history, by="ds")
|
||||||
|
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("cross_validation_extra_regressors", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
df <- DATA
|
||||||
|
df$extra <- seq(0, nrow(df) - 1)
|
||||||
|
m <- prophet()
|
||||||
|
m <- add_seasonality(m, name = 'monthly', period = 30.5, fourier.order = 5)
|
||||||
|
m <- add_regressor(m, 'extra')
|
||||||
|
m <- fit.prophet(m, df)
|
||||||
|
df.cv <- cross_validation(
|
||||||
|
m, horizon = 4, units = "days", period = 4, initial = 135)
|
||||||
|
expect_equal(length(unique(df.cv$cutoff)), 2)
|
||||||
|
period <- as.difftime(4, units = "days")
|
||||||
|
dc <- diff(df.cv$cutoff)
|
||||||
|
dc <- min(dc[dc > 0])
|
||||||
|
expect_true(dc >= period)
|
||||||
|
expect_true(all(df.cv$cutoff < df.cv$ds))
|
||||||
|
df.merged <- dplyr::left_join(df.cv, m$history, by="ds")
|
||||||
|
expect_equal(sum((df.merged$y.x - df.merged$y.y) ** 2), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("cross_validation_default_value_check", {
|
test_that("cross_validation_default_value_check", {
|
||||||
|
|
@ -103,3 +80,103 @@ test_that("cross_validation_default_value_check", {
|
||||||
m, horizon = 32, units = 'days', period = 10, initial = 96)
|
m, horizon = 32, units = 'days', period = 10, initial = 96)
|
||||||
expect_equal(sum(dplyr::select(df.cv1 - df.cv2, y, yhat)), 0)
|
expect_equal(sum(dplyr::select(df.cv1 - df.cv2, y, yhat)), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test_that("performance_metrics", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
m <- prophet(DATA)
|
||||||
|
df_cv <- cross_validation(
|
||||||
|
m, horizon = 4, units = "days", period = 10, initial = 90)
|
||||||
|
# Aggregation level none
|
||||||
|
df_none <- performance_metrics(df_cv, rolling_window = 0)
|
||||||
|
expect_true(all(
|
||||||
|
sort(colnames(df_none))
|
||||||
|
== sort(c('horizon', 'coverage', 'mae', 'mape', 'mse', 'rmse'))
|
||||||
|
))
|
||||||
|
expect_equal(nrow(df_none), 16)
|
||||||
|
# Aggregation level 0.2
|
||||||
|
df_horizon <- performance_metrics(df_cv, rolling_window = 0.2)
|
||||||
|
expect_equal(length(unique(df_horizon$horizon)), 4)
|
||||||
|
expect_equal(nrow(df_horizon), 14)
|
||||||
|
# Aggregation level all
|
||||||
|
df_all <- performance_metrics(df_cv, rolling_window = 1)
|
||||||
|
expect_equal(nrow(df_all), 1)
|
||||||
|
for (metric in c('mse', 'mape', 'mae', 'coverage')) {
|
||||||
|
expect_equal(df_all[[metric]][1], mean(df_none[[metric]]))
|
||||||
|
}
|
||||||
|
# Custom list of metrics
|
||||||
|
df_horizon <- performance_metrics(df_cv, metrics = c('coverage', 'mse'))
|
||||||
|
expect_true(all(
|
||||||
|
sort(colnames(df_horizon)) == sort(c('coverage', 'mse', 'horizon'))
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("copy", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
df <- DATA_all
|
||||||
|
df$cap <- 200.
|
||||||
|
df$binary_feature <- c(rep(0, 255), rep(1, 255))
|
||||||
|
inputs <- list(
|
||||||
|
growth = c('linear', 'logistic'),
|
||||||
|
yearly.seasonality = c(TRUE, FALSE),
|
||||||
|
weekly.seasonality = c(TRUE, FALSE),
|
||||||
|
daily.seasonality = c(TRUE, FALSE),
|
||||||
|
holidays = c('null', 'insert_dataframe'),
|
||||||
|
seasonality.mode = c('additive', 'multiplicative')
|
||||||
|
)
|
||||||
|
products <- expand.grid(inputs)
|
||||||
|
for (i in 1:length(products)) {
|
||||||
|
if (products$holidays[i] == 'insert_dataframe') {
|
||||||
|
holidays <- data.frame(ds=c('2016-12-25'), holiday=c('x'))
|
||||||
|
} else {
|
||||||
|
holidays <- NULL
|
||||||
|
}
|
||||||
|
m1 <- prophet(
|
||||||
|
growth = as.character(products$growth[i]),
|
||||||
|
changepoints = NULL,
|
||||||
|
n.changepoints = 3,
|
||||||
|
changepoint.range = 0.9,
|
||||||
|
yearly.seasonality = products$yearly.seasonality[i],
|
||||||
|
weekly.seasonality = products$weekly.seasonality[i],
|
||||||
|
daily.seasonality = products$daily.seasonality[i],
|
||||||
|
holidays = holidays,
|
||||||
|
seasonality.prior.scale = 1.1,
|
||||||
|
holidays.prior.scale = 1.1,
|
||||||
|
changepoints.prior.scale = 0.1,
|
||||||
|
mcmc.samples = 100,
|
||||||
|
interval.width = 0.9,
|
||||||
|
uncertainty.samples = 200,
|
||||||
|
fit = FALSE
|
||||||
|
)
|
||||||
|
out <- prophet:::setup_dataframe(m1, df, initialize_scales = TRUE)
|
||||||
|
m1 <- out$m
|
||||||
|
m1$history <- out$df
|
||||||
|
m1 <- prophet:::set_auto_seasonalities(m1)
|
||||||
|
m2 <- prophet:::prophet_copy(m1)
|
||||||
|
# Values should be copied correctly
|
||||||
|
args <- c('growth', 'changepoints', 'n.changepoints', 'holidays',
|
||||||
|
'seasonality.prior.scale', 'holidays.prior.scale',
|
||||||
|
'changepoints.prior.scale', 'mcmc.samples', 'interval.width',
|
||||||
|
'uncertainty.samples', 'seasonality.mode', 'changepoint.range')
|
||||||
|
for (arg in args) {
|
||||||
|
expect_equal(m1[[arg]], m2[[arg]])
|
||||||
|
}
|
||||||
|
expect_equal(FALSE, m2$yearly.seasonality)
|
||||||
|
expect_equal(FALSE, m2$weekly.seasonality)
|
||||||
|
expect_equal(FALSE, m2$daily.seasonality)
|
||||||
|
expect_equal(m1$yearly.seasonality, 'yearly' %in% names(m2$seasonalities))
|
||||||
|
expect_equal(m1$weekly.seasonality, 'weekly' %in% names(m2$seasonalities))
|
||||||
|
expect_equal(m1$daily.seasonality, 'daily' %in% names(m2$seasonalities))
|
||||||
|
}
|
||||||
|
# Check for cutoff and custom seasonality and extra regressors
|
||||||
|
changepoints <- seq.Date(as.Date('2012-06-15'), as.Date('2012-09-15'), by='d')
|
||||||
|
cutoff <- as.Date('2012-07-25')
|
||||||
|
m1 <- prophet(changepoints = changepoints)
|
||||||
|
m1 <- add_seasonality(m1, 'custom', 10, 5)
|
||||||
|
m1 <- add_regressor(m1, 'binary_feature')
|
||||||
|
m1 <- fit.prophet(m1, df)
|
||||||
|
m2 <- prophet:::prophet_copy(m1, cutoff)
|
||||||
|
changepoints <- changepoints[changepoints <= cutoff]
|
||||||
|
expect_equal(prophet:::set_date(changepoints), m2$changepoints)
|
||||||
|
expect_true('custom' %in% names(m2$seasonalities))
|
||||||
|
expect_true('binary_feature' %in% names(m2$extra_regressors))
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -127,11 +127,26 @@ test_that("get_changepoints", {
|
||||||
cp <- m$changepoints.t
|
cp <- m$changepoints.t
|
||||||
expect_equal(length(cp), m$n.changepoints)
|
expect_equal(length(cp), m$n.changepoints)
|
||||||
expect_true(min(cp) > 0)
|
expect_true(min(cp) > 0)
|
||||||
expect_true(max(cp) < N)
|
expect_true(max(cp) <= history$t[ceiling(0.8 * length(history$t))])
|
||||||
|
})
|
||||||
|
|
||||||
mat <- prophet:::get_changepoint_matrix(m)
|
test_that("set_changepoint_range", {
|
||||||
expect_equal(nrow(mat), floor(N / 2))
|
history <- train
|
||||||
expect_equal(ncol(mat), m$n.changepoints)
|
m <- prophet(history, fit = FALSE, changepoint.range = 0.4)
|
||||||
|
|
||||||
|
out <- prophet:::setup_dataframe(m, history, initialize_scales = TRUE)
|
||||||
|
history <- out$df
|
||||||
|
m <- out$m
|
||||||
|
m$history <- history
|
||||||
|
|
||||||
|
m <- prophet:::set_changepoints(m)
|
||||||
|
|
||||||
|
cp <- m$changepoints.t
|
||||||
|
expect_equal(length(cp), m$n.changepoints)
|
||||||
|
expect_true(min(cp) > 0)
|
||||||
|
expect_true(max(cp) <= history$t[ceiling(0.4 * length(history$t))])
|
||||||
|
expect_error(prophet(history, changepoint.range = -0.1))
|
||||||
|
expect_error(prophet(history, changepoint.range = 2))
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("get_zero_changepoints", {
|
test_that("get_zero_changepoints", {
|
||||||
|
|
@ -147,10 +162,6 @@ test_that("get_zero_changepoints", {
|
||||||
cp <- m$changepoints.t
|
cp <- m$changepoints.t
|
||||||
expect_equal(length(cp), 1)
|
expect_equal(length(cp), 1)
|
||||||
expect_equal(cp[1], 0)
|
expect_equal(cp[1], 0)
|
||||||
|
|
||||||
mat <- prophet:::get_changepoint_matrix(m)
|
|
||||||
expect_equal(nrow(mat), floor(N / 2))
|
|
||||||
expect_equal(ncol(mat), 1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("override_n_changepoints", {
|
test_that("override_n_changepoints", {
|
||||||
|
|
@ -239,7 +250,7 @@ test_that("piecewise_logistic", {
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("holidays", {
|
test_that("holidays", {
|
||||||
holidays = data.frame(ds = c('2016-12-25'),
|
holidays <- data.frame(ds = c('2016-12-25'),
|
||||||
holiday = c('xmas'),
|
holiday = c('xmas'),
|
||||||
lower_window = c(-1),
|
lower_window = c(-1),
|
||||||
upper_window = c(0))
|
upper_window = c(0))
|
||||||
|
|
@ -250,12 +261,14 @@ test_that("holidays", {
|
||||||
out <- prophet:::make_holiday_features(m, df$ds)
|
out <- prophet:::make_holiday_features(m, df$ds)
|
||||||
feats <- out$holiday.features
|
feats <- out$holiday.features
|
||||||
priors <- out$prior.scales
|
priors <- out$prior.scales
|
||||||
|
names <- out$holiday.names
|
||||||
expect_equal(nrow(feats), nrow(df))
|
expect_equal(nrow(feats), nrow(df))
|
||||||
expect_equal(ncol(feats), 2)
|
expect_equal(ncol(feats), 2)
|
||||||
expect_equal(sum(colSums(feats) - c(1, 1)), 0)
|
expect_equal(sum(colSums(feats) - c(1, 1)), 0)
|
||||||
expect_true(all(priors == c(10., 10.)))
|
expect_true(all(priors == c(10., 10.)))
|
||||||
|
expect_equal(names, c('xmas'))
|
||||||
|
|
||||||
holidays = data.frame(ds = c('2016-12-25'),
|
holidays <- data.frame(ds = c('2016-12-25'),
|
||||||
holiday = c('xmas'),
|
holiday = c('xmas'),
|
||||||
lower_window = c(-1),
|
lower_window = c(-1),
|
||||||
upper_window = c(10))
|
upper_window = c(10))
|
||||||
|
|
@ -263,9 +276,11 @@ test_that("holidays", {
|
||||||
out <- prophet:::make_holiday_features(m, df$ds)
|
out <- prophet:::make_holiday_features(m, df$ds)
|
||||||
feats <- out$holiday.features
|
feats <- out$holiday.features
|
||||||
priors <- out$prior.scales
|
priors <- out$prior.scales
|
||||||
|
names <- out$holiday.names
|
||||||
expect_equal(nrow(feats), nrow(df))
|
expect_equal(nrow(feats), nrow(df))
|
||||||
expect_equal(ncol(feats), 12)
|
expect_equal(ncol(feats), 12)
|
||||||
expect_true(all(priors == rep(10, 12)))
|
expect_true(all(priors == rep(10, 12)))
|
||||||
|
expect_equal(names, c('xmas'))
|
||||||
# Check prior specifications
|
# Check prior specifications
|
||||||
holidays <- data.frame(
|
holidays <- data.frame(
|
||||||
ds = prophet:::set_date(c('2016-12-25', '2017-12-25')),
|
ds = prophet:::set_date(c('2016-12-25', '2017-12-25')),
|
||||||
|
|
@ -277,7 +292,9 @@ test_that("holidays", {
|
||||||
m <- prophet(holidays = holidays, fit = FALSE)
|
m <- prophet(holidays = holidays, fit = FALSE)
|
||||||
out <- prophet:::make_holiday_features(m, df$ds)
|
out <- prophet:::make_holiday_features(m, df$ds)
|
||||||
priors <- out$prior.scales
|
priors <- out$prior.scales
|
||||||
|
names <- out$holiday.names
|
||||||
expect_true(all(priors == c(5., 5.)))
|
expect_true(all(priors == c(5., 5.)))
|
||||||
|
expect_equal(names, c('xmas'))
|
||||||
# 2 different priors
|
# 2 different priors
|
||||||
holidays2 <- data.frame(
|
holidays2 <- data.frame(
|
||||||
ds = prophet:::set_date(c('2012-06-06', '2013-06-06')),
|
ds = prophet:::set_date(c('2012-06-06', '2013-06-06')),
|
||||||
|
|
@ -290,7 +307,9 @@ test_that("holidays", {
|
||||||
m <- prophet(holidays = holidays2, fit = FALSE)
|
m <- prophet(holidays = holidays2, fit = FALSE)
|
||||||
out <- prophet:::make_holiday_features(m, df$ds)
|
out <- prophet:::make_holiday_features(m, df$ds)
|
||||||
priors <- out$prior.scales
|
priors <- out$prior.scales
|
||||||
|
names <- out$holiday.names
|
||||||
expect_true(all(priors == c(8, 8, 5, 5)))
|
expect_true(all(priors == c(8, 8, 5, 5)))
|
||||||
|
expect_true(all(sort(names) == c('seans-bday', 'xmas')))
|
||||||
holidays2 <- data.frame(
|
holidays2 <- data.frame(
|
||||||
ds = prophet:::set_date(c('2012-06-06', '2013-06-06')),
|
ds = prophet:::set_date(c('2012-06-06', '2013-06-06')),
|
||||||
holiday = c('seans-bday', 'seans-bday'),
|
holiday = c('seans-bday', 'seans-bday'),
|
||||||
|
|
@ -353,7 +372,8 @@ test_that("auto_weekly_seasonality", {
|
||||||
expect_equal(m$weekly.seasonality, 'auto')
|
expect_equal(m$weekly.seasonality, 'auto')
|
||||||
m <- fit.prophet(m, train.w)
|
m <- fit.prophet(m, train.w)
|
||||||
expect_true('weekly' %in% names(m$seasonalities))
|
expect_true('weekly' %in% names(m$seasonalities))
|
||||||
true <- list(period = 7, fourier.order = 3, prior.scale = 10)
|
true <- list(
|
||||||
|
period = 7, fourier.order = 3, prior.scale = 10, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$weekly[[name]], true[[name]])
|
expect_equal(m$seasonalities$weekly[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -372,7 +392,8 @@ test_that("auto_weekly_seasonality", {
|
||||||
m <- prophet(train.w)
|
m <- prophet(train.w)
|
||||||
expect_false('weekly' %in% names(m$seasonalities))
|
expect_false('weekly' %in% names(m$seasonalities))
|
||||||
m <- prophet(DATA, weekly.seasonality = 2, seasonality.prior.scale = 3)
|
m <- prophet(DATA, weekly.seasonality = 2, seasonality.prior.scale = 3)
|
||||||
true <- list(period = 7, fourier.order = 2, prior.scale = 3)
|
true <- list(
|
||||||
|
period = 7, fourier.order = 2, prior.scale = 3, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$weekly[[name]], true[[name]])
|
expect_equal(m$seasonalities$weekly[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -385,7 +406,8 @@ test_that("auto_yearly_seasonality", {
|
||||||
expect_equal(m$yearly.seasonality, 'auto')
|
expect_equal(m$yearly.seasonality, 'auto')
|
||||||
m <- fit.prophet(m, DATA)
|
m <- fit.prophet(m, DATA)
|
||||||
expect_true('yearly' %in% names(m$seasonalities))
|
expect_true('yearly' %in% names(m$seasonalities))
|
||||||
true <- list(period = 365.25, fourier.order = 10, prior.scale = 10)
|
true <- list(
|
||||||
|
period = 365.25, fourier.order = 10, prior.scale = 10, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$yearly[[name]], true[[name]])
|
expect_equal(m$seasonalities$yearly[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -397,7 +419,8 @@ test_that("auto_yearly_seasonality", {
|
||||||
m <- prophet(train.y, yearly.seasonality = TRUE)
|
m <- prophet(train.y, yearly.seasonality = TRUE)
|
||||||
expect_true('yearly' %in% names(m$seasonalities))
|
expect_true('yearly' %in% names(m$seasonalities))
|
||||||
m <- prophet(DATA, yearly.seasonality = 7, seasonality.prior.scale = 3)
|
m <- prophet(DATA, yearly.seasonality = 7, seasonality.prior.scale = 3)
|
||||||
true <- list(period = 365.25, fourier.order = 7, prior.scale = 3)
|
true <- list(
|
||||||
|
period = 365.25, fourier.order = 7, prior.scale = 3, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$yearly[[name]], true[[name]])
|
expect_equal(m$seasonalities$yearly[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -410,7 +433,8 @@ test_that("auto_daily_seasonality", {
|
||||||
expect_equal(m$daily.seasonality, 'auto')
|
expect_equal(m$daily.seasonality, 'auto')
|
||||||
m <- fit.prophet(m, DATA2)
|
m <- fit.prophet(m, DATA2)
|
||||||
expect_true('daily' %in% names(m$seasonalities))
|
expect_true('daily' %in% names(m$seasonalities))
|
||||||
true <- list(period = 1, fourier.order = 4, prior.scale = 10)
|
true <- list(
|
||||||
|
period = 1, fourier.order = 4, prior.scale = 10, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$daily[[name]], true[[name]])
|
expect_equal(m$seasonalities$daily[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +446,8 @@ test_that("auto_daily_seasonality", {
|
||||||
m <- prophet(train.y, daily.seasonality = TRUE)
|
m <- prophet(train.y, daily.seasonality = TRUE)
|
||||||
expect_true('daily' %in% names(m$seasonalities))
|
expect_true('daily' %in% names(m$seasonalities))
|
||||||
m <- prophet(DATA2, daily.seasonality = 7, seasonality.prior.scale = 3)
|
m <- prophet(DATA2, daily.seasonality = 7, seasonality.prior.scale = 3)
|
||||||
true <- list(period = 1, fourier.order = 7, prior.scale = 3)
|
true <- list(
|
||||||
|
period = 1, fourier.order = 7, prior.scale = 3, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$daily[[name]], true[[name]])
|
expect_equal(m$seasonalities$daily[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +471,8 @@ test_that("custom_seasonality", {
|
||||||
prior_scale = c(4))
|
prior_scale = c(4))
|
||||||
m <- prophet(holidays=holidays)
|
m <- prophet(holidays=holidays)
|
||||||
m <- add_seasonality(m, name='monthly', period=30, fourier.order=5)
|
m <- add_seasonality(m, name='monthly', period=30, fourier.order=5)
|
||||||
true <- list(period = 30, fourier.order = 5, prior.scale = 10)
|
true <- list(
|
||||||
|
period = 30, fourier.order = 5, prior.scale = 10, mode = 'additive')
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(m$seasonalities$monthly[[name]], true[[name]])
|
expect_equal(m$seasonalities$monthly[[name]], true[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -458,12 +484,25 @@ test_that("custom_seasonality", {
|
||||||
)
|
)
|
||||||
m <- add_seasonality(m, name='weekly', period=30, fourier.order=5)
|
m <- add_seasonality(m, name='weekly', period=30, fourier.order=5)
|
||||||
# Test priors
|
# Test priors
|
||||||
m <- prophet(holidays = holidays, yearly.seasonality = FALSE)
|
m <- prophet(
|
||||||
|
holidays = holidays, yearly.seasonality = FALSE,
|
||||||
|
seasonality.mode = 'multiplicative')
|
||||||
m <- add_seasonality(
|
m <- add_seasonality(
|
||||||
m, name='monthly', period=30, fourier.order=5, prior.scale = 2)
|
m, name='monthly', period=30, fourier.order=5, prior.scale = 2,
|
||||||
|
mode = 'additive')
|
||||||
m <- fit.prophet(m, DATA)
|
m <- fit.prophet(m, DATA)
|
||||||
prior.scales <- prophet:::make_all_seasonality_features(
|
expect_equal(m$seasonalities$monthly$mode, 'additive')
|
||||||
m, m$history)$prior.scales
|
expect_equal(m$seasonalities$weekly$mode, 'multiplicative')
|
||||||
|
out <- prophet:::make_all_seasonality_features(m, m$history)
|
||||||
|
prior.scales <- out$prior.scales
|
||||||
|
component.cols <- out$component.cols
|
||||||
|
expect_equal(sum(component.cols$monthly), 10)
|
||||||
|
expect_equal(sum(component.cols$special_day), 1)
|
||||||
|
expect_equal(sum(component.cols$weekly), 6)
|
||||||
|
expect_equal(sum(component.cols$additive_terms), 10)
|
||||||
|
expect_equal(sum(component.cols$multiplicative_terms), 7)
|
||||||
|
expect_equal(sum(component.cols$monthly[1:11]), 10)
|
||||||
|
expect_equal(sum(component.cols$weekly[11:17]), 6)
|
||||||
expect_true(all(prior.scales == c(rep(2, 10), rep(10, 6), 4)))
|
expect_true(all(prior.scales == c(rep(2, 10), rep(10, 6), 4)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -472,10 +511,13 @@ test_that("added_regressors", {
|
||||||
m <- prophet()
|
m <- prophet()
|
||||||
m <- add_regressor(m, 'binary_feature', prior.scale=0.2)
|
m <- add_regressor(m, 'binary_feature', prior.scale=0.2)
|
||||||
m <- add_regressor(m, 'numeric_feature', prior.scale=0.5)
|
m <- add_regressor(m, 'numeric_feature', prior.scale=0.5)
|
||||||
|
m <- add_regressor(
|
||||||
|
m, 'numeric_feature2', prior.scale=0.5, mode = 'multiplicative')
|
||||||
m <- add_regressor(m, 'binary_feature2', standardize=TRUE)
|
m <- add_regressor(m, 'binary_feature2', standardize=TRUE)
|
||||||
df <- DATA
|
df <- DATA
|
||||||
df$binary_feature <- c(rep(0, 255), rep(1, 255))
|
df$binary_feature <- c(rep(0, 255), rep(1, 255))
|
||||||
df$numeric_feature <- 0:509
|
df$numeric_feature <- 0:509
|
||||||
|
df$numeric_feature2 <- 0:509
|
||||||
# Require all regressors in df
|
# Require all regressors in df
|
||||||
expect_error(
|
expect_error(
|
||||||
fit.prophet(m, df)
|
fit.prophet(m, df)
|
||||||
|
|
@ -483,7 +525,9 @@ test_that("added_regressors", {
|
||||||
df$binary_feature2 <- c(rep(1, 100), rep(0, 410))
|
df$binary_feature2 <- c(rep(1, 100), rep(0, 410))
|
||||||
m <- fit.prophet(m, df)
|
m <- fit.prophet(m, df)
|
||||||
# Check that standardizations are correctly set
|
# Check that standardizations are correctly set
|
||||||
true <- list(prior.scale = 0.2, mu = 0, std = 1, standardize = 'auto')
|
true <- list(
|
||||||
|
prior.scale = 0.2, mu = 0, std = 1, standardize = 'auto', mode = 'additive'
|
||||||
|
)
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(true[[name]], m$extra_regressors$binary_feature[[name]])
|
expect_equal(true[[name]], m$extra_regressors$binary_feature[[name]])
|
||||||
}
|
}
|
||||||
|
|
@ -492,6 +536,7 @@ test_that("added_regressors", {
|
||||||
expect_equal(true[[name]], m$extra_regressors$numeric_feature[[name]],
|
expect_equal(true[[name]], m$extra_regressors$numeric_feature[[name]],
|
||||||
tolerance = 1e-5)
|
tolerance = 1e-5)
|
||||||
}
|
}
|
||||||
|
expect_equal(m$extra_regressors$numeric_feature2$mode, 'multiplicative')
|
||||||
true <- list(prior.scale = 10., mu = 0.1960784, std = 0.3974183)
|
true <- list(prior.scale = 10., mu = 0.1960784, std = 0.3974183)
|
||||||
for (name in names(true)) {
|
for (name in names(true)) {
|
||||||
expect_equal(true[[name]], m$extra_regressors$binary_feature2[[name]],
|
expect_equal(true[[name]], m$extra_regressors$binary_feature2[[name]],
|
||||||
|
|
@ -506,97 +551,92 @@ test_that("added_regressors", {
|
||||||
out <- prophet:::make_all_seasonality_features(m, df2)
|
out <- prophet:::make_all_seasonality_features(m, df2)
|
||||||
seasonal.features <- out$seasonal.features
|
seasonal.features <- out$seasonal.features
|
||||||
prior.scales <- out$prior.scales
|
prior.scales <- out$prior.scales
|
||||||
expect_true('binary_feature' %in% colnames(seasonal.features))
|
component.cols <- out$component.cols
|
||||||
expect_true('numeric_feature' %in% colnames(seasonal.features))
|
modes <- out$modes
|
||||||
expect_true('binary_feature2' %in% colnames(seasonal.features))
|
expect_equal(ncol(seasonal.features), 30)
|
||||||
expect_equal(ncol(seasonal.features), 29)
|
r_names <- c('binary_feature', 'numeric_feature', 'binary_feature2')
|
||||||
expect_true(all(sort(prior.scales[27:29]) == c(0.2, 0.5, 10.)))
|
true.priors <- c(0.2, 0.5, 10.)
|
||||||
|
for (i in seq_along(r_names)) {
|
||||||
|
name <- r_names[i]
|
||||||
|
expect_true(name %in% colnames(seasonal.features))
|
||||||
|
expect_equal(sum(component.cols[[name]]), 1)
|
||||||
|
expect_equal(sum(prior.scales * component.cols[[name]]), true.priors[i])
|
||||||
|
}
|
||||||
# Check that forecast components are reasonable
|
# Check that forecast components are reasonable
|
||||||
future <- data.frame(
|
future <- data.frame(
|
||||||
ds = c('2014-06-01'), binary_feature = c(0), numeric_feature = c(10))
|
ds = c('2014-06-01'),
|
||||||
|
binary_feature = c(0),
|
||||||
|
numeric_feature = c(10),
|
||||||
|
numeric_feature2 = c(10)
|
||||||
|
)
|
||||||
expect_error(predict(m, future))
|
expect_error(predict(m, future))
|
||||||
future$binary_feature2 <- 0.
|
future$binary_feature2 <- 0.
|
||||||
fcst <- predict(m, future)
|
fcst <- predict(m, future)
|
||||||
expect_equal(ncol(fcst), 31)
|
expect_equal(ncol(fcst), 37)
|
||||||
expect_equal(fcst$binary_feature[1], 0)
|
expect_equal(fcst$binary_feature[1], 0)
|
||||||
expect_equal(fcst$extra_regressors[1],
|
expect_equal(fcst$extra_regressors_additive[1],
|
||||||
fcst$numeric_feature[1] + fcst$binary_feature2[1])
|
fcst$numeric_feature[1] + fcst$binary_feature2[1])
|
||||||
expect_equal(fcst$seasonalities[1], fcst$yearly[1] + fcst$weekly[1])
|
expect_equal(fcst$extra_regressors_multiplicative[1],
|
||||||
expect_equal(fcst$seasonal[1],
|
fcst$numeric_feature2[1])
|
||||||
fcst$seasonalities[1] + fcst$extra_regressors[1])
|
expect_equal(fcst$additive_terms[1],
|
||||||
expect_equal(fcst$yhat[1], fcst$trend[1] + fcst$seasonal[1])
|
fcst$yearly[1] + fcst$weekly[1]
|
||||||
# Check fails if constant extra regressor
|
+ fcst$extra_regressors_additive[1])
|
||||||
df$constant_feature <- 5
|
expect_equal(fcst$multiplicative_terms[1],
|
||||||
|
fcst$extra_regressors_multiplicative[1])
|
||||||
|
expect_equal(
|
||||||
|
fcst$yhat[1],
|
||||||
|
fcst$trend[1] * (1 + fcst$multiplicative_terms[1]) + fcst$additive_terms[1]
|
||||||
|
)
|
||||||
|
# Check works with constant extra regressor of 0
|
||||||
|
df$constant_feature <- 0
|
||||||
m <- prophet()
|
m <- prophet()
|
||||||
m <- add_regressor(m, 'constant_feature')
|
m <- add_regressor(m, 'constant_feature', standardize = TRUE)
|
||||||
expect_error(fit.prophet(m, df))
|
m <- fit.prophet(m, df)
|
||||||
|
expect_equal(m$extra_regressors$constant_feature$std, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("copy", {
|
test_that("set_seasonality_mode", {
|
||||||
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
df <- DATA
|
m <- prophet()
|
||||||
df$cap <- 200.
|
expect_equal(m$seasonality.mode, 'additive')
|
||||||
df$binary_feature <- c(rep(0, 255), rep(1, 255))
|
m <- prophet(seasonality.mode = 'multiplicative')
|
||||||
inputs <- list(
|
expect_equal(m$seasonality.mode, 'multiplicative')
|
||||||
growth = c('linear', 'logistic'),
|
expect_error(prophet(seasonality.mode = 'batman'))
|
||||||
yearly.seasonality = c(TRUE, FALSE),
|
})
|
||||||
weekly.seasonality = c(TRUE, FALSE),
|
|
||||||
daily.seasonality = c(TRUE, FALSE),
|
test_that("seasonality_modes", {
|
||||||
holidays = c('null', 'insert_dataframe')
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
)
|
holidays <- data.frame(ds = c('2016-12-25'),
|
||||||
products <- expand.grid(inputs)
|
holiday = c('xmas'),
|
||||||
for (i in 1:length(products)) {
|
lower_window = c(-1),
|
||||||
if (products$holidays[i] == 'insert_dataframe') {
|
upper_window = c(0))
|
||||||
holidays <- data.frame(ds=c('2016-12-25'), holiday=c('x'))
|
m <- prophet(seasonality.mode = 'multiplicative', holidays = holidays)
|
||||||
} else {
|
m <- add_seasonality(
|
||||||
holidays <- NULL
|
m, name = 'monthly', period = 30, fourier.order = 3, mode = 'additive')
|
||||||
}
|
m <- add_regressor(m, name = 'binary_feature', mode = 'additive')
|
||||||
m1 <- prophet(
|
m <- add_regressor(m, name = 'numeric_feature')
|
||||||
growth = as.character(products$growth[i]),
|
# Construct seasonal features
|
||||||
changepoints = NULL,
|
df <- DATA
|
||||||
n.changepoints = 3,
|
df$binary_feature <- c(rep(0, 255), rep(1, 255))
|
||||||
yearly.seasonality = products$yearly.seasonality[i],
|
df$numeric_feature <- 0:509
|
||||||
weekly.seasonality = products$weekly.seasonality[i],
|
out <- prophet:::setup_dataframe(m, df, initialize_scales = TRUE)
|
||||||
daily.seasonality = products$daily.seasonality[i],
|
df <- out$df
|
||||||
holidays = holidays,
|
m <- out$m
|
||||||
seasonality.prior.scale = 1.1,
|
m$history <- df
|
||||||
holidays.prior.scale = 1.1,
|
m <- prophet:::set_auto_seasonalities(m)
|
||||||
changepoints.prior.scale = 0.1,
|
out <- prophet:::make_all_seasonality_features(m, df)
|
||||||
mcmc.samples = 100,
|
component.cols <- out$component.cols
|
||||||
interval.width = 0.9,
|
modes <- out$modes
|
||||||
uncertainty.samples = 200,
|
expect_equal(sum(component.cols$additive_terms), 7)
|
||||||
fit = FALSE
|
expect_equal(sum(component.cols$multiplicative_terms), 29)
|
||||||
)
|
expect_equal(
|
||||||
out <- prophet:::setup_dataframe(m1, df, initialize_scales = TRUE)
|
sort(modes$additive),
|
||||||
m1 <- out$m
|
c('additive_terms', 'binary_feature', 'extra_regressors_additive',
|
||||||
m1$history <- out$df
|
'monthly')
|
||||||
m1 <- prophet:::set_auto_seasonalities(m1)
|
)
|
||||||
m2 <- prophet:::prophet_copy(m1)
|
expect_equal(
|
||||||
# Values should be copied correctly
|
sort(modes$multiplicative),
|
||||||
args <- c('growth', 'changepoints', 'n.changepoints', 'holidays',
|
c('extra_regressors_multiplicative', 'holidays', 'multiplicative_terms',
|
||||||
'seasonality.prior.scale', 'holidays.prior.scale',
|
'numeric_feature', 'weekly', 'xmas', 'yearly')
|
||||||
'changepoints.prior.scale', 'mcmc.samples', 'interval.width',
|
)
|
||||||
'uncertainty.samples')
|
|
||||||
for (arg in args) {
|
|
||||||
expect_equal(m1[[arg]], m2[[arg]])
|
|
||||||
}
|
|
||||||
expect_equal(FALSE, m2$yearly.seasonality)
|
|
||||||
expect_equal(FALSE, m2$weekly.seasonality)
|
|
||||||
expect_equal(FALSE, m2$daily.seasonality)
|
|
||||||
expect_equal(m1$yearly.seasonality, 'yearly' %in% names(m2$seasonalities))
|
|
||||||
expect_equal(m1$weekly.seasonality, 'weekly' %in% names(m2$seasonalities))
|
|
||||||
expect_equal(m1$daily.seasonality, 'daily' %in% names(m2$seasonalities))
|
|
||||||
}
|
|
||||||
# Check for cutoff and custom seasonality and extra regressors
|
|
||||||
changepoints <- seq.Date(as.Date('2012-06-15'), as.Date('2012-09-15'), by='d')
|
|
||||||
cutoff <- as.Date('2012-07-25')
|
|
||||||
m1 <- prophet(changepoints = changepoints)
|
|
||||||
m1 <- add_seasonality(m1, 'custom', 10, 5)
|
|
||||||
m1 <- add_regressor(m1, 'binary_feature')
|
|
||||||
m1 <- fit.prophet(m1, df)
|
|
||||||
m2 <- prophet:::prophet_copy(m1, cutoff)
|
|
||||||
changepoints <- changepoints[changepoints <= cutoff]
|
|
||||||
expect_equal(prophet:::set_date(changepoints), m2$changepoints)
|
|
||||||
expect_true('custom' %in% names(m2$seasonalities))
|
|
||||||
expect_true('binary_feature' %in% names(m2$extra_regressors))
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
109
R/tests/testthat/test_stan_functions.R
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
library(prophet)
|
||||||
|
context("Prophet stan model tests")
|
||||||
|
|
||||||
|
fn <- tryCatch({
|
||||||
|
rstan::expose_stan_functions(
|
||||||
|
rstan::stanc(file="../../inst/stan/prophet.stan")
|
||||||
|
)
|
||||||
|
}, error = function(e) {
|
||||||
|
rstan::expose_stan_functions(
|
||||||
|
rstan::stanc(file=system.file("stan/prophet.stan", package="prophet"))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
DATA <- read.csv('data.csv')
|
||||||
|
N <- nrow(DATA)
|
||||||
|
train <- DATA[1:floor(N / 2), ]
|
||||||
|
future <- DATA[(ceiling(N/2) + 1):N, ]
|
||||||
|
|
||||||
|
DATA2 <- read.csv('data2.csv')
|
||||||
|
|
||||||
|
DATA$ds <- prophet:::set_date(DATA$ds)
|
||||||
|
DATA2$ds <- prophet:::set_date(DATA2$ds)
|
||||||
|
|
||||||
|
test_that("get_changepoint_matrix", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
history <- train
|
||||||
|
m <- prophet(history, fit = FALSE)
|
||||||
|
|
||||||
|
out <- prophet:::setup_dataframe(m, history, initialize_scales = TRUE)
|
||||||
|
history <- out$df
|
||||||
|
m <- out$m
|
||||||
|
m$history <- history
|
||||||
|
|
||||||
|
m <- prophet:::set_changepoints(m)
|
||||||
|
|
||||||
|
cp <- m$changepoints.t
|
||||||
|
|
||||||
|
mat <- get_changepoint_matrix(history$t, cp, nrow(history), length(cp))
|
||||||
|
expect_equal(nrow(mat), floor(N / 2))
|
||||||
|
expect_equal(ncol(mat), m$n.changepoints)
|
||||||
|
# Compare to the R implementation
|
||||||
|
A <- matrix(0, nrow(history), length(cp))
|
||||||
|
for (i in 1:length(cp)) {
|
||||||
|
A[history$t >= cp[i], i] <- 1
|
||||||
|
}
|
||||||
|
expect_true(all(A == mat))
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("get_zero_changepoints", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
history <- train
|
||||||
|
m <- prophet(history, n.changepoints = 0, fit = FALSE)
|
||||||
|
|
||||||
|
out <- prophet:::setup_dataframe(m, history, initialize_scales = TRUE)
|
||||||
|
m <- out$m
|
||||||
|
history <- out$df
|
||||||
|
m$history <- history
|
||||||
|
|
||||||
|
m <- prophet:::set_changepoints(m)
|
||||||
|
cp <- m$changepoints.t
|
||||||
|
|
||||||
|
mat <- get_changepoint_matrix(history$t, cp, nrow(history), length(cp))
|
||||||
|
expect_equal(nrow(mat), floor(N / 2))
|
||||||
|
expect_equal(ncol(mat), 1)
|
||||||
|
expect_true(all(mat == 1))
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("linear_trend", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
t <- seq(0, 10)
|
||||||
|
m <- 0
|
||||||
|
k <- 1.0
|
||||||
|
deltas <- c(0.5)
|
||||||
|
changepoint.ts <- c(5)
|
||||||
|
A <- get_changepoint_matrix(t, changepoint.ts, length(t), 1)
|
||||||
|
|
||||||
|
y <- linear_trend(k, m, deltas, t, A, changepoint.ts)
|
||||||
|
y.true <- c(0, 1, 2, 3, 4, 5, 6.5, 8, 9.5, 11, 12.5)
|
||||||
|
expect_equal(y, y.true)
|
||||||
|
|
||||||
|
t <- t[8:length(t)]
|
||||||
|
A <- get_changepoint_matrix(t, changepoint.ts, length(t), 1)
|
||||||
|
y.true <- y.true[8:length(y.true)]
|
||||||
|
y <- linear_trend(k, m, deltas, t, A, changepoint.ts)
|
||||||
|
expect_equal(y, y.true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("piecewise_logistic", {
|
||||||
|
skip_if_not(Sys.getenv('R_ARCH') != '/i386')
|
||||||
|
t <- seq(0, 10)
|
||||||
|
cap <- rep(10, 11)
|
||||||
|
m <- 0
|
||||||
|
k <- 1.0
|
||||||
|
deltas <- c(0.5)
|
||||||
|
changepoint.ts <- c(5)
|
||||||
|
A <- get_changepoint_matrix(t, changepoint.ts, length(t), 1)
|
||||||
|
|
||||||
|
y <- logistic_trend(k, m, deltas, t, cap, A, changepoint.ts, 1)
|
||||||
|
y.true <- c(5.000000, 7.310586, 8.807971, 9.525741, 9.820138, 9.933071,
|
||||||
|
9.984988, 9.996646, 9.999252, 9.999833, 9.999963)
|
||||||
|
expect_equal(y, y.true, tolerance = 1e-6)
|
||||||
|
|
||||||
|
t <- t[8:length(t)]
|
||||||
|
A <- get_changepoint_matrix(t, changepoint.ts, length(t), 1)
|
||||||
|
y.true <- y.true[8:length(y.true)]
|
||||||
|
cap <- cap[8:length(cap)]
|
||||||
|
y <- logistic_trend(k, m, deltas, t, cap, A, changepoint.ts, 1)
|
||||||
|
expect_equal(y, y.true, tolerance = 1e-6)
|
||||||
|
})
|
||||||
|
|
@ -16,6 +16,7 @@ library(prophet)
|
||||||
library(dplyr)
|
library(dplyr)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This document provides a very brief introduction to the Prophet API. For a detailed guide on using Prophet, please visit the main site at [https://facebook.github.io/prophet/](https://facebook.github.io/prophet/).
|
||||||
|
|
||||||
Prophet uses the normal model fitting API. We provide a `prophet` function that performs fitting and returns a model object. You can then call `predict` and `plot` on this model object.
|
Prophet uses the normal model fitting API. We provide a `prophet` function that performs fitting and returns a model object. You can then call `predict` and `plot` on this model object.
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ You can use the generic `plot` function to plot the forecast, but you must also
|
||||||
plot(m, forecast)
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||
Just as in Python, you can plot the components of the forecast. In R, you use the `prophet_plot_components` function instead of an instance method:
|
You can plot the components of the forecast using the `prophet_plot_components` function:
|
||||||
|
|
||||||
```{r}
|
```{r}
|
||||||
prophet_plot_components(m, forecast)
|
prophet_plot_components(m, forecast)
|
||||||
|
|
|
||||||
21
README.md
|
|
@ -1,6 +1,8 @@
|
||||||
# Prophet: Automatic Forecasting Procedure
|
# Prophet: Automatic Forecasting Procedure
|
||||||
|
|
||||||
Prophet is a procedure for forecasting time series data. It is based on an additive model where non-linear trends are fit with yearly and weekly seasonality, plus holidays. It works best with daily periodicity data with at least one year of historical data. Prophet is robust to missing data, shifts in the trend, and large outliers.
|
[](https://travis-ci.org/facebook/prophet)
|
||||||
|
|
||||||
|
Prophet is a procedure for forecasting time series data based on an additive model where non-linear trends are fit with yearly, weekly, and daily seasonality, plus holiday effects. It works best with time series that have strong seasonal effects and several seasons of historical data. Prophet is robust to missing data and shifts in the trend, and typically handles outliers well.
|
||||||
|
|
||||||
Prophet is [open source software](https://code.facebook.com/projects/) released by Facebook's [Core Data Science team](https://research.fb.com/category/data-science/). It is available for download on [CRAN](https://cran.r-project.org/package=prophet) and [PyPI](https://pypi.python.org/pypi/fbprophet/).
|
Prophet is [open source software](https://code.facebook.com/projects/) released by Facebook's [Core Data Science team](https://research.fb.com/category/data-science/). It is available for download on [CRAN](https://cran.r-project.org/package=prophet) and [PyPI](https://pypi.python.org/pypi/fbprophet/).
|
||||||
|
|
||||||
|
|
@ -14,7 +16,7 @@ Prophet is [open source software](https://code.facebook.com/projects/) released
|
||||||
- Prophet R package: https://cran.r-project.org/package=prophet
|
- Prophet R package: https://cran.r-project.org/package=prophet
|
||||||
- Prophet Python package: https://pypi.python.org/pypi/fbprophet/
|
- Prophet Python package: https://pypi.python.org/pypi/fbprophet/
|
||||||
- Release blogpost: https://research.fb.com/prophet-forecasting-at-scale/
|
- Release blogpost: https://research.fb.com/prophet-forecasting-at-scale/
|
||||||
- Prophet paper, "Forecasting at Scale": https://peerj.com/preprints/3190.pdf
|
- Prophet paper: Sean J. Taylor, Benjamin Letham (2018) Forecasting at scale. The American Statistician 72(1):37-45 (https://peerj.com/preprints/3190.pdf).
|
||||||
|
|
||||||
## Installation in R
|
## Installation in R
|
||||||
|
|
||||||
|
|
@ -42,17 +44,19 @@ Prophet is on PyPI, so you can use pip to install it:
|
||||||
$ pip install fbprophet
|
$ pip install fbprophet
|
||||||
```
|
```
|
||||||
|
|
||||||
The major dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html).
|
The major dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html). Install pystan with pip before using pip to install fbprophet.
|
||||||
|
|
||||||
After installation, you can [get started!](https://facebook.github.io/prophet/docs/quick_start.html#python-api)
|
After installation, you can [get started!](https://facebook.github.io/prophet/docs/quick_start.html#python-api)
|
||||||
|
|
||||||
|
If you upgrade the version of PyStan installed on your system, you may need to reinstall fbprophet ([see here](https://github.com/facebook/prophet/issues/324)).
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
On Windows, PyStan requires a compiler so you'll need to [follow the instructions](http://pystan.readthedocs.io/en/latest/windows.html). The key step is installing a recent [C++ compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools).
|
On Windows, PyStan requires a compiler so you'll need to [follow the instructions](http://pystan.readthedocs.io/en/latest/windows.html). The key step is installing a recent [C++ compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools).
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
Make sure compilers (gcc, g++, build-essential) and Python development tools (python-dev, python3-dev) are installed. If you are using a VM, be aware that you will need at least 4GB of memory to install fbprophet, and at least 2GB of memory to use fbprophet.
|
Make sure compilers (gcc, g++, build-essential) and Python development tools (python-dev, python3-dev) are installed. In Red Hat systems, install the packages gcc64 and gcc64-c++. If you are using a VM, be aware that you will need at least 4GB of memory to install fbprophet, and at least 2GB of memory to use fbprophet.
|
||||||
|
|
||||||
### Anaconda
|
### Anaconda
|
||||||
|
|
||||||
|
|
@ -60,6 +64,15 @@ Use `conda install gcc` to set up gcc. The easiest way to install Prophet is thr
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### Version 0.3 (2018.06.01)
|
||||||
|
|
||||||
|
- Multiplicative seasonality
|
||||||
|
- Cross validation error metrics and visualizations
|
||||||
|
- Parameter to set range of potential changepoints
|
||||||
|
- Unified Stan model for both trend types
|
||||||
|
- Improved future trend uncertainty for sub-daily data
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
### Version 0.2.1 (2017.11.08)
|
### Version 0.2.1 (2017.11.08)
|
||||||
|
|
||||||
- Bugfixes
|
- Bugfixes
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
- id: quick_start
|
- id: quick_start
|
||||||
- id: saturating_forecasts
|
- id: saturating_forecasts
|
||||||
- id: trend_changepoints
|
- id: trend_changepoints
|
||||||
- id: seasonality_and_holiday_effects
|
- id: seasonality,_holiday_effects,_and_regressors
|
||||||
|
- id: multiplicative_seasonality
|
||||||
- id: uncertainty_intervals
|
- id: uncertainty_intervals
|
||||||
- id: outliers
|
- id: outliers
|
||||||
- id: non-daily_data
|
- id: non-daily_data
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
---
|
---
|
||||||
layout: docs
|
layout: docs
|
||||||
docid: "contributing"
|
docid: "contributing"
|
||||||
title: "How to Contribute"
|
title: "Getting Help and Contributing"
|
||||||
permalink: /docs/contributing.html
|
permalink: /docs/contributing.html
|
||||||
---
|
---
|
||||||
|
|
||||||
Prophet has an non-fixed release cycle but we will be making bugfixes in response to user feedback and adding features. Its current state is Beta (v0.2), we expect no obvious bugs. Please let us know if you encounter a bug by [filing an issue](https://github.com/facebook/prophet/issues).
|
Prophet has a non-fixed release cycle but we will be making bugfixes in response to user feedback and adding features. Its current state is Beta (v0.3), we expect no obvious bugs. Please let us know if you encounter a bug by [filing an issue](https://github.com/facebook/prophet/issues). Github issues is also the right place to ask questions about using Prophet.
|
||||||
|
|
||||||
We appreciate all contributions. If you are planning to contribute back bug-fixes, please do so without any further discussion.
|
We appreciate all contributions. If you are planning to contribute back bug-fixes, please do so without any further discussion.
|
||||||
|
|
||||||
If you plan to contribute new features or extensions to the core, please first open an issue and discuss the feature with us. Sending a pull request is fine, too, but if it is a large change we suggest you run it by us first.
|
If you plan to contribute new features or extensions to the core, please first open an issue and discuss the feature with us. Sending a pull request is fine too, but it will likely be merged more quickly if any design decisions are settled on beforehand in an issue.
|
||||||
|
|
||||||
We require that any API changes or feature additions are made available for both Python and R in parallel.
|
The R and Python versions are kept feature identical, but new features can be implemented for each method in separate commits.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,23 +14,38 @@ Prophet includes functionality for time series cross validation to measure forec
|
||||||
|
|
||||||
This cross validation procedure can be done automatically for a range of historical cutoffs using the `cross_validation` function. We specify the forecast horizon (`horizon`), and then optionally the size of the initial training period (`initial`) and the spacing between cutoff dates (`period`). By default, the initial training period is set to three times the horizon, and cutoffs are made every half a horizon.
|
This cross validation procedure can be done automatically for a range of historical cutoffs using the `cross_validation` function. We specify the forecast horizon (`horizon`), and then optionally the size of the initial training period (`initial`) and the spacing between cutoff dates (`period`). By default, the initial training period is set to three times the horizon, and cutoffs are made every half a horizon.
|
||||||
|
|
||||||
The output of `cross_validation` is a dataframe with the true values `y` and the out-of-sample forecast values `yhat`, at each simulated forecast date and for each cutoff date. This dataframe can then be used to compute error measures of `yhat` vs. `y`.
|
The output of `cross_validation` is a dataframe with the true values `y` and the out-of-sample forecast values `yhat`, at each simulated forecast date and for each cutoff date. In particular, a forecast is made for every observed point between `cutoff` and `cutoff + horizon`. This dataframe can then be used to compute error measures of `yhat` vs. `y`.
|
||||||
|
|
||||||
|
Here we do cross-validation to assess prediction performance on a horizon of 365 days, starting with 730 days of training data in the first cutoff and then making predictions every 180 days. On this 8 year time series, this corresponds to 11 total forecasts.
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df.cv <- cross_validation(m, horizon = 730, units = 'days')
|
df.cv <- cross_validation(m, initial = 730, period = 180, horizon = 365, units = 'days')
|
||||||
head(df.cv)
|
head(df.cv)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
from fbprophet.diagnostics import cross_validation
|
from fbprophet.diagnostics import cross_validation
|
||||||
df_cv = cross_validation(m, horizon = '730 days')
|
df_cv = cross_validation(m, initial='730 days', period='180 days', horizon = '365 days')
|
||||||
df_cv.head()
|
df_cv.head()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -46,51 +61,164 @@ df_cv.head()
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>0</th>
|
<th>0</th>
|
||||||
<td>2014-01-21</td>
|
<td>2010-02-16</td>
|
||||||
<td>9.439510</td>
|
<td>8.957184</td>
|
||||||
<td>8.799215</td>
|
<td>8.438130</td>
|
||||||
<td>10.080240</td>
|
<td>9.431683</td>
|
||||||
<td>10.542574</td>
|
<td>8.242493</td>
|
||||||
<td>2014-01-20</td>
|
<td>2010-02-15</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>1</th>
|
<th>1</th>
|
||||||
<td>2014-01-22</td>
|
<td>2010-02-17</td>
|
||||||
<td>9.267086</td>
|
<td>8.723619</td>
|
||||||
<td>8.645900</td>
|
<td>8.228941</td>
|
||||||
<td>9.882225</td>
|
<td>9.225985</td>
|
||||||
<td>10.004283</td>
|
<td>8.008033</td>
|
||||||
<td>2014-01-20</td>
|
<td>2010-02-15</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2</th>
|
<th>2</th>
|
||||||
<td>2014-01-23</td>
|
<td>2010-02-18</td>
|
||||||
<td>9.263447</td>
|
<td>8.607378</td>
|
||||||
<td>8.628803</td>
|
<td>8.086717</td>
|
||||||
<td>9.852847</td>
|
<td>9.125563</td>
|
||||||
<td>9.732818</td>
|
<td>8.045268</td>
|
||||||
<td>2014-01-20</td>
|
<td>2010-02-15</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>3</th>
|
<th>3</th>
|
||||||
<td>2014-01-24</td>
|
<td>2010-02-19</td>
|
||||||
<td>9.277452</td>
|
<td>8.529250</td>
|
||||||
<td>8.693226</td>
|
<td>8.053584</td>
|
||||||
<td>9.897891</td>
|
<td>9.056437</td>
|
||||||
<td>9.866460</td>
|
<td>7.928766</td>
|
||||||
<td>2014-01-20</td>
|
<td>2010-02-15</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>4</th>
|
<th>4</th>
|
||||||
<td>2014-01-25</td>
|
<td>2010-02-20</td>
|
||||||
<td>9.087565</td>
|
<td>8.271228</td>
|
||||||
<td>8.447306</td>
|
<td>7.748368</td>
|
||||||
<td>9.728898</td>
|
<td>8.756539</td>
|
||||||
<td>9.370927</td>
|
<td>7.745003</td>
|
||||||
<td>2014-01-20</td>
|
<td>2010-02-15</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The `performance_metrics` utility can be used to compute some useful statistics of the prediction performance (`yhat`, `yhat_lower`, and `yhat_upper` compared to `y`), as a function of the distance from the cutoff (how far into the future the prediction was). The statistics computed are mean squared error (MSE), root mean squared error (RMSE), mean absolute error (MAE), mean absolute percent error (MAPE), and coverage of the `yhat_lower` and `yhat_upper` estimates. These are computed on a rolling window of the predictions in `df_cv` after sorting by horizon (`ds` minus `cutoff`). By default 10% of the predictions will be included in each window, but this can be changed with the `rolling_window` argument.
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
df.p <- performance_metrics(df.cv)
|
||||||
|
head(df.p)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
from fbprophet.diagnostics import performance_metrics
|
||||||
|
df_p = performance_metrics(df_cv)
|
||||||
|
df_p.head()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table border="1" class="dataframe">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align: right;">
|
||||||
|
<th></th>
|
||||||
|
<th>horizon</th>
|
||||||
|
<th>mse</th>
|
||||||
|
<th>rmse</th>
|
||||||
|
<th>mae</th>
|
||||||
|
<th>mape</th>
|
||||||
|
<th>coverage</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>3297</th>
|
||||||
|
<td>37 days</td>
|
||||||
|
<td>0.481970</td>
|
||||||
|
<td>0.694241</td>
|
||||||
|
<td>0.502930</td>
|
||||||
|
<td>0.058371</td>
|
||||||
|
<td>0.673367</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>35</th>
|
||||||
|
<td>37 days</td>
|
||||||
|
<td>0.480991</td>
|
||||||
|
<td>0.693535</td>
|
||||||
|
<td>0.502007</td>
|
||||||
|
<td>0.058262</td>
|
||||||
|
<td>0.675879</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2207</th>
|
||||||
|
<td>37 days</td>
|
||||||
|
<td>0.480936</td>
|
||||||
|
<td>0.693496</td>
|
||||||
|
<td>0.501928</td>
|
||||||
|
<td>0.058257</td>
|
||||||
|
<td>0.675879</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2934</th>
|
||||||
|
<td>37 days</td>
|
||||||
|
<td>0.481455</td>
|
||||||
|
<td>0.693870</td>
|
||||||
|
<td>0.502999</td>
|
||||||
|
<td>0.058393</td>
|
||||||
|
<td>0.675879</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>393</th>
|
||||||
|
<td>37 days</td>
|
||||||
|
<td>0.483990</td>
|
||||||
|
<td>0.695694</td>
|
||||||
|
<td>0.503418</td>
|
||||||
|
<td>0.058494</td>
|
||||||
|
<td>0.675879</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Cross validation performance metrics can be visualized with `plot_cross_validation_metric`, here shown for MAPE. Dots show the absolute percent error for each prediction in `df_cv`. The blue line shows the MAPE, where the mean is taken over a rolling window of the dots. We see for this forecast that errors around 5% are typical for predictions one month into the future, and that errors increase up to around 11% for predictions that are a year out.
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
plot_cross_validation_metric(df.cv, metric = 'mape')
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
from fbprophet.plot import plot_cross_validation_metric
|
||||||
|
fig = plot_cross_validation_metric(df_cv, metric='mape')
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The size of the rolling window in the figure can be changed with the optional argument `rolling_window`, which specifies the proportion of forecasts to use in each rolling window. The default is 0.1, corresponding to 10% of rows from `df_cv` included in each window; increasing this will lead to a smoother average curve in the figure.
|
||||||
|
|
||||||
|
The `initial` period should be long enough to capture all of the components of the model, in particular seasonalities and extra regressors: at least a year for yearly seasonality, at least a week for weekly seasonality, etc.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ Prophet is on PyPI, so you can use pip to install it:
|
||||||
$ pip install fbprophet
|
$ pip install fbprophet
|
||||||
```
|
```
|
||||||
|
|
||||||
The major dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html).
|
The major dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html). Install pystan with pip before using pip to install fbprophet.
|
||||||
|
|
||||||
After installation, you can [get started!](quick_start.html#python-api)
|
After installation, you can [get started!](quick_start.html#python-api)
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ On Windows, PyStan requires a compiler so you'll need to [follow the instruction
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
Make sure compilers (gcc, g++) and Python development tools (python-dev) are installed. If you are using a VM, be aware that you will need at least 4GB of memory to install fbprophet, and at least 2GB of memory to use fbprophet.
|
Make sure compilers (gcc, g++, build-essential) and Python development tools (python-dev, python3-dev) are installed. In Red Hat systems, install the packages gcc64 and gcc64-c++. If you are using a VM, be aware that you will need at least 4GB of memory to install fbprophet, and at least 2GB of memory to use fbprophet.
|
||||||
|
|
||||||
### Anaconda
|
### Anaconda
|
||||||
|
|
||||||
|
|
|
||||||
81
docs/_docs/multiplicative_seasonality.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
layout: docs
|
||||||
|
docid: "multiplicative_seasonality"
|
||||||
|
title: "Multiplicative Seasonality"
|
||||||
|
permalink: /docs/multiplicative_seasonality.html
|
||||||
|
---
|
||||||
|
By default Prophet fits additive seasonalities, meaning the effect of the seasonality is added to the trend to get the forecast. This time series of the number of air passengers is an example of when additive seasonality does not work:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
df <- read.csv('../examples/example_air_passengers.csv')
|
||||||
|
m <- prophet(df)
|
||||||
|
future <- make_future_dataframe(m, 50, freq = 'm')
|
||||||
|
forecast <- predict(m, future)
|
||||||
|
plot(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
df = pd.read_csv('../examples/example_air_passengers.csv')
|
||||||
|
m = Prophet()
|
||||||
|
m.fit(df)
|
||||||
|
future = m.make_future_dataframe(50, freq='MS')
|
||||||
|
forecast = m.predict(future)
|
||||||
|
fig = m.plot(forecast)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
This time series has a clear yearly cycle, but the seasonality in the forecast is too large at the start of the time series and too small at the end. In this time series, the seasonality is not a constant additive factor as assumed by Prophet, rather it grows with the trend. This is multiplicative seasonality.
|
||||||
|
|
||||||
|
Prophet can model multiplicative seasonality by setting `seasonality_mode='multiplicative'` in the input arguments:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(df, seasonality.mode = 'multiplicative')
|
||||||
|
forecast <- predict(m, future)
|
||||||
|
plot(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet(seasonality_mode='multiplicative')
|
||||||
|
m.fit(df)
|
||||||
|
forecast = m.predict(future)
|
||||||
|
fig = m.plot(forecast)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The components figure will now show the seasonality as a percent of the trend:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
prophet_plot_components(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
fig = m.plot_components(forecast)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
With `seasonality_mode='multiplicative'`, holiday effects will also be modeled as multiplicative. Any added seasonalities or extra regressors will by default use whatever `seasonality_mode` is set to, but can be overriden by specifying `mode='additive'` or `mode='multiplicative'` as an argument when adding the seasonality or regressor.
|
||||||
|
|
||||||
|
For example, this block sets the built-in seasonalities to multiplicative, but includes an additive quarterly seasonality and an additive regressor:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(seasonality.mode = 'multiplicative')
|
||||||
|
m <- add_seasonality(m, 'quarterly', period = 91.25, fourier.order = 8, mode = 'additive')
|
||||||
|
m <- add_regressor(m, 'regressor', mode = 'additive')
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet(seasonality_mode='multiplicative')
|
||||||
|
m.add_seasonality('quarterly', period=91.25, fourier_order=8, mode='additive')
|
||||||
|
m.add_regressor('regressor', mode='additive')
|
||||||
|
```
|
||||||
|
Additive and multiplicative extra regressors will show up in separate panels on the components plot.
|
||||||
|
|
@ -6,7 +6,7 @@ permalink: /docs/non-daily_data.html
|
||||||
---
|
---
|
||||||
## Sub-daily data
|
## Sub-daily data
|
||||||
|
|
||||||
Prophet can make forecasts for time series with sub-daily observations by passing in a dataframe with timestamps in the `ds` column. When sub-daily data are used, daily seasonality will automatically be fit. Here we fit Prophet to data with 5-minute resolution (daily temperatures at Yosemite):
|
Prophet can make forecasts for time series with sub-daily observations by passing in a dataframe with timestamps in the `ds` column. The format of the timestamps should be YYYY-MM-DD HH:MM:SS - see the example csv [here](https://github.com/facebook/prophet/blob/master/examples/example_yosemite_temps.csv). When sub-daily data are used, daily seasonality will automatically be fit. Here we fit Prophet to data with 5-minute resolution (daily temperatures at Yosemite):
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
|
|
@ -14,7 +14,7 @@ df <- read.csv('../examples/example_yosemite_temps.csv')
|
||||||
m <- prophet(df, changepoint.prior.scale=0.01)
|
m <- prophet(df, changepoint.prior.scale=0.01)
|
||||||
future <- make_future_dataframe(m, periods = 300, freq = 60 * 60)
|
future <- make_future_dataframe(m, periods = 300, freq = 60 * 60)
|
||||||
fcst <- predict(m, future)
|
fcst <- predict(m, future)
|
||||||
plot(m, fcst);
|
plot(m, fcst)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
|
|
@ -22,7 +22,7 @@ df = pd.read_csv('../examples/example_yosemite_temps.csv')
|
||||||
m = Prophet(changepoint_prior_scale=0.01).fit(df)
|
m = Prophet(changepoint_prior_scale=0.01).fit(df)
|
||||||
future = m.make_future_dataframe(periods=300, freq='H')
|
future = m.make_future_dataframe(periods=300, freq='H')
|
||||||
fcst = m.predict(future)
|
fcst = m.predict(future)
|
||||||
m.plot(fcst);
|
fig = m.plot(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -36,12 +36,62 @@ prophet_plot_components(m, fcst)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m.plot_components(fcst);
|
fig = m.plot_components(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
## Data with regular gaps
|
||||||
|
|
||||||
|
Suppose the dataset above only had observations from 12a to 6a:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
df2 <- df %>%
|
||||||
|
mutate(ds = as.POSIXct(ds, tz="GMT")) %>%
|
||||||
|
filter(as.numeric(format(ds, "%H")) < 6)
|
||||||
|
m <- prophet(df2)
|
||||||
|
future <- make_future_dataframe(m, periods = 300, freq = 60 * 60)
|
||||||
|
fcst <- predict(m, future)
|
||||||
|
plot(m, fcst)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
df2 = df.copy()
|
||||||
|
df2['ds'] = pd.to_datetime(df2['ds'])
|
||||||
|
df2 = df2[df2['ds'].dt.hour < 6]
|
||||||
|
m = Prophet().fit(df2)
|
||||||
|
future = m.make_future_dataframe(periods=300, freq='H')
|
||||||
|
fcst = m.predict(future)
|
||||||
|
fig = m.plot(fcst)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The forecast seems quite poor, with much larger fluctuations in the future than were seen in the history. The issue here is that we have fit a daily cycle to a time series that only has data for part of the day (12a to 6a). The daily seasonality is thus unconstrained for the remainder of the day and is not estimated well. The solution is to only make predictions for the time windows for which there are historical data. Here, that means to limit the `future` dataframe to have times from 12a to 6a:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
future2 <- future %>%
|
||||||
|
filter(as.numeric(format(ds, "%H")) < 6)
|
||||||
|
fcst <- predict(m, future2)
|
||||||
|
plot(m, fcst)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
future2 = future.copy()
|
||||||
|
future2 = future2[future2['ds'].dt.hour < 6]
|
||||||
|
fcst = m.predict(future2)
|
||||||
|
fig = m.plot(fcst)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The same principle applies to other datasets with regular gaps in the data. For example, if the history contains only weekdays, then predictions should only be made for weekdays since the weekly seasonality will not be well estimated for the weekends.
|
||||||
|
|
||||||
## Monthly data
|
## Monthly data
|
||||||
|
|
||||||
You can use Prophet to fit monthly data. However, the underlying model is continuous-time, which means that you can get strange results if you fit the model to monthly data and then ask for daily forecasts. Here we forecast US retail sales volume for the next 10 years:
|
You can use Prophet to fit monthly data. However, the underlying model is continuous-time, which means that you can get strange results if you fit the model to monthly data and then ask for daily forecasts. Here we forecast US retail sales volume for the next 10 years:
|
||||||
|
|
@ -49,24 +99,42 @@ You can use Prophet to fit monthly data. However, the underlying model is contin
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df <- read.csv('../examples/example_retail_sales.csv')
|
df <- read.csv('../examples/example_retail_sales.csv')
|
||||||
m <- prophet(df)
|
m <- prophet(df, seasonality.mode = 'multiplicative')
|
||||||
future <- make_future_dataframe(m, periods = 3652)
|
future <- make_future_dataframe(m, periods = 3652)
|
||||||
fcst <- predict(m, future)
|
fcst <- predict(m, future)
|
||||||
plot(m, fcst);
|
plot(m, fcst)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df = pd.read_csv('../examples/example_retail_sales.csv')
|
df = pd.read_csv('../examples/example_retail_sales.csv')
|
||||||
m = Prophet().fit(df)
|
m = Prophet(seasonality_mode='multiplicative').fit(df)
|
||||||
future = m.make_future_dataframe(periods=3652)
|
future = m.make_future_dataframe(periods=3652)
|
||||||
fcst = m.predict(future)
|
fcst = m.predict(future)
|
||||||
m.plot(fcst);
|
fig = m.plot(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The forecast here seems very noisy. What's happening is that this particular data set only provides monthly data. When we fit the yearly seasonality, it only has data for the first of each month and the seasonality components for the remaining days are unidentifiable and overfit. When you are fitting Prophet to monthly data, only make monthly forecasts, which can be done by passing the frequency into make_future_dataframe:
|
This is the same issue from above where the dataset has regular gaps. When we fit the yearly seasonality, it only has data for the first of each month and the seasonality components for the remaining days are unidentifiable and overfit. This can be clearly seen by doing MCMC to see uncertainty in the seasonality:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(df, seasonality.mode = 'multiplicative', mcmc.samples = 300)
|
||||||
|
fcst <- predict(m, future)
|
||||||
|
prophet_plot_components(m, fcst)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet(seasonality_mode='multiplicative', mcmc_samples=300).fit(df)
|
||||||
|
fcst = m.predict(future)
|
||||||
|
fig = m.plot_components(fcst)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The seasonality has low uncertainty at the start of each month where there are data points, but has very high posterior variance in between. When fitting Prophet to monthly data, only make monthly forecasts, which can be done by passing the frequency into `make_future_dataframe`:
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
|
|
@ -78,8 +146,8 @@ plot(m, fcst)
|
||||||
# Python
|
# Python
|
||||||
future = m.make_future_dataframe(periods=120, freq='M')
|
future = m.make_future_dataframe(periods=120, freq='M')
|
||||||
fcst = m.predict(future)
|
fcst = m.predict(future)
|
||||||
m.plot(fcst);
|
fig = m.plot(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,20 @@ There are two main ways that outliers can affect Prophet forecasts. Here we make
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df <- read.csv('../examples/example_wp_R_outliers1.csv')
|
df <- read.csv('../examples/example_wp_log_R_outliers1.csv')
|
||||||
df$y <- log(df$y)
|
|
||||||
m <- prophet(df)
|
m <- prophet(df)
|
||||||
future <- make_future_dataframe(m, periods = 1096)
|
future <- make_future_dataframe(m, periods = 1096)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df = pd.read_csv('../examples/example_wp_R_outliers1.csv')
|
df = pd.read_csv('../examples/example_wp_log_R_outliers1.csv')
|
||||||
df['y'] = np.log(df['y'])
|
|
||||||
m = Prophet()
|
m = Prophet()
|
||||||
m.fit(df)
|
m.fit(df)
|
||||||
future = m.make_future_dataframe(periods=1096)
|
future = m.make_future_dataframe(periods=1096)
|
||||||
forecast = m.predict(future)
|
forecast = m.predict(future)
|
||||||
m.plot(forecast);
|
fig = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -40,13 +38,13 @@ outliers <- (as.Date(df$ds) > as.Date('2010-01-01')
|
||||||
df$y[outliers] = NA
|
df$y[outliers] = NA
|
||||||
m <- prophet(df)
|
m <- prophet(df)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df.loc[(df['ds'] > '2010-01-01') & (df['ds'] < '2011-01-01'), 'y'] = None
|
df.loc[(df['ds'] > '2010-01-01') & (df['ds'] < '2011-01-01'), 'y'] = None
|
||||||
model = Prophet().fit(df)
|
model = Prophet().fit(df)
|
||||||
model.plot(model.predict(future));
|
fig = model.plot(model.predict(future))
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -56,22 +54,20 @@ In the above example the outliers messed up the uncertainty estimation but did n
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df <- read.csv('../examples/example_wp_R_outliers2.csv')
|
df <- read.csv('../examples/example_wp_log_R_outliers2.csv')
|
||||||
df$y = log(df$y)
|
|
||||||
m <- prophet(df)
|
m <- prophet(df)
|
||||||
future <- make_future_dataframe(m, periods = 1096)
|
future <- make_future_dataframe(m, periods = 1096)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df = pd.read_csv('../examples/example_wp_R_outliers2.csv')
|
df = pd.read_csv('../examples/example_wp_log_R_outliers2.csv')
|
||||||
df['y'] = np.log(df['y'])
|
|
||||||
m = Prophet()
|
m = Prophet()
|
||||||
m.fit(df)
|
m.fit(df)
|
||||||
future = m.make_future_dataframe(periods=1096)
|
future = m.make_future_dataframe(periods=1096)
|
||||||
forecast = m.predict(future)
|
forecast = m.predict(future)
|
||||||
m.plot(forecast);
|
fig = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -86,13 +82,13 @@ outliers <- (as.Date(df$ds) > as.Date('2015-06-01')
|
||||||
df$y[outliers] = NA
|
df$y[outliers] = NA
|
||||||
m <- prophet(df)
|
m <- prophet(df)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df.loc[(df['ds'] > '2015-06-01') & (df['ds'] < '2015-06-30'), 'y'] = None
|
df.loc[(df['ds'] > '2015-06-01') & (df['ds'] < '2015-06-30'), 'y'] = None
|
||||||
m = Prophet().fit(df)
|
m = Prophet().fit(df)
|
||||||
m.plot(m.predict(future));
|
fig = m.plot(m.predict(future))
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -8,28 +8,39 @@ permalink: /docs/quick_start.html
|
||||||
|
|
||||||
Prophet follows the `sklearn` model API. We create an instance of the `Prophet` class and then call its `fit` and `predict` methods.
|
Prophet follows the `sklearn` model API. We create an instance of the `Prophet` class and then call its `fit` and `predict` methods.
|
||||||
|
|
||||||
The input to Prophet is always a dataframe with two columns: `ds` and `y`. The `ds` (datestamp) column must contain a date or datetime (either is fine). The `y` column must be numeric, and represents the measurement we wish to forecast.
|
The input to Prophet is always a dataframe with two columns: `ds` and `y`. The `ds` (datestamp) column should be of a format expected by Pandas, ideally YYYY-MM-DD for a date or YYYY-MM-DD HH:MM:SS for a timestamp. The `y` column must be numeric, and represents the measurement we wish to forecast.
|
||||||
|
|
||||||
As an example, let's look at a time series of daily page views for the Wikipedia page for [Peyton Manning](https://en.wikipedia.org/wiki/Peyton_Manning). We scraped this data using the [Wikipediatrend](https://cran.r-project.org/web/packages/wikipediatrend/vignettes/using-wikipediatrend.html) package in R. Peyton Manning provides a nice example because it illustrates some of Prophet's features, like multiple seasonality, changing growth rates, and the ability to model special days (such as Manning's playoff and superbowl appearances). The CSV is available [here](https://github.com/facebook/prophet/blob/master/examples/example_wp_peyton_manning.csv).
|
As an example, let's look at a time series of the log daily page views for the Wikipedia page for [Peyton Manning](https://en.wikipedia.org/wiki/Peyton_Manning). We scraped this data using the [Wikipediatrend](https://cran.r-project.org/web/packages/wikipediatrend/vignettes/using-wikipediatrend.html) package in R. Peyton Manning provides a nice example because it illustrates some of Prophet's features, like multiple seasonality, changing growth rates, and the ability to model special days (such as Manning's playoff and superbowl appearances). The CSV is available [here](https://github.com/facebook/prophet/blob/master/examples/example_wp_log_peyton_manning.csv).
|
||||||
|
|
||||||
First we'll import the data and log-transform the y variable.
|
First we'll import the data:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
|
||||||
from fbprophet import Prophet
|
from fbprophet import Prophet
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
df = pd.read_csv('../examples/example_wp_peyton_manning.csv')
|
df = pd.read_csv('../examples/example_wp_log_peyton_manning.csv')
|
||||||
df['y'] = np.log(df['y'])
|
|
||||||
df.head()
|
df.head()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -75,7 +86,7 @@ We fit the model by instantiating a new `Prophet` object. Any settings to the f
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m = Prophet()
|
m = Prophet()
|
||||||
m.fit(df);
|
m.fit(df)
|
||||||
```
|
```
|
||||||
Predictions are then made on a dataframe with a column `ds` containing the dates for which a prediction is to be made. You can get a suitable dataframe that extends into the future a specified number of days using the helper method `Prophet.make_future_dataframe`. By default it will also include the dates from the history, so we will see the model fit as well.
|
Predictions are then made on a dataframe with a column `ds` containing the dates for which a prediction is to be made. You can get a suitable dataframe that extends into the future a specified number of days using the helper method `Prophet.make_future_dataframe`. By default it will also include the dates from the history, so we will see the model fit as well.
|
||||||
|
|
||||||
|
|
@ -88,6 +99,19 @@ future.tail()
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -133,6 +157,19 @@ forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -147,37 +184,37 @@ forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()
|
||||||
<tr>
|
<tr>
|
||||||
<th>3265</th>
|
<th>3265</th>
|
||||||
<td>2017-01-15</td>
|
<td>2017-01-15</td>
|
||||||
<td>8.206753</td>
|
<td>8.199274</td>
|
||||||
<td>7.485107</td>
|
<td>7.489884</td>
|
||||||
<td>8.920149</td>
|
<td>8.969065</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>3266</th>
|
<th>3266</th>
|
||||||
<td>2017-01-16</td>
|
<td>2017-01-16</td>
|
||||||
<td>8.531766</td>
|
<td>8.524244</td>
|
||||||
<td>7.779331</td>
|
<td>7.790682</td>
|
||||||
<td>9.284859</td>
|
<td>9.266504</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>3267</th>
|
<th>3267</th>
|
||||||
<td>2017-01-17</td>
|
<td>2017-01-17</td>
|
||||||
<td>8.319156</td>
|
<td>8.311615</td>
|
||||||
<td>7.610545</td>
|
<td>7.553025</td>
|
||||||
<td>8.986889</td>
|
<td>9.049803</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>3268</th>
|
<th>3268</th>
|
||||||
<td>2017-01-18</td>
|
<td>2017-01-18</td>
|
||||||
<td>8.151772</td>
|
<td>8.144232</td>
|
||||||
<td>7.415802</td>
|
<td>7.428174</td>
|
||||||
<td>8.875191</td>
|
<td>8.864747</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>3269</th>
|
<th>3269</th>
|
||||||
<td>2017-01-19</td>
|
<td>2017-01-19</td>
|
||||||
<td>8.163690</td>
|
<td>8.156091</td>
|
||||||
<td>7.427153</td>
|
<td>7.395160</td>
|
||||||
<td>8.884826</td>
|
<td>8.883232</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -189,7 +226,7 @@ You can plot the forecast by calling the `Prophet.plot` method and passing in yo
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m.plot(forecast);
|
fig1 = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -199,13 +236,13 @@ If you want to see the forecast components, you can use the `Prophet.plot_compon
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m.plot_components(forecast);
|
fig2 = m.plot_components(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
More details about the options available for each method are available in the docstrings, for example, via `help(Prophet)` or `help(Prophet.fit)`.
|
More details about the options available for each method are available in the docstrings, for example, via `help(Prophet)` or `help(Prophet.fit)`. The [R reference manual](https://cran.r-project.org/web/packages/prophet/prophet.pdf) on CRAN provides a concise list of all of the available functions, each of which has a Python equivalent.
|
||||||
|
|
||||||
## R API
|
## R API
|
||||||
|
|
||||||
|
|
@ -214,14 +251,12 @@ In R, we use the normal model fitting API. We provide a `prophet` function that
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
library(prophet)
|
library(prophet)
|
||||||
library(dplyr)
|
|
||||||
```
|
```
|
||||||
First we read in the data and create the outcome variable. As in the Python API, this is a dataframe with columns `ds` and `y`, containing the date and numeric value respectively. As above, we use here the log number of views to Petyon Manning's Wikipedia page, available [here](https://github.com/facebook/prophet/blob/master/examples/example_wp_peyton_manning.csv).
|
First we read in the data and create the outcome variable. As in the Python API, this is a dataframe with columns `ds` and `y`, containing the date and numeric value respectively. The ds column should be YYYY-MM-DD for a date, or YYYY-MM-DD HH:MM:SS for a timestamp. As above, we use here the log number of views to Petyon Manning's Wikipedia page, available [here](https://github.com/facebook/prophet/blob/master/examples/example_wp_log_peyton_manning.csv).
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df <- read.csv('../examples/example_wp_peyton_manning.csv') %>%
|
df <- read.csv('../examples/example_wp_log_peyton_manning.csv')
|
||||||
mutate(y = log(y))
|
|
||||||
```
|
```
|
||||||
We call the `prophet` function to fit the model. The first argument is the historical dataframe. Additional arguments control how Prophet fits the data and are described in later pages of this documentation.
|
We call the `prophet` function to fit the model. The first argument is the historical dataframe. Additional arguments control how Prophet fits the data and are described in later pages of this documentation.
|
||||||
|
|
||||||
|
|
@ -256,12 +291,12 @@ tail(forecast[c('ds', 'yhat', 'yhat_lower', 'yhat_upper')])
|
||||||
```
|
```
|
||||||
|
|
||||||
ds yhat yhat_lower yhat_upper
|
ds yhat yhat_lower yhat_upper
|
||||||
3265 2017-01-14 7.825609 7.183818 8.488012
|
3265 2017-01-14 7.824163 7.127881 8.609668
|
||||||
3266 2017-01-15 8.207400 7.478778 8.951113
|
3266 2017-01-15 8.205942 7.452071 8.904387
|
||||||
3267 2017-01-16 8.532394 7.826360 9.240482
|
3267 2017-01-16 8.530942 7.742400 9.300974
|
||||||
3268 2017-01-17 8.319785 7.596815 9.042505
|
3268 2017-01-17 8.318327 7.606534 9.071184
|
||||||
3269 2017-01-18 8.152424 7.440858 8.874581
|
3269 2017-01-18 8.150948 7.440224 8.902922
|
||||||
3270 2017-01-19 8.164327 7.419148 8.882906
|
3270 2017-01-19 8.162839 7.385953 8.890669
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -285,4 +320,6 @@ prophet_plot_components(m, forecast)
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
An interactive plot of the forecast using Dygraphs can be made with the command `dyplot.prophet(m, forecast)`.
|
||||||
|
|
||||||
More details about the options available for each method are available in the docstrings, for example, via `?prophet` or `?fit.prophet`. This documentation is also available in the [reference manual](https://cran.r-project.org/web/packages/prophet/prophet.pdf) on CRAN.
|
More details about the options available for each method are available in the docstrings, for example, via `?prophet` or `?fit.prophet`. This documentation is also available in the [reference manual](https://cran.r-project.org/web/packages/prophet/prophet.pdf) on CRAN.
|
||||||
|
|
|
||||||
|
|
@ -10,40 +10,37 @@ By default, Prophet uses a linear model for its forecast. When forecasting growt
|
||||||
|
|
||||||
Prophet allows you to make forecasts using a [logistic growth](https://en.wikipedia.org/wiki/Logistic_function) trend model, with a specified carrying capacity. We illustrate this with the log number of page visits to the [R (programming language)](https://en.wikipedia.org/wiki/R_%28programming_language%29) page on Wikipedia:
|
Prophet allows you to make forecasts using a [logistic growth](https://en.wikipedia.org/wiki/Logistic_function) trend model, with a specified carrying capacity. We illustrate this with the log number of page visits to the [R (programming language)](https://en.wikipedia.org/wiki/R_%28programming_language%29) page on Wikipedia:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
df = pd.read_csv('../examples/example_wp_R.csv')
|
|
||||||
import numpy as np
|
|
||||||
df['y'] = np.log(df['y'])
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df <- read.csv('../examples/example_wp_R.csv')
|
df <- read.csv('../examples/example_wp_log_R.csv')
|
||||||
df$y <- log(df$y)
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
df = pd.read_csv('../examples/example_wp_log_R.csv')
|
||||||
```
|
```
|
||||||
We must specify the carrying capacity in a column `cap`. Here we will assume a particular value, but this would usually be set using data or expertise about the market size.
|
We must specify the carrying capacity in a column `cap`. Here we will assume a particular value, but this would usually be set using data or expertise about the market size.
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
df['cap'] = 8.5
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
df$cap <- 8.5
|
df$cap <- 8.5
|
||||||
```
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
df['cap'] = 8.5
|
||||||
|
```
|
||||||
The important things to note are that `cap` must be specified for every row in the dataframe, and that it does not have to be constant. If the market size is growing, then `cap` can be an increasing sequence.
|
The important things to note are that `cap` must be specified for every row in the dataframe, and that it does not have to be constant. If the market size is growing, then `cap` can be an increasing sequence.
|
||||||
|
|
||||||
We then fit the model as before, except pass in an additional argument to specify logistic growth:
|
We then fit the model as before, except pass in an additional argument to specify logistic growth:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(df, growth = 'logistic')
|
||||||
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m = Prophet(growth='logistic')
|
m = Prophet(growth='logistic')
|
||||||
m.fit(df)
|
m.fit(df)
|
||||||
```
|
```
|
||||||
```R
|
|
||||||
# R
|
|
||||||
m <- prophet(df, growth = 'logistic')
|
|
||||||
```
|
|
||||||
We make a dataframe for future predictions as before, except we must also specify the capacity in the future. Here we keep capacity constant at the same value as in the history, and forecast 3 years into the future:
|
We make a dataframe for future predictions as before, except we must also specify the capacity in the future. Here we keep capacity constant at the same value as in the history, and forecast 3 years into the future:
|
||||||
|
|
||||||
```R
|
```R
|
||||||
|
|
@ -51,14 +48,14 @@ We make a dataframe for future predictions as before, except we must also specif
|
||||||
future <- make_future_dataframe(m, periods = 1826)
|
future <- make_future_dataframe(m, periods = 1826)
|
||||||
future$cap <- 8.5
|
future$cap <- 8.5
|
||||||
fcst <- predict(m, future)
|
fcst <- predict(m, future)
|
||||||
plot(m, fcst);
|
plot(m, fcst)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
future = m.make_future_dataframe(periods=1826)
|
future = m.make_future_dataframe(periods=1826)
|
||||||
future['cap'] = 8.5
|
future['cap'] = 8.5
|
||||||
fcst = m.predict(future)
|
fcst = m.predict(future)
|
||||||
m.plot(fcst);
|
fig = m.plot(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -91,7 +88,7 @@ future['floor'] = 1.5
|
||||||
m = Prophet(growth='logistic')
|
m = Prophet(growth='logistic')
|
||||||
m.fit(df)
|
m.fit(df)
|
||||||
fcst = m.predict(future)
|
fcst = m.predict(future)
|
||||||
m.plot(fcst);
|
fig = m.plot(fcst)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,9 @@
|
||||||
---
|
---
|
||||||
layout: docs
|
layout: docs
|
||||||
docid: "seasonality_and_holiday_effects"
|
docid: "seasonality,_holiday_effects,_and_regressors"
|
||||||
title: "Seasonality And Holiday Effects"
|
title: "Seasonality, Holiday Effects, And Regressors"
|
||||||
permalink: /docs/seasonality_and_holiday_effects.html
|
permalink: /docs/seasonality,_holiday_effects,_and_regressors.html
|
||||||
---
|
---
|
||||||
### Specifying Seasonalities
|
|
||||||
|
|
||||||
Prophet will by default fit weekly and yearly seasonalities, if the time series is more than two cycles long. It will also fit daily seasonality for a sub-daily time series. You can add other seasonalities (monthly, quarterly, hourly) using the `add_seasonality` method (Python) or function (R).
|
|
||||||
|
|
||||||
The inputs to this function are a name, the period of the seasonality in days, and the number of Fourier terms for the seasonality. Increasing the number of Fourier terms allows the seasonality to fit faster changing cycles, but can also lead to overfitting: $N$ Fourier terms corresponds to $2N$ variables used for modeling the cycle. For reference, by default Prophet uses 3 terms for weekly seasonality and 10 for yearly seasonality. An optional input to `add_seasonality` is the prior scale for that seasonal component - this is discussed below.
|
|
||||||
|
|
||||||
As an example, here we fit the Peyton Manning data from the Quickstart, but replace the weekly seasonality with monthly seasonality. The monthly seasonality then will appear in the components plot:
|
|
||||||
|
|
||||||
```R
|
|
||||||
# R
|
|
||||||
m <- prophet(weekly.seasonality=FALSE)
|
|
||||||
m <- add_seasonality(m, name='monthly', period=30.5, fourier.order=5)
|
|
||||||
m <- fit.prophet(m, df)
|
|
||||||
forecast <- predict(m, future)
|
|
||||||
prophet_plot_components(m, forecast)
|
|
||||||
```
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
m = Prophet(weekly_seasonality=False)
|
|
||||||
m.add_seasonality(name='monthly', period=30.5, fourier_order=5)
|
|
||||||
forecast = m.fit(df).predict(future)
|
|
||||||
m.plot_components(forecast);
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Modeling Holidays and Special Events
|
### Modeling Holidays and Special Events
|
||||||
If you have holidays or other recurring events that you'd like to model, you must create a dataframe for them. It has two columns (`holiday` and `ds`) and a row for each occurrence of the holiday. It must include all occurrences of the holiday, both in the past (back as far as the historical data go) and in the future (out as far as the forecast is being made). If they won't repeat in the future, Prophet will model them and then not include them in the forecast.
|
If you have holidays or other recurring events that you'd like to model, you must create a dataframe for them. It has two columns (`holiday` and `ds`) and a row for each occurrence of the holiday. It must include all occurrences of the holiday, both in the past (back as far as the historical data go) and in the future (out as far as the forecast is being made). If they won't repeat in the future, Prophet will model them and then not include them in the forecast.
|
||||||
|
|
||||||
|
|
@ -38,26 +11,6 @@ You can also include columns `lower_window` and `upper_window` which extend the
|
||||||
|
|
||||||
Here we create a dataframe that includes the dates of all of Peyton Manning's playoff appearances:
|
Here we create a dataframe that includes the dates of all of Peyton Manning's playoff appearances:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
playoffs = pd.DataFrame({
|
|
||||||
'holiday': 'playoff',
|
|
||||||
'ds': pd.to_datetime(['2008-01-13', '2009-01-03', '2010-01-16',
|
|
||||||
'2010-01-24', '2010-02-07', '2011-01-08',
|
|
||||||
'2013-01-12', '2014-01-12', '2014-01-19',
|
|
||||||
'2014-02-02', '2015-01-11', '2016-01-17',
|
|
||||||
'2016-01-24', '2016-02-07']),
|
|
||||||
'lower_window': 0,
|
|
||||||
'upper_window': 1,
|
|
||||||
})
|
|
||||||
superbowls = pd.DataFrame({
|
|
||||||
'holiday': 'superbowl',
|
|
||||||
'ds': pd.to_datetime(['2010-02-07', '2014-02-02', '2016-02-07']),
|
|
||||||
'lower_window': 0,
|
|
||||||
'upper_window': 1,
|
|
||||||
})
|
|
||||||
holidays = pd.concat((playoffs, superbowls))
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
library(dplyr)
|
library(dplyr)
|
||||||
|
|
@ -79,20 +32,40 @@ superbowls <- data_frame(
|
||||||
)
|
)
|
||||||
holidays <- bind_rows(playoffs, superbowls)
|
holidays <- bind_rows(playoffs, superbowls)
|
||||||
```
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
playoffs = pd.DataFrame({
|
||||||
|
'holiday': 'playoff',
|
||||||
|
'ds': pd.to_datetime(['2008-01-13', '2009-01-03', '2010-01-16',
|
||||||
|
'2010-01-24', '2010-02-07', '2011-01-08',
|
||||||
|
'2013-01-12', '2014-01-12', '2014-01-19',
|
||||||
|
'2014-02-02', '2015-01-11', '2016-01-17',
|
||||||
|
'2016-01-24', '2016-02-07']),
|
||||||
|
'lower_window': 0,
|
||||||
|
'upper_window': 1,
|
||||||
|
})
|
||||||
|
superbowls = pd.DataFrame({
|
||||||
|
'holiday': 'superbowl',
|
||||||
|
'ds': pd.to_datetime(['2010-02-07', '2014-02-02', '2016-02-07']),
|
||||||
|
'lower_window': 0,
|
||||||
|
'upper_window': 1,
|
||||||
|
})
|
||||||
|
holidays = pd.concat((playoffs, superbowls))
|
||||||
|
```
|
||||||
Above we have include the superbowl days as both playoff games and superbowl games. This means that the superbowl effect will be an additional additive bonus on top of the playoff effect.
|
Above we have include the superbowl days as both playoff games and superbowl games. This means that the superbowl effect will be an additional additive bonus on top of the playoff effect.
|
||||||
|
|
||||||
Once the table is created, holiday effects are included in the forecast by passing them in with the `holidays` argument. Here we do it with the Peyton Manning data from the Quickstart:
|
Once the table is created, holiday effects are included in the forecast by passing them in with the `holidays` argument. Here we do it with the Peyton Manning data from the Quickstart:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
m = Prophet(holidays=holidays)
|
|
||||||
forecast = m.fit(df).predict(future)
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, holidays = holidays)
|
m <- prophet(df, holidays = holidays)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
```
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet(holidays=holidays)
|
||||||
|
forecast = m.fit(df).predict(future)
|
||||||
|
```
|
||||||
The holiday effect can be seen in the `forecast` dataframe:
|
The holiday effect can be seen in the `forecast` dataframe:
|
||||||
|
|
||||||
```R
|
```R
|
||||||
|
|
@ -111,6 +84,19 @@ forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -124,62 +110,62 @@ forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][
|
||||||
<tr>
|
<tr>
|
||||||
<th>2190</th>
|
<th>2190</th>
|
||||||
<td>2014-02-02</td>
|
<td>2014-02-02</td>
|
||||||
<td>1.226679</td>
|
<td>1.229999</td>
|
||||||
<td>1.192500</td>
|
<td>1.176410</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2191</th>
|
<th>2191</th>
|
||||||
<td>2014-02-03</td>
|
<td>2014-02-03</td>
|
||||||
<td>1.911294</td>
|
<td>1.900543</td>
|
||||||
<td>1.373781</td>
|
<td>1.486962</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2532</th>
|
<th>2532</th>
|
||||||
<td>2015-01-11</td>
|
<td>2015-01-11</td>
|
||||||
<td>1.226679</td>
|
<td>1.229999</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2533</th>
|
<th>2533</th>
|
||||||
<td>2015-01-12</td>
|
<td>2015-01-12</td>
|
||||||
<td>1.911294</td>
|
<td>1.900543</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2901</th>
|
<th>2901</th>
|
||||||
<td>2016-01-17</td>
|
<td>2016-01-17</td>
|
||||||
<td>1.226679</td>
|
<td>1.229999</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2902</th>
|
<th>2902</th>
|
||||||
<td>2016-01-18</td>
|
<td>2016-01-18</td>
|
||||||
<td>1.911294</td>
|
<td>1.900543</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2908</th>
|
<th>2908</th>
|
||||||
<td>2016-01-24</td>
|
<td>2016-01-24</td>
|
||||||
<td>1.226679</td>
|
<td>1.229999</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2909</th>
|
<th>2909</th>
|
||||||
<td>2016-01-25</td>
|
<td>2016-01-25</td>
|
||||||
<td>1.911294</td>
|
<td>1.900543</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2922</th>
|
<th>2922</th>
|
||||||
<td>2016-02-07</td>
|
<td>2016-02-07</td>
|
||||||
<td>1.226679</td>
|
<td>1.229999</td>
|
||||||
<td>1.192500</td>
|
<td>1.176410</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2923</th>
|
<th>2923</th>
|
||||||
<td>2016-02-08</td>
|
<td>2016-02-08</td>
|
||||||
<td>1.911294</td>
|
<td>1.900543</td>
|
||||||
<td>1.373781</td>
|
<td>1.486962</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -189,19 +175,84 @@ forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][
|
||||||
|
|
||||||
The holiday effects will also show up in the components plot, where we see that there is a spike on the days around playoff appearances, with an especially large spike for the superbowl:
|
The holiday effects will also show up in the components plot, where we see that there is a spike on the days around playoff appearances, with an especially large spike for the superbowl:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
m.plot_components(forecast);
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
prophet_plot_components(m, forecast);
|
prophet_plot_components(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
fig = m.plot_components(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
Individual holidays can be plotted using the `plot_forecast_component` method (Python) or function (R). For example, `m.plot_forecast_component(forecast, 'superbowl')` in Python and `plot_forecast_component(forecast, 'superbowl')` in R to plot just the superbowl holiday component.
|
Individual holidays can be plotted using the `plot_forecast_component` function (imported from `fbprophet.plot` in Python) like `plot_forecast_component(forecast, 'superbowl')` to plot just the superbowl holiday component.
|
||||||
|
|
||||||
|
### Fourier Order for Seasonalities
|
||||||
|
|
||||||
|
Seasonalities are estimated using a partial Fourier sum. See [the paper](https://peerj.com/preprints/3190/) for complete details, and [this figure on Wikipedia](https://en.wikipedia.org/wiki/Fourier_series#/media/File:Fourier_Series.svg) for an illustration of how a partial Fourier sum can approximate an aribtrary periodic signal. The number of terms in the partial sum (the order) is a parameter that determines how quickly the seasonality can change. To illustrate this, consider the Peyton Manning data from the Quickstart. The default Fourier order for yearly seasonality is 10, which produces this fit:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(df)
|
||||||
|
prophet:::plot_yearly(m)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
from fbprophet.plot import plot_yearly
|
||||||
|
m = Prophet().fit(df)
|
||||||
|
a = plot_yearly(m)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The default values are often appropriate, but they can be increased when the seasonality needs to fit higher-frequency changes, and generally be less smooth. The Fourier order can be specified for each built-in seasonality when instantiating the model, here it is increased to 20:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(df, yearly.seasonality = 20)
|
||||||
|
prophet:::plot_yearly(m)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
from fbprophet.plot import plot_yearly
|
||||||
|
m = Prophet(yearly_seasonality=20).fit(df)
|
||||||
|
a = plot_yearly(m)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Increasing the number of Fourier terms allows the seasonality to fit faster changing cycles, but can also lead to overfitting: N Fourier terms corresponds to 2N variables used for modeling the cycle
|
||||||
|
|
||||||
|
### Specifying Custom Seasonalities
|
||||||
|
|
||||||
|
Prophet will by default fit weekly and yearly seasonalities, if the time series is more than two cycles long. It will also fit daily seasonality for a sub-daily time series. You can add other seasonalities (monthly, quarterly, hourly) using the `add_seasonality` method (Python) or function (R).
|
||||||
|
|
||||||
|
The inputs to this function are a name, the period of the seasonality in days, and the Fourier order for the seasonality. For reference, by default Prophet uses a Fourier order of 3 for weekly seasonality and 10 for yearly seasonality. An optional input to `add_seasonality` is the prior scale for that seasonal component - this is discussed below.
|
||||||
|
|
||||||
|
As an example, here we fit the Peyton Manning data from the Quickstart, but replace the weekly seasonality with monthly seasonality. The monthly seasonality then will appear in the components plot:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
m <- prophet(weekly.seasonality=FALSE)
|
||||||
|
m <- add_seasonality(m, name='monthly', period=30.5, fourier.order=5)
|
||||||
|
m <- fit.prophet(m, df)
|
||||||
|
forecast <- predict(m, future)
|
||||||
|
prophet_plot_components(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet(weekly_seasonality=False)
|
||||||
|
m.add_seasonality(name='monthly', period=30.5, fourier_order=5)
|
||||||
|
forecast = m.fit(df).predict(future)
|
||||||
|
fig = m.plot_components(forecast)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
### Prior scale for holidays and seasonality
|
### Prior scale for holidays and seasonality
|
||||||
If you find that the holidays are overfitting, you can adjust their prior scale to smooth them using the parameter `holidays_prior_scale`. By default this parameter is 10, which provides very little regularization. Reducing this parameter dampens holiday effects:
|
If you find that the holidays are overfitting, you can adjust their prior scale to smooth them using the parameter `holidays_prior_scale`. By default this parameter is 10, which provides very little regularization. Reducing this parameter dampens holiday effects:
|
||||||
|
|
@ -226,6 +277,19 @@ forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<table border="1" class="dataframe">
|
<table border="1" class="dataframe">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: right;">
|
<tr style="text-align: right;">
|
||||||
|
|
@ -239,62 +303,62 @@ forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][
|
||||||
<tr>
|
<tr>
|
||||||
<th>2190</th>
|
<th>2190</th>
|
||||||
<td>2014-02-02</td>
|
<td>2014-02-02</td>
|
||||||
<td>1.200631</td>
|
<td>1.205344</td>
|
||||||
<td>0.957093</td>
|
<td>0.963327</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2191</th>
|
<th>2191</th>
|
||||||
<td>2014-02-03</td>
|
<td>2014-02-03</td>
|
||||||
<td>1.841906</td>
|
<td>1.851992</td>
|
||||||
<td>0.979777</td>
|
<td>0.991010</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2532</th>
|
<th>2532</th>
|
||||||
<td>2015-01-11</td>
|
<td>2015-01-11</td>
|
||||||
<td>1.200631</td>
|
<td>1.205344</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2533</th>
|
<th>2533</th>
|
||||||
<td>2015-01-12</td>
|
<td>2015-01-12</td>
|
||||||
<td>1.841906</td>
|
<td>1.851992</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2901</th>
|
<th>2901</th>
|
||||||
<td>2016-01-17</td>
|
<td>2016-01-17</td>
|
||||||
<td>1.200631</td>
|
<td>1.205344</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2902</th>
|
<th>2902</th>
|
||||||
<td>2016-01-18</td>
|
<td>2016-01-18</td>
|
||||||
<td>1.841906</td>
|
<td>1.851992</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2908</th>
|
<th>2908</th>
|
||||||
<td>2016-01-24</td>
|
<td>2016-01-24</td>
|
||||||
<td>1.200631</td>
|
<td>1.205344</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2909</th>
|
<th>2909</th>
|
||||||
<td>2016-01-25</td>
|
<td>2016-01-25</td>
|
||||||
<td>1.841906</td>
|
<td>1.851992</td>
|
||||||
<td>0.000000</td>
|
<td>0.000000</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2922</th>
|
<th>2922</th>
|
||||||
<td>2016-02-07</td>
|
<td>2016-02-07</td>
|
||||||
<td>1.200631</td>
|
<td>1.205344</td>
|
||||||
<td>0.957093</td>
|
<td>0.963327</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>2923</th>
|
<th>2923</th>
|
||||||
<td>2016-02-08</td>
|
<td>2016-02-08</td>
|
||||||
<td>1.841906</td>
|
<td>1.851992</td>
|
||||||
<td>0.979777</td>
|
<td>0.991010</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -306,18 +370,19 @@ The magnitude of the holiday effect has been reduced compared to before, especia
|
||||||
|
|
||||||
Prior scales can be set separately for individual holidays by including a column `prior_scale` in the holidays dataframe. Prior scales for individual seasonalities can be passed as an argument to `add_seasonality`. For instance, the prior scale for just weekly seasonality can be set using:
|
Prior scales can be set separately for individual holidays by including a column `prior_scale` in the holidays dataframe. Prior scales for individual seasonalities can be passed as an argument to `add_seasonality`. For instance, the prior scale for just weekly seasonality can be set using:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
m = Prophet()
|
|
||||||
m.add_seasonality(
|
|
||||||
name='weekly', period=7, fourier_order=3, prior_scale=0.1);
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
m <- prophet()
|
m <- prophet()
|
||||||
m <- add_seasonality(
|
m <- add_seasonality(
|
||||||
m, name='weekly', period=7, fourier.order=3, prior.scale=0.1)
|
m, name='weekly', period=7, fourier.order=3, prior.scale=0.1)
|
||||||
```
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
m = Prophet()
|
||||||
|
m.add_seasonality(
|
||||||
|
name='weekly', period=7, fourier_order=3, prior_scale=0.1)
|
||||||
|
```
|
||||||
|
|
||||||
### Additional regressors
|
### Additional regressors
|
||||||
Additional regressors can be added to the linear part of the model using the `add_regressor` method or function. A column with the regressor value will need to be present in both the fitting and prediction dataframes. For example, we can add an additional effect on Sundays during the NFL season. On the components plot, this effect will show up in the 'extra_regressors' plot:
|
Additional regressors can be added to the linear part of the model using the `add_regressor` method or function. A column with the regressor value will need to be present in both the fitting and prediction dataframes. For example, we can add an additional effect on Sundays during the NFL season. On the components plot, this effect will show up in the 'extra_regressors' plot:
|
||||||
|
|
||||||
|
|
@ -356,12 +421,16 @@ m.fit(df)
|
||||||
future['nfl_sunday'] = future['ds'].apply(nfl_sunday)
|
future['nfl_sunday'] = future['ds'].apply(nfl_sunday)
|
||||||
|
|
||||||
forecast = m.predict(future)
|
forecast = m.predict(future)
|
||||||
m.plot_components(forecast);
|
fig = m.plot_components(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
NFL Sundays could also have been handled using the "holidays" interface described above, by creating a list of past and future NFL Sundays. The `add_regressor` function provides a more general interface for defining extra linear regressors, and in particular does not require that the regressor be a binary indicator. Another time series could be used as a regressor, although its future values would have to be known. The regressor cannot be constant in the training data; fitting will exit with an error if it is.
|
NFL Sundays could also have been handled using the "holidays" interface described above, by creating a list of past and future NFL Sundays. The `add_regressor` function provides a more general interface for defining extra linear regressors, and in particular does not require that the regressor be a binary indicator. Another time series could be used as a regressor, although its future values would have to be known.
|
||||||
|
|
||||||
The `add_regressor` function has optional arguments for specifying the prior scale (holiday prior scale is used by default) and whether or not the regressor is standardized - see the docstring with `help(Prophet.add_regressor)` in Python and `?add_regressor` in R.
|
The `add_regressor` function has optional arguments for specifying the prior scale (holiday prior scale is used by default) and whether or not the regressor is standardized - see the docstring with `help(Prophet.add_regressor)` in Python and `?add_regressor` in R. Note that regressors must be added prior to model fitting.
|
||||||
|
|
||||||
|
The extra regressor must be known for both the history and for future dates. It thus must either be something that has known future values (such as `nfl_sunday`), or something that has separately been forecasted elsewhere. Prophet will also raise an error if the regressor is constant throughout the history, since there is nothing to fit from it.
|
||||||
|
|
||||||
|
Extra regressors are put in the linear component of the model, so the underlying model is that the time series depends on the extra regressor as either an additive or multiplicative factor (see the next section for multiplicativity).
|
||||||
|
|
@ -19,7 +19,23 @@ Even though we have a lot of places where the rate can possibly change, because
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The number of potential changepoints can be set using the argument `n_changepoints`, but this is better tuned by adjusting the regularization.
|
The number of potential changepoints can be set using the argument `n_changepoints`, but this is better tuned by adjusting the regularization. The locations of the signification changepoints can be visualized with:
|
||||||
|
|
||||||
|
```R
|
||||||
|
# R
|
||||||
|
plot(m, forecast) + add_changepoints_to_plot(m)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
from fbprophet.plot import add_changepoints_to_plot
|
||||||
|
fig = m.plot(forecast)
|
||||||
|
a = add_changepoints_to_plot(fig.gca(), m, forecast)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
By default changepoints are only inferred for the first 80% of the time series in order to have plenty of runway for projecting the trend forward and to avoid overfitting fluctuations at the end of the time series. This default works in many situations but not all, and can be change using the `changepoint_range` argument. For example, `m = Prophet(changepoint_range=0.9)` in Python or `m <- prophet(changepoint.range = 0.9)` in R will place potential changepoints in the first 90% of the time series.
|
||||||
|
|
||||||
### Adjusting trend flexibility
|
### Adjusting trend flexibility
|
||||||
If the trend changes are being overfit (too much flexibility) or underfit (not enough flexibility), you can adjust the strength of the sparse prior using the input argument `changepoint_prior_scale`. By default, this parameter is set to 0.05. Increasing it will make the trend *more* flexible:
|
If the trend changes are being overfit (too much flexibility) or underfit (not enough flexibility), you can adjust the strength of the sparse prior using the input argument `changepoint_prior_scale`. By default, this parameter is set to 0.05. Increasing it will make the trend *more* flexible:
|
||||||
|
|
@ -28,16 +44,16 @@ If the trend changes are being overfit (too much flexibility) or underfit (not e
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, changepoint.prior.scale = 0.5)
|
m <- prophet(df, changepoint.prior.scale = 0.5)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m = Prophet(changepoint_prior_scale=0.5)
|
m = Prophet(changepoint_prior_scale=0.5)
|
||||||
forecast = m.fit(df).predict(future)
|
forecast = m.fit(df).predict(future)
|
||||||
m.plot(forecast);
|
fig = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
Decreasing it will make the trend *less* flexible:
|
Decreasing it will make the trend *less* flexible:
|
||||||
|
|
@ -46,34 +62,34 @@ Decreasing it will make the trend *less* flexible:
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, changepoint.prior.scale = 0.001)
|
m <- prophet(df, changepoint.prior.scale = 0.001)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m = Prophet(changepoint_prior_scale=0.001)
|
m = Prophet(changepoint_prior_scale=0.001)
|
||||||
forecast = m.fit(df).predict(future)
|
forecast = m.fit(df).predict(future)
|
||||||
m.plot(forecast);
|
fig = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### Specifying the locations of the changepoints
|
### Specifying the locations of the changepoints
|
||||||
|
|
||||||
If you wish, rather than using automatic changepoint detection you can manually specify the locations of potential changepoints with the `changepoints` argument.
|
If you wish, rather than using automatic changepoint detection you can manually specify the locations of potential changepoints with the `changepoints` argument. Slope changes will then be allowed only at these points, with the same sparse regularization as before. One could, for instance, create a grid of points as is done automatically, but then augment that grid with some specific dates that are known to be likely to have changes. As another example, the changepoints could be entirely limited to a small set of dates, as is done here:
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, changepoints = c('2014-01-01'))
|
m <- prophet(df, changepoints = c('2014-01-01'))
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
plot(m, forecast);
|
plot(m, forecast)
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m = Prophet(changepoints=['2014-01-01'])
|
m = Prophet(changepoints=['2014-01-01'])
|
||||||
forecast = m.fit(df).predict(future)
|
forecast = m.fit(df).predict(future)
|
||||||
m.plot(forecast);
|
fig = m.plot(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,39 +15,39 @@ One property of this way of measuring uncertainty is that allowing higher flexib
|
||||||
|
|
||||||
The width of the uncertainty intervals (by default 80%) can be set using the parameter `interval_width`:
|
The width of the uncertainty intervals (by default 80%) can be set using the parameter `interval_width`:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
forecast = Prophet(interval_width=0.95).fit(df).predict(future)
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, interval.width = 0.95)
|
m <- prophet(df, interval.width = 0.95)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
```
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
forecast = Prophet(interval_width=0.95).fit(df).predict(future)
|
||||||
|
```
|
||||||
Again, these intervals assume that the future will see the same frequency and magnitude of rate changes as the past. This assumption is probably not true, so you should not expect to get accurate coverage on these uncertainty intervals.
|
Again, these intervals assume that the future will see the same frequency and magnitude of rate changes as the past. This assumption is probably not true, so you should not expect to get accurate coverage on these uncertainty intervals.
|
||||||
|
|
||||||
### Uncertainty in seasonality
|
### Uncertainty in seasonality
|
||||||
By default Prophet will only return uncertainty in the trend and observation noise. To get uncertainty in seasonality, you must do full Bayesian sampling. This is done using the parameter `mcmc.samples` (which defaults to 0). We do this here for the Peyton Manning data from the Quickstart:
|
By default Prophet will only return uncertainty in the trend and observation noise. To get uncertainty in seasonality, you must do full Bayesian sampling. This is done using the parameter `mcmc.samples` (which defaults to 0). We do this here for the first six months of the Peyton Manning data from the Quickstart:
|
||||||
|
|
||||||
```python
|
|
||||||
# Python
|
|
||||||
m = Prophet(mcmc_samples=300)
|
|
||||||
forecast = m.fit(df).predict(future)
|
|
||||||
```
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
m <- prophet(df, mcmc.samples = 300)
|
m <- prophet(df, mcmc.samples = 300)
|
||||||
forecast <- predict(m, future)
|
forecast <- predict(m, future)
|
||||||
```
|
```
|
||||||
This replaces the typical MAP estimation with MCMC sampling, and takes much longer - think 10 minutes instead of 10 seconds. If you do full sampling, then you will see the uncertainty in seasonal components when you plot them:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Python
|
# Python
|
||||||
m.plot_components(forecast);
|
m = Prophet(mcmc_samples=300)
|
||||||
|
forecast = m.fit(df).predict(future)
|
||||||
```
|
```
|
||||||
|
This replaces the typical MAP estimation with MCMC sampling, and can take much longer depending on how many observations there are - expect several minutes instead of several seconds. If you do full sampling, then you will see the uncertainty in seasonal components when you plot them:
|
||||||
|
|
||||||
```R
|
```R
|
||||||
# R
|
# R
|
||||||
prophet_plot_components(m, forecast);
|
prophet_plot_components(m, forecast)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# Python
|
||||||
|
fig = m.plot_components(forecast)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title: Prophet
|
||||||
id: home
|
id: home
|
||||||
---
|
---
|
||||||
|
|
||||||
Prophet is a procedure for forecasting time series data. It is based on an additive model where non-linear trends are fit with yearly and weekly seasonality, plus holidays. It works best with daily periodicity data with at least one year of historical data. Prophet is robust to missing data, shifts in the trend, and large outliers.
|
Prophet is a procedure for forecasting time series data based on an additive model where non-linear trends are fit with yearly, weekly, and daily seasonality, plus holiday effects. It works best with time series that have strong seasonal effects and several seasons of historical data. Prophet is robust to missing data and shifts in the trend, and typically handles outliers well.
|
||||||
|
|
||||||
Prophet is [open source software](https://code.facebook.com/projects/) released by Facebook's [Core Data Science team](https://research.fb.com/category/data-science/). It is available for download on [CRAN](https://cran.r-project.org/package=prophet) and [PyPI](https://pypi.python.org/pypi/fbprophet/).
|
Prophet is [open source software](https://code.facebook.com/projects/) released by Facebook's [Core Data Science team](https://research.fb.com/category/data-science/). It is available for download on [CRAN](https://cran.r-project.org/package=prophet) and [PyPI](https://pypi.python.org/pypi/fbprophet/).
|
||||||
|
|
||||||
|
|
|
||||||
BIN
docs/static/diagnostics_files/diagnostics_11_0.png
vendored
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/static/diagnostics_files/diagnostics_12_0.png
vendored
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/static/diagnostics_files/diagnostics_3_0.png
vendored
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_10_0.png
vendored
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_3_1.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_4_0.png
vendored
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_6_1.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_7_0.png
vendored
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/static/multiplicative_seasonality_files/multiplicative_seasonality_9_0.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 136 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_15_1.png
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_16_0.png
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_18_1.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_19_0.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_21_0.png
vendored
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/static/non-daily_data_files/non-daily_data_22_0.png
vendored
Normal file
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 76 KiB |
BIN
docs/static/outliers_files/outliers_10_0.png
vendored
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
BIN
docs/static/outliers_files/outliers_12_1.png
vendored
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 46 KiB |
BIN
docs/static/outliers_files/outliers_13_0.png
vendored
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
BIN
docs/static/outliers_files/outliers_3_1.png
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
BIN
docs/static/outliers_files/outliers_4_0.png
vendored
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
BIN
docs/static/outliers_files/outliers_6_1.png
vendored
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
BIN
docs/static/outliers_files/outliers_7_0.png
vendored
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
BIN
docs/static/outliers_files/outliers_9_1.png
vendored
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
BIN
docs/static/quick_start_files/quick_start_12_0.png
vendored
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
BIN
docs/static/quick_start_files/quick_start_14_0.png
vendored
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
BIN
docs/static/quick_start_files/quick_start_27_0.png
vendored
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
BIN
docs/static/quick_start_files/quick_start_29_0.png
vendored
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
BIN
docs/static/saturating_forecasts_files/saturating_forecasts_12_0.png
vendored
Normal file
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 28 KiB |