July 28, 2026

Motivation and purpose

  • Time series analyses apply to endless job types and sectors

    • Economic forecasting

    • Climate change planning

    • Political polling and predictions

  • Why learn it in R?

    • R is largely considered a gold standard for open-source statistical analysis

    • One of the ten most popular programming languages world wide

Key functions and packages

Package/Library Functions Purpose
base R ts, decompose, stl break down time series
lubridate ymd, mdy, floor_date parse and manipulate dates
ggplot2 geom_line, geom_smooth create aesthetic visuals
forecast ses, holt, hw perform advanced time series modeling
lmtest dwtest test for first-order autocorrelation

Time series review

Different types of models

Model Type Consider Trend? Consider Seasonality? When to Use
SES No No Data has no clear pattern or trend
Holt Additive Yes No Linear trend, no seasonal variation
Holt Additive Damped Yes No Linear trend expected to flatten
Holt Multiplicative Damped Yes No Trend increasing/decreasing by a RATE

Different types of models

Model Type Consider Trend? Consider Seasonality? When to Use
HW Additive Yes Yes Linear trend, constant seasonal variation
HW Additive Damped Yes Yes Flattening linear trend, constant seasonal variation
HW Multiplicative Yes Yes Trend increasing/decreasing by a RATE, steady seasonality
HW Multiplicative Damped Yes Yes Trend increasing/decreasing by a RATE, unpredictable seasonality

Example scenario

  • We will look at Walmart stock price data
  • Scope: Jan 2014 to Jan 2024

  • Focus: will look at average opening price on a monthly level

  • Goal: Determine which time series model is most effective / applicable for this situation

Step 1: Get data in desired format

# read in data, scan check data types and affirm no missing values
full_data = read_csv("wmt_data.csv")
glimpse(full_data)
sum(is.na(full_data)) # no missing values

# select columns we need, renaming it to lowercase for easier coding
data = select(full_data, c(Date, Open))
colnames(data) = c("date", "open")

# look at 10 year span, January of 2014 to January 2024
data = filter(data, date >= "2014-01-01" & date <= "2024-01-01") 

## create single column for month/year, drop original full date column
data = mutate(data, month_year = floor_date(date, unit = "month"))
data = select(data, c(open, month_year))

# getting average open by month
data = group_by(data, month_year) %>% 
  summarize(
    avg_open = mean(open),
    #min_open = min(open),
    #max_open = max(open)
  )

# round to nearest cent
data$avg_open = round(data$avg_open,2)

Step 1: Get data in desired format

Original Raw Data
Date Open High Low Close Adj Close Volume
1972-08-25 0.021159 0.021566 0.021159 0.021484 0.0116645 7526400
1972-08-28 0.021484 0.021647 0.021403 0.021403 0.0116205 2918400
1972-08-29 0.021322 0.021322 0.021159 0.021159 0.0114880 5836800
1972-08-30 0.021159 0.021159 0.020996 0.021159 0.0114880 1228800
1972-08-31 0.020996 0.020996 0.020833 0.020833 0.0113110 2611200
1972-09-01 0.020915 0.020996 0.020915 0.020996 0.0113995 768000

Step 1: Get data in desired format

Data Ready for Exploration
month_year avg_open
2014-01-01 25.57
2014-02-01 24.62
2014-03-01 25.10
2014-04-01 25.89
2014-05-01 25.79
2014-06-01 25.35

Step 2: Visualize and decompose data

  • Overall trend is positive and linear (additive)

Step 2: Visualize and decompose data

  • Throughout whole time duration, no obvious pattern of consistent and noticeable seasonal oscillation
    • Aug 2017 to Feb 2019 large difference between Q2 and Q4
    • Feb 2022 to Feb 2023; large difference between Q1 and Q2
  • Will perform formal decomposition before making final model choice.

Step 2: Visualize and decompose data

# convert data frame to time series object
data_ts = ts(data$avg_open,
             start = c(2014,1),
             end = c(2023, 12),
             frequency = 12)

# create with decompose() function
help("decompose")
decomp_add = decompose(x = data_ts,
                       type = "additive")

# create with stl() function
help(stl)
decomp_stl = stl(x = data_ts,
    s.window = "periodic") 
    # s.window function signals that sesonality does, at least in theory, exist

Step 2: Visualize and decompose data

Step 2: Visualize and decompose data

Step 2: Visualize and decompose data

After looking at initial visualizations and decompisitions:

  • Positive trend that is fairly linear

  • Constant seasonal oscillation

  • Given this, will test the HW additive and HW damped additive models

Step 3: Training/testing split, model creation

  • Will use the last 12 months (10% of the data) as testing split


Step 3: Training/testing split, model creation

hw_add = hw(training_ts,
            h = 12,
            damped = FALSE,
            seasonal = "additive")

hw_add_damped = hw(training_ts,
            h = 12,
            damped = TRUE,
            seasonal = "additive")

hw_add_predictions = data.frame(round(hw_add$mean,2))
hw_add_damped_predictions = data.frame(round(hw_add_damped$mean,2))

predictions = cbind(data_testing$month_year,
                    hw_add_predictions,
                    hw_add_damped_predictions)

colnames(predictions) = c("month_year", "hw_add", "hw_add_damped")
Prediction Data
month_year hw_add hw_add_damped
2023-01-01 48.90 48.88
2023-02-01 48.65 48.22
2023-03-01 48.07 47.44
2023-04-01 49.44 48.59
2023-05-01 48.84 47.77
2023-06-01 48.07 47.20
2023-07-01 49.12 48.23
2023-08-01 49.79 48.44
2023-09-01 50.11 48.59
2023-10-01 50.25 48.67
2023-11-01 51.55 49.86
2023-12-01 51.32 49.36

Step 4: Evaluate efficiency on testing data

Step 4: Evaluate efficiency on testing data

Conclusion

  • Ultimately, HW Add was the ideal choice for this scenario.
Accuracy Measure Summary
MSE MAPE
HW Add 5.677 0.039
HW Add Damped 11.447 0.056

References

Questions?