Introduction

Dataset Overview

The Baregg Tunnel dataset contains daily traffic volume measurements recorded at the Baregg Tunnel in Switzerland. This dataset spans from November 1, 2003, to November 16, 2005, providing approximately two years of daily observations. Each record captures the total number of vehicles passing through the tunnel on a given day.

Forecasting Objective

The primary objective of this analysis is to develop and evaluate forecasting models capable of accurately predicting daily traffic volumes at the Baregg Tunnel. Accurate traffic forecasts are essential for:

  • Infrastructure planning and maintenance scheduling
  • Traffic management and congestion mitigation
  • Resource allocation for tunnel operations
  • Understanding long-term traffic trends and patterns

We will compare two forecasting approaches: a Naïve model (benchmark) and a Linear Regression model with trend and weekly seasonality (TSLM). Our goal is to determine which approach provides superior forecast accuracy and to identify opportunities for further improvement.


Data Exploration

Load Required Packages

# Load the fpp3 package suite (includes tsibble, fable, feasts, and ggplot2)
library(fpp3)

Step 1: Load and Prepare Data

# Read CSV and create a tsibble object
baregg <- read.csv("BareggTunnel.csv") |>
  mutate(Day = dmy(Day)) |>
  as_tsibble(index = Day)

# Display first few rows
head(baregg)
# Summary statistics
summary(baregg$Number.of.vehicles)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   53098   98794  108204  106568  115477  139840

Time Series Visualization

# Create a comprehensive time plot of the entire dataset
autoplot(baregg, Number.of.vehicles) +
  labs(
    title = "Baregg Tunnel: Daily Traffic Volume (Nov 2003 - Nov 2005)",
    y = "Number of Vehicles",
    x = "Date"
  ) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

Observations from Data Exploration

From the time series plot above, we observe several key characteristics:

  1. Trend: The data exhibits a relatively stable pattern with no strong upward or downward trend over the observation period.

  2. Seasonality: There is clear weekly seasonality evident in the data. Traffic volumes show regular patterns that repeat every seven days, likely reflecting differences between weekday and weekend traffic.

  3. Volatility: The series shows consistent variability with regular oscillations, suggesting predictable day-of-week effects.

  4. Outliers: There appear to be some periods with notably lower traffic volumes, which could correspond to holidays or special events.


Methodology

Data Partitioning Strategy

To properly evaluate forecast accuracy, we partition the data into two subsets:

  • Training Set: November 2003 through June 2005 (approximately 20 months)
  • Validation Set: July 2005 through November 2005 (approximately 5 months)

This partitioning allows us to fit models on historical data and test their predictive performance on a hold-out sample that was not used during model estimation.

# Split data into training and validation sets
train <- baregg |>
  filter(Day < ymd("2005-07-01"))

valid <- baregg |>
  filter(Day >= ymd("2005-07-01"))

cat("Training observations:", nrow(train), "\n")
## Training observations: 608
cat("Validation observations:", nrow(valid), "\n")
## Validation observations: 139

Model Descriptions

1. Naïve Model (Benchmark)

The Naïve forecasting method is the simplest possible approach: it assumes that the forecast for any future period equals the last observed value. Mathematically:

\[\hat{y}_{t+h|t} = y_t\]

Where \(\hat{y}_{t+h|t}\) is the forecast for time \(t+h\) made at time \(t\), and \(y_t\) is the last observed value.

Why use the Naïve model? The Naïve method serves as a benchmark against which more sophisticated models should be compared. If a complex model cannot outperform the Naïve forecast, it suggests the added complexity provides no practical benefit. For many business applications, the Naïve method is surprisingly effective, especially for stable time series.

2. Linear Regression Model (TSLM)

The Time Series Linear Model (TSLM) uses ordinary least squares (OLS) regression with time-based predictors:

\[y_t = \beta_0 + \beta_1 \cdot \text{trend}_t + \sum_{d=1}^{6} \beta_d \cdot \text{Day}_d + \varepsilon_t\]

Where:

  • trend() captures linear growth or decline over time (a counter: 1, 2, 3, …)
  • season(“week”) creates dummy variables for days of the week (6 dummies, with one day as reference)
  • \(\varepsilon_t\) represents the random error term

This model can capture both long-term trends and weekly seasonal patterns, making it more flexible than the Naïve approach.


Model Fitting

Step 3: Fit Models on Training Data

# Fit the Naïve model
fit_naive <- train |>
  model(Naive = NAIVE(Number.of.vehicles))

# Fit the Linear Regression model with trend and weekly seasonality
fit_lm <- train |>
  model(
    LinReg = TSLM(Number.of.vehicles ~ trend() + season("week"))
  )

# Display regression coefficients
report(fit_lm)
## Series: Number.of.vehicles 
## Model: TSLM 
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -51399  -3345   2441   5081  18641 
## 
## Coefficients:
##                           Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             102993.045   1049.249  98.159  < 2e-16 ***
## trend()                     24.250      1.886  12.858  < 2e-16 ***
## season("week")season_72   6161.250   1241.087   4.964 8.99e-07 ***
## season("week")season_73  -9373.072   1237.522  -7.574 1.37e-13 ***
## season("week")season_74 -21859.736   1237.509 -17.664  < 2e-16 ***
## season("week")season_75  -6685.066   1237.499  -5.402 9.51e-08 ***
## season("week")season_76  -4152.765   1237.492  -3.356 0.000841 ***
## season("week")season_77   -310.601   1237.488  -0.251 0.801905    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 8162 on 600 degrees of freedom
## Multiple R-squared: 0.5689,  Adjusted R-squared: 0.5638
## F-statistic: 113.1 on 7 and 600 DF, p-value: < 2.22e-16

Interpretation of Regression Coefficients

The report() output shows the estimated coefficients for the linear regression model. The coefficients reveal:

  • Intercept: The baseline traffic volume
  • Trend coefficient: Whether traffic is increasing or decreasing over time
  • Day-of-week effects: How much traffic differs on each day compared to the reference day

Results

Step 4: Generate Forecasts and Compare Model Accuracy

Generate Forecasts for Validation Period

# Generate forecasts from the Naïve model
fc_naive <- fit_naive |>
  forecast(new_data = valid)

# Generate forecasts from the Linear Regression model
fc_lm <- fit_lm |>
  forecast(new_data = valid)

# Display first few forecasts from each model
cat("First 10 Naïve Forecasts:\n")
## First 10 Naïve Forecasts:
head(fc_naive$.mean, 10)
##  [1] 125353 125353 125353 125353 125353 125353 125353 125353 125353 125353
cat("\nFirst 10 Linear Regression Forecasts:\n")
## 
## First 10 Linear Regression Forecasts:
head(fc_lm$.mean, 10)
##  [1] 123922.50 108412.42  95950.01 111148.93 113705.48 117571.89 117906.74
##  [8] 124092.24 108582.17  96119.76

Forecast Overlay Plots

# Create overlay plot for Naïve model
p1 <- fc_naive |>
  autoplot(baregg, level = NULL) +
  labs(
    title = "Naïve Model: Forecast vs Actual",
    y = "Number of Vehicles",
    x = "Date"
  ) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

# Create overlay plot for Linear Regression model
p2 <- fc_lm |>
  autoplot(baregg, level = NULL) +
  labs(
    title = "Linear Regression Model: Forecast vs Actual",
    y = "Number of Vehicles",
    x = "Date"
  ) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, face = "bold"))

# Display both plots
print(p1)

print(p2)

Combined Comparison Plot

# Combine both forecasts into a single dataset for comparison
fc_combined <- bind_rows(
  fc_naive |> mutate(Model = "Naïve"),
  fc_lm |> mutate(Model = "Linear Regression")
)

# Create overlay plot showing both forecasts against actual data
fc_combined |>
  autoplot(baregg, level = NULL) +
  autolayer(valid, Number.of.vehicles, color = "black", size = 1) +
  labs(
    title = "Forecast Comparison: Naïve vs Linear Regression",
    subtitle = "Validation Period: July - November 2005",
    y = "Number of Vehicles",
    x = "Date",
    color = "Model"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5),
    legend.position = "bottom"
  )

Accuracy Metrics Comparison

# Compute accuracy metrics for both models
acc_naive <- accuracy(fc_naive, baregg)
acc_lm <- accuracy(fc_lm, baregg)

# Combine into a comparison table
accuracy_table <- bind_rows(
  acc_naive |> mutate(Model = "Naïve"),
  acc_lm |> mutate(Model = "Linear Regression")
) |>
  select(Model, ME, RMSE, MAE, MAPE, MASE)

# Display the table
print(accuracy_table)
## # A tibble: 2 × 6
##   Model                  ME   RMSE    MAE  MAPE  MASE
##   <chr>               <dbl>  <dbl>  <dbl> <dbl> <dbl>
## 1 Naïve             -12734. 16821. 13606. 13.1  2.78 
## 2 Linear Regression  -1603.  5869.  3900.  3.70 0.798

Manual MASE Calculation

The Mean Absolute Scaled Error (MASE) scales the forecast errors relative to the Naïve model’s in-sample performance. It is calculated as:

\[\text{MASE} = \frac{\text{MAE}_{\text{forecast}}}{\text{MAE}_{\text{naive-train}}}\]

Where: - \(\text{MAE}_{\text{forecast}}\) is the mean absolute error of the forecast on validation data - \(\text{MAE}_{\text{naive-train}}\) is the mean absolute error of the Naïve method on training data

Interpretation: - MASE < 1: The model outperforms the Naïve benchmark - MASE = 1: The model performs equally to the Naïve benchmark
- MASE > 1: The model underperforms the Naïve benchmark

# Calculate MAE for Naïve model on training data (in-sample)
# For daily data, we use first differences
mae_naive_train <- mean(abs(diff(train$Number.of.vehicles)), na.rm = TRUE)

cat("In-sample MAE (Naïve benchmark):", round(mae_naive_train, 2), "\n\n")
## In-sample MAE (Naïve benchmark): 9420.08
# Calculate MASE for Naïve model on validation data
mae_naive_valid <- mean(abs(valid$Number.of.vehicles - fc_naive$.mean))
mase_naive <- mae_naive_valid / mae_naive_train

cat("Naïve Model:\n")
## Naïve Model:
cat("  MAE on validation:", round(mae_naive_valid, 2), "\n")
##   MAE on validation: 13605.53
cat("  MASE:", round(mase_naive, 3), "\n\n")
##   MASE: 1.444
# Calculate MASE for Linear Regression model on validation data
mae_lm_valid <- mean(abs(valid$Number.of.vehicles - fc_lm$.mean))
mase_lm <- mae_lm_valid / mae_naive_train

cat("Linear Regression Model:\n")
## Linear Regression Model:
cat("  MAE on validation:", round(mae_lm_valid, 2), "\n")
##   MAE on validation: 3899.74
cat("  MASE:", round(mase_lm, 3), "\n\n")
##   MASE: 0.414
cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cat("Interpretation: If MASE < 1 → beats naïve benchmark\n")
## Interpretation: If MASE < 1 → beats naïve benchmark
cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Accuracy Interpretation

Based on the accuracy metrics computed above, we can evaluate both models:

Key Metrics Explained:

  • ME (Mean Error): Measures bias. Values close to zero indicate unbiased forecasts. Positive values mean the model tends to over-forecast; negative values indicate under-forecasting.

  • RMSE (Root Mean Squared Error): Measures overall forecast accuracy, penalizing large errors more heavily. Lower values indicate better performance.

  • MAE (Mean Absolute Error): Measures average magnitude of forecast errors. More interpretable than RMSE as it’s in the same units as the data.

  • MAPE (Mean Absolute Percentage Error): Expresses accuracy as a percentage, useful for understanding relative error magnitude.

  • MASE (Mean Absolute Scaled Error): Scale-independent measure comparing forecast accuracy to the Naïve benchmark. MASE < 1 indicates superior performance to the Naïve method.

Comparison: (The specific comparison will depend on your actual results)

Looking at the accuracy table, we observe that Linear Regression model demonstrates superior performance across multiple metrics. Specifically:

  • The Linear Regression achieves a lower RMSE of 5869 compared to 16821
  • Its MAE of 3900 indicates more accurate predictions on average
  • The MASE value of 0.798 confirms it beats the Naïve benchmark

Step 5: Diagnose Forecast Errors

Examining forecast residuals helps us understand whether our models are capturing the underlying patterns in the data or if systematic errors remain.

# Generate comprehensive diagnostic plots for Naïve model
fit_naive |>
  gg_tsresiduals() +
  labs(title = "Residual Diagnostics: Naïve Model") +
  theme_minimal()

# Generate comprehensive diagnostic plots for Linear Regression model
fit_lm |>
  gg_tsresiduals() +
  labs(title = "Residual Diagnostics: Linear Regression Model") +
  theme_minimal()

Diagnostic Questions and Analysis

Question 5: Does the forecast follow the actual pattern (ups and downs)?

Answer:

Using the forecast overlay plots for the validation period, we can see how closely each model tracks the day-to-day movements in traffic.

  • Naïve Model: The Naïve forecast is essentially flat at the last training value during the validation window, so it does not follow the ups and downs visible in the actual series.

  • Linear Regression Model: The Linear Regression forecasts show a repeating weekly pattern, so they move up and down in a way that broadly matches the observed weekly cycles, although individual peaks and troughs may be slightly misaligned.

Conclusion: Overall, the Linear Regression model follows the actual pattern of ups and downs much better than the Naïve model.


Question 6: Is one model consistently above or below the actual values? What does this tell you about bias?

Answer:

From the accuracy table, the Mean Error (ME) for each model on the validation data is:

  • Naïve Model ME: -12733.51
  • Linear Regression Model ME: -1603.49

If a model’s ME is clearly positive, it tends to over-forecast; if clearly negative, it tends to under-forecast.

Interpretation: In the results, the Naïve model ME is strongly negative, indicating it under-forecasts on average by about 12734 vehicles per day. The Linear Regression model ME is closer to zero, so it appears less biased than the Naïve model. This suggests that Linear Regression is closer to being unbiased.

Implication: Bias indicates that the model is missing a systematic component. An unbiased model may still have large errors, but those errors are not directionally consistent.


Question 7: Are the forecast errors random or do they show a pattern over time?

Answer:

The residual time plots (top panel of each gg_tsresiduals output) show how errors evolve over time.

  • Naïve Model: The residuals display a clear repeating pattern, with systematic swings that line up with the weekly cycle in the data. This indicates that important structure (weekly seasonality) is not captured, so the errors are not random.

  • Linear Regression Model: The residuals fluctuate around zero without a strong repeating pattern, although there may still be some small clusters. This looks much closer to random noise.

Conclusion: Therefore, the Linear Regression model has more random residuals over time, which indicates a better fit to the underlying structure of the series.


Question 8: Is the histogram of errors approximately centered at zero?

Answer:

The bottom panel of each gg_tsresiduals plot shows the residual histogram.

  • Naïve Model: The histogram is Roughly symmetric and appears to be shifted away from zero, suggesting some directional bias.

  • Linear Regression Model: The histogram is more symmetric and more clearly centered around zero, which is consistent with an approximately unbiased model.

Conclusion: In terms of centering, the Linear Regression model’s histogram is closer to zero than the Naïve model’s, so it shows less bias overall.


Question 9: Does the ACF plot show any significant spikes? If so, what do they suggest?

Answer:

The ACF plot (middle panel of gg_tsresiduals) shows whether residuals are autocorrelated at different lags. Spikes outside the dashed blue bounds are statistically significant.

  • Naïve Model: The residual ACF shows clear significant spikes at lags around 7, 14, 21, etc., which correspond to the weekly pattern in the data. This tells us that the Naïve model leaves strong, systematic weekly structure in the residuals and is therefore inadequate.

  • Linear Regression Model: The residual ACF has few significant spikes, with most autocorrelations lying inside the bounds. There may be a small spike at lag 7 that suggests a bit of remaining structure, but the overall autocorrelation is much weaker.

Conclusion: These ACF results indicate that the Linear Regression model captures most of the serial dependence in the data, while the Naïve model leaves strong autocorrelation and thus misses important patterns.


Conclusion

Model Performance Summary

Based on our comprehensive analysis of forecast accuracy and residual diagnostics, we can draw the following conclusions:

Winner: Linear Regression

Justification:

  • The Linear Regression model achieved lower RMSE, MAE, and MAPE values
  • Its MASE of 0.798 indicates it outperforms the Naïve benchmark
  • Residual diagnostics show better properties: more random errors, fewer ACF spikes, and less bias
  • The forecast visually tracks the actual pattern more closely

Why this model performs better:

The Linear Regression model outperforms the Naïve forecast because it explicitly models weekly seasonality through day-of-week dummy variables. Since traffic patterns at the Baregg Tunnel exhibit strong weekly cycles, capturing this seasonal component dramatically improves forecast accuracy. The Naïve model, by contrast, simply repeats the last observed value and cannot anticipate these regular fluctuations.

Recommendations for Future Improvement

While the [winning model] demonstrates reasonable forecast accuracy, several enhancements could further improve performance:

  1. Incorporate Holiday Effects: Traffic patterns likely differ on public holidays. Adding dummy variables for holidays could reduce forecast errors during these periods.

  2. Explore More Sophisticated Seasonal Models: Methods like Seasonal ARIMA (SARIMA) or STL decomposition could capture seasonal patterns more flexibly than simple dummy variables.

  3. Add External Regressors: Weather conditions, gas prices, or regional events could influence traffic volumes. Including these as predictors might improve accuracy.

  4. Try Exponential Smoothing Methods: Models like Holt-Winters can adaptively weight recent observations and may capture trends and seasonality more effectively.

  5. Consider Ensemble Methods: Combining forecasts from multiple models often outperforms any single model.

  6. Longer Validation Period: Testing on a longer hold-out sample (e.g., 12 months) would provide more robust accuracy estimates.

  7. Forecast Combination: Averaging the Naïve and Linear Regression forecasts might reduce overall error.

Practical Implications

For tunnel operators and transportation planners, these findings suggest:

  • Weekly seasonality is the dominant pattern in daily traffic volumes
  • Simple statistical models can provide useful forecasts for operational planning
  • Further refinement may be needed for high-stakes decisions or long-term strategic planning

Code Reproducibility

All code used in this analysis is included above and can be executed sequentially to reproduce the results. The analysis requires:

  • R version: 4.0 or higher
  • Required packages: fpp3 (which loads tsibble, fable, feasts, and ggplot2)
  • Data file: BareggTunnel.csv (must be in the working directory)

To reproduce this analysis:

  1. Install required packages: install.packages("fpp3")
  2. Set working directory to location containing BareggTunnel.csv
  3. Knit this R Markdown file to HTML or PDF

Citations and Attribution

This analysis was completed independently with guidance from the course materials and assignment instructions provided in SP 26 Business Forecasting.

External Resources Used:

AI Tool Disclosure:


Session Information

sessionInfo()
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 10 x64 (build 19045)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: America/New_York
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] fable_0.5.0       feasts_0.4.2      fabletools_0.5.1  ggtime_0.2.0     
##  [5] tsibbledata_0.4.1 tsibble_1.1.6     ggplot2_3.5.2     lubridate_1.9.4  
##  [9] tidyr_1.3.1       dplyr_1.1.4       tibble_3.3.0      fpp3_1.0.3       
## 
## loaded via a namespace (and not attached):
##  [1] ggdist_3.3.3         rappdirs_0.3.3       sass_0.4.10         
##  [4] utf8_1.2.6           generics_0.1.4       anytime_0.3.12      
##  [7] digest_0.6.37        magrittr_2.0.3       evaluate_1.0.4      
## [10] grid_4.5.1           timechange_0.3.0     RColorBrewer_1.1-3  
## [13] fastmap_1.2.0        jsonlite_2.0.0       purrr_1.1.0         
## [16] scales_1.4.0         jquerylib_0.1.4      cli_3.6.5           
## [19] rlang_1.1.6          crayon_1.5.3         ellipsis_0.3.2      
## [22] withr_3.0.2          cachem_1.1.0         yaml_2.3.10         
## [25] tools_4.5.1          vctrs_0.6.5          R6_2.6.1            
## [28] lifecycle_1.0.4      pkgconfig_2.0.3      progressr_0.18.0    
## [31] pillar_1.11.0        bslib_0.9.0          gtable_0.3.6        
## [34] glue_1.8.0           Rcpp_1.1.1           xfun_0.52           
## [37] tidyselect_1.2.1     rstudioapi_0.17.1    knitr_1.50          
## [40] farver_2.1.2         htmltools_0.5.8.1    rmarkdown_2.29      
## [43] labeling_0.4.3       compiler_4.5.1       distributional_0.6.0

Report prepared by: Aashraya Rai Student ID: 00974536 Course: Business Forecasting
Assignment: #1 - Baregg Tunnel Traffic Forecasting
Submission Date: February 16, 2026