Operations Analytics Using R

10 Forecasting and Predictive Analytics

image

Learning Objectives

By the end of this chapter, students will:

  1. Prepare operational time series in R and split into training/test (and rolling-origin).

  2. Build and compare baseline forecasts (naïve, seasonal naïve, moving average).

  3. Fit exponential smoothing (ETS) and ARIMA models; interpret components and diagnostics.

  4. Evaluate accuracy with MAE, RMSE, and MAPE; select a model for deployment.

  5. Produce a short operations recommendation grounded in forecast results.

10.1 Framing and data prep (R)

Forecasting is about estimating future values in a time series — here, daily orders. Predictive analytics goes a step further by adding outside drivers, such as promotions or weather, that may explain or improve those forecasts.

Before modeling, clarify the business question: “How many orders should we expect in the next 28 days, so we can plan staffing and inventory?” Then:

  • Choose the right frequency (daily orders with a weekly pattern).

  • Hold out the last 28 days of data as a test window. This gives us an honest way to judge model accuracy.

  • Prepare candidate external drivers (e.g., promo days, average daily temperature) for models that support them.

R

library(readr); library(dplyr); library(lubridate); library(ggplot2); library(forecast)

df <- read_csv(“Chapter10_DailyOrders_PlymouthNH.csv”) %>% mutate(date = as.Date(date))

# hold out the last 28 days
h <- 28
df_train <- df |> slice_head(n = n() h)
df_test <- df |> slice_tail(n = h)

# weekly seasonality -> frequency = 7
y_train <- ts(df_train$orders, frequency = 7)
y_full <- ts(df$orders, frequency = 7)

# candidate external drivers (optional in some models)
x_train <- as.matrix(df_train[, c(“promo_flag”,“avg_temp_f”)])
x_test <- as.matrix(df_test[, c(“promo_flag”,“avg_temp_f”)])

ggplot(df, aes(date, orders)) + geom_line()

10.2 Baselines: naïve, seasonal naïve, moving average

Baselines are simple “no-frills” forecasts. They set the minimum that more complex models must beat.

  • Naïve: tomorrow equals today. Good when series wander slowly.

  • Seasonal naïve: next Monday equals last Monday. Strong when day-of-week patterns dominate.

  • Moving average: smooths noise to show the local level but does not project seasonality by itself.

R

fc_naive <- naive(y_train, h = h)
fc_snaive <- snaive(y_train, h = h) # respects weekly pattern
autoplot(y_full) +

autolayer(fc_snaive, series = “Seasonal naive”, PI = FALSE) +
autolayer(fc_naive, series = “Naive”, PI = FALSE)

10.3 Exponential smoothing (ETS)

ETS breaks the series into level, trend, and seasonality. It adapts quickly when things change and is a common default for operational data. R can automatically pick the best combination of additive or multiplicative forms. After fitting, check residuals to confirm no leftover pattern.

R

fit_ets <- ets(y_train) # auto-selects additive/multiplicative forms
fc_ets <- forecast(fit_ets, h = h)
autoplot(fc_ets)

checkresiduals(fit_ets) # want residuals ~ white noise

10.4 ARIMA and ARIMAX (with temperature and promos)

ARIMA uses past values and past errors to explain the series. Seasonal ARIMA adds repeating patterns (like weekly cycles).

  • ARIMA: relies only on the series’ own history.

  • ARIMAX: adds external predictors (e.g., promos, temperature). This can sharpen forecasts when drivers explain part of demand.

Residual checks confirm whether the model has captured the structure, or whether systematic patterns remain.

R

# ARIMA without drivers
fit_arima <- auto.arima(y_train, seasonal = TRUE)
fc_arima <- forecast(fit_arima, h = h)
# ARIMAX with promo + temperature

fit_arimax <- auto.arima(y_train, xreg = x_train, seasonal = TRUE)
fc_arimax <- forecast(fit_arimax, xreg = x_test, h = h)checkresiduals(fit_arima)
checkresiduals(fit_arimax)

Notes: Be careful not to “peek” at the future when preparing drivers (xreg for the test window must be known at forecast time). If residuals still show weekly structure or long runs, revisit the specification.

10.5 Accuracy comparison and rolling-origin

We compare competing models on the same holdout window using:

  • MAE (average absolute error): easy to interpret in original units.

  • RMSE (root mean square error): penalizes large misses more heavily.

  • MAPE (percentage error): intuitive, but avoid when actuals can be near zero.

A single split can be lucky. Rolling-origin cross-validation re-forecasts many times from expanding windows and averages the error, giving a more stable view of performance.

R

library(Metrics)

y_test <- df_test$orders
compare <- dplyr::bind_rows(
tibble::tibble(model=“naive”,
MAE=mae(y_test, fc_naive$mean),
RMSE=rmse(y_test, fc_naive$mean),
MAPE=Metrics::mape(y_test, fc_naive$mean)),
tibble::tibble(model=“snaive”,
MAE=mae(y_test, fc_snaive$mean),
RMSE=rmse(y_test, fc_snaive$mean),
MAPE=Metrics::mape(y_test, fc_snaive$mean)),
tibble::tibble(model=“ETS”,
MAE=mae(y_test, fc_ets$mean),
RMSE=rmse(y_test, fc_ets$mean),
MAPE=Metrics::mape(y_test, fc_ets$mean)),
tibble::tibble(model=“ARIMA”,
MAE=mae(y_test, fc_arima$mean),
RMSE=rmse(y_test, fc_arima$mean),
MAPE=Metrics::mape(y_test, fc_arima$mean)),
tibble::tibble(model=“ARIMAX”,
MAE=mae(y_test, fc_arimax$mean),
RMSE=rmse(y_test, fc_arimax$mean),
MAPE=Metrics::mape(y_test, fc_arimax$mean))
) |> arrange(RMSE)
compare

Rolling-origin example (7-day horizon):

e_snaive <- tsCV(y_full, function(y, h) forecast(snaive(y), h=h), h = 7)
e_arima <- tsCV(y_full, function(y, h) forecast(auto.arima(y), h=h), h = 7)
sqrt(mean(e_snaive^2, na.rm=TRUE))

sqrt(mean(e_arima^2, na.rm=TRUE))

Choose the simplest model that meets your accuracy target and leaves residuals looking like noise.

10.6 Applied example: 28-day staffing plan (R)

Forecasts matter when they drive action. For Pemi Coffee Roasters, we use the chosen model (e.g., ETS or ARIMAX) to forecast 28 days of orders. Orders are converted into staff requirements using a simple rule (one staff per 40 orders).

The deliverable is a short memo with:

  • The forecast horizon (28 days).

  • The chosen model and why it was selected.

  • A staffing table by day.

  • Any flagged risks (e.g., promotions or heat waves) and one suggested mitigation.

R

best <- fit_arimax # or fit_ets / fit_arima based on compare + residuals
fc <- forecast(best, xreg = x_test, h = h)
staff_per_day <- ceiling(as.numeric(fc$mean) / 40)

out <- tibble::tibble(
date = tail(df$date, 1) + 1:h,
forecast_orders = as.numeric(fc$mean),
staff_needed = staff_per_day
)
print(out)

Memo pointers: state the horizon, the chosen model and why, attach the daily staffing table, flag any operational risks (e.g., a promo cluster) and one mitigation.

Key Takeaways

  • Forecasting projects future demand; predictive analytics adds drivers like promos or weather.

  • Define the question, set frequency, and hold out data for testing.

  • Baselines (naïve, seasonal naïve, moving average) are the benchmark.

  • ETS, ARIMA, and ARIMAX capture trend, seasonality, and external drivers.

  • Accuracy checks use MAE, RMSE, and MAPE, plus rolling-origin validation.

  • Forecasts should guide concrete actions, such as staffing plans.

Chapter 10 References

Microsoft. (n.d.). Forecasting functions in Excel. Microsoft. https://support.microsoft.com/excel

R Core Team. (2023). R: A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.r-project.org

Hyndman, R. J., & Athanasopoulos, G. (2021). Forecasting: Principles and practice (3rd ed.). OTexts. https://otexts.com/fpp3/

Wickham, H., & Grolemund, G. (2017). R for data science: Import, tidy, transform, visualize, and model data. O’Reilly Media. https://r4ds.had.co.nz

 

License

Icon for the Creative Commons Attribution 4.0 International License

Business Operations Analytics Copyright © by Melissa Christensen is licensed under a Creative Commons Attribution 4.0 International License, except where otherwise noted.

Share This Book