Discussion 5

Discussion 5

Data Preparation

library(fpp3)
Warning: package 'fpp3' was built under R version 4.5.2
── Attaching packages ──────────────────────────────────────────── fpp3 1.0.3 ──
✔ tibble      3.3.0     ✔ tsibble     1.2.0
✔ dplyr       1.2.1     ✔ tsibbledata 0.4.1
✔ tidyr       1.3.1     ✔ ggtime      0.2.0
✔ lubridate   1.9.4     ✔ feasts      0.5.0
✔ ggplot2     4.0.3     ✔ fable       0.5.0
Warning: package 'dplyr' was built under R version 4.5.2
Warning: package 'ggplot2' was built under R version 4.5.2
Warning: package 'tsibble' was built under R version 4.5.2
Warning: package 'ggtime' was built under R version 4.5.2
Warning: package 'feasts' was built under R version 4.5.2
Warning: package 'fabletools' was built under R version 4.5.2
Warning: package 'fable' was built under R version 4.5.2
── Conflicts ───────────────────────────────────────────────── fpp3_conflicts ──
✖ lubridate::date()    masks base::date()
✖ dplyr::filter()      masks stats::filter()
✖ tsibble::intersect() masks base::intersect()
✖ tsibble::interval()  masks lubridate::interval()
✖ dplyr::lag()         masks stats::lag()
✖ tsibble::setdiff()   masks base::setdiff()
✖ tsibble::union()     masks base::union()
data(package= "fpp3")

vic_elec
# A tsibble: 52,608 x 5 [30m] <Australia/Melbourne>
   Time                Demand Temperature Date       Holiday
   <dttm>               <dbl>       <dbl> <date>     <lgl>  
 1 2012-01-01 00:00:00  4383.        21.4 2012-01-01 TRUE   
 2 2012-01-01 00:30:00  4263.        21.0 2012-01-01 TRUE   
 3 2012-01-01 01:00:00  4049.        20.7 2012-01-01 TRUE   
 4 2012-01-01 01:30:00  3878.        20.6 2012-01-01 TRUE   
 5 2012-01-01 02:00:00  4036.        20.4 2012-01-01 TRUE   
 6 2012-01-01 02:30:00  3866.        20.2 2012-01-01 TRUE   
 7 2012-01-01 03:00:00  3694.        20.1 2012-01-01 TRUE   
 8 2012-01-01 03:30:00  3562.        19.6 2012-01-01 TRUE   
 9 2012-01-01 04:00:00  3433.        19.1 2012-01-01 TRUE   
10 2012-01-01 04:30:00  3359.        19.0 2012-01-01 TRUE   
# ℹ 52,598 more rows
glimpse(vic_elec)
Rows: 52,608
Columns: 5
$ Time        <dttm> 2012-01-01 00:00:00, 2012-01-01 00:30:00, 2012-01-01 01:0…
$ Demand      <dbl> 4382.825, 4263.366, 4048.966, 3877.563, 4036.230, 3865.597…
$ Temperature <dbl> 21.40, 21.05, 20.70, 20.55, 20.40, 20.25, 20.10, 19.60, 19…
$ Date        <date> 2012-01-01, 2012-01-01, 2012-01-01, 2012-01-01, 2012-01-0…
$ Holiday     <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE…
summary(vic_elec)
      Time                         Demand      Temperature   
 Min.   :2012-01-01 00:00:00   Min.   :2858   Min.   : 1.50  
 1st Qu.:2012-09-30 22:52:30   1st Qu.:3969   1st Qu.:12.30  
 Median :2013-07-01 22:45:00   Median :4635   Median :15.40  
 Mean   :2013-07-01 22:45:00   Mean   :4665   Mean   :16.27  
 3rd Qu.:2014-04-01 23:37:30   3rd Qu.:5244   3rd Qu.:19.40  
 Max.   :2014-12-31 23:30:00   Max.   :9345   Max.   :43.20  
      Date             Holiday       
 Min.   :2012-01-01   Mode :logical  
 1st Qu.:2012-09-30   FALSE:51120    
 Median :2013-07-01   TRUE :1488     
 Mean   :2013-07-01                  
 3rd Qu.:2014-04-01                  
 Max.   :2014-12-31                  
vic_elec_2013 <- vic_elec |>
  filter(year(Time) == 2013) |>
  index_by(Date = as_date(Time)) |>
  summarise(
    Demand = sum(Demand) / 1000,
    Temperature = max(Temperature),
    Holiday = any(Holiday)
  ) |>
  mutate(
    Day_Type = if_else(
      Holiday | wday(Date) %in% c(1, 7),
      "Non-working day",
      "Working day"
    )
  )

The original dataset contains 48 half-hourly observations per day. I aggregated these observations into daily total electricity demand so that the overall annual pattern could be examined more clearly. Daily maximum temperature and holiday status were also retained for use as external predictors in the dynamic regression analysis.

glimpse(vic_elec_2013)
Rows: 365
Columns: 5
$ Date        <date> 2013-01-01, 2013-01-02, 2013-01-03, 2013-01-04, 2013-01-0…
$ Demand      <dbl> 175.9020, 195.6539, 242.8752, 294.3749, 220.5377, 196.4079…
$ Temperature <dbl> 24.5, 21.9, 36.7, 40.6, 34.7, 25.2, 29.3, 30.0, 20.2, 26.0…
$ Holiday     <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
$ Day_Type    <chr> "Non-working day", "Working day", "Working day", "Working …
summary(vic_elec_2013)
      Date                Demand       Temperature     Holiday       
 Min.   :2013-01-01   Min.   :168.2   Min.   :10.20   Mode :logical  
 1st Qu.:2013-04-02   1st Qu.:207.3   1st Qu.:16.30   FALSE:355      
 Median :2013-07-02   Median :224.0   Median :19.80   TRUE :10       
 Mean   :2013-07-02   Mean   :223.2   Mean   :21.13                  
 3rd Qu.:2013-10-01   3rd Qu.:241.8   3rd Qu.:24.40                  
 Max.   :2013-12-31   Max.   :311.4   Max.   :40.60                  
   Day_Type        
 Length:365        
 Class :character  
 Mode  :character  
                   
                   
                   
#Check for the missing data or NA

vic_elec_2013|>
  summarise(
    Missing_Demand=sum(is.na(Demand)),
    Missing_Temperature=sum(is.na(Temperature)),
    Missing_Holiday=sum(is.na(Holiday))
  )
# A tsibble: 365 x 4 [1D]
   Date       Missing_Demand Missing_Temperature Missing_Holiday
   <date>              <int>               <int>           <int>
 1 2013-01-01              0                   0               0
 2 2013-01-02              0                   0               0
 3 2013-01-03              0                   0               0
 4 2013-01-04              0                   0               0
 5 2013-01-05              0                   0               0
 6 2013-01-06              0                   0               0
 7 2013-01-07              0                   0               0
 8 2013-01-08              0                   0               0
 9 2013-01-09              0                   0               0
10 2013-01-10              0                   0               0
# ℹ 355 more rows

Part 1. ARIMA

Plot Time Series

vic_elec_2013 |>
  autoplot(Demand) +
  labs(
    title = "Daily Electricity Demand in Victoria, 2013",
    x = "Date",
    y = "Daily electricity demand (GWh)"
  )

The time-series plot shows several large spikes in electricity demand, particularly near the beginning and end of the year. Because these periods correspond to the Australian summer, the higher demand may be partly associated with increased air-conditioning use. However, electricity demand may also rise during colder periods because of heating, so temperature should be examined as a potentially nonlinear external predictor.

Original Series ACF and PACF

vic_elec_2013 |>
  gg_tsdisplay(
    Demand,
    plot_type = "partial",
    lag_max = 36
  ) +
  labs(title = "Daily Electricity Demand with ACF and PACF")

Although the original time-series plot appears to show higher electricity demand during the beginning and end of the year, this should not be interpreted as monthly seasonality. ACF analysis reveals strong autocorrelation at multiples of seven days rather than thirty days, indicating that the dominant seasonal pattern is weekly. The peaks observed during the summer months are more likely driven by changes in temperature than by a repeating monthly cycle. Therefore, a 7-day seasonal difference is more appropriate than a 30-day difference, while temperature can be incorporated later as an external predictor in the ARIMAX model.

First Difference

vic_elec_2013 |>
  gg_tsdisplay(
    difference(Demand),
    plot_type = "partial"
  )
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_point()`).

After applying the first difference, the series appears to fluctuate around a relatively constant mean with no obvious long-term trend, suggesting that the trend component has been largely removed. However, the ACF still shows strong significant spikes at lags 7, 14, 21, and 28, indicating that a clear weekly seasonal pattern remains.Therefore, an additional seasonal difference with a lag of 7 days may be required before fitting an ARIMA model.

Second Difference

vic_elec_2013 |>
  gg_tsdisplay(
    difference(Demand, lag = 7),
    plot_type = "partial"
  )
Warning: Removed 7 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 7 rows containing missing values or values outside the scale range
(`geom_point()`).

After applying a seasonal difference with a lag of 7 days, the weekly seasonal pattern has been substantially reduced. The differenced series fluctuates around a relatively constant mean without any obvious long-term trend, suggesting that the seasonal non-stationarity has largely been removed.

ARIMA Model

arima_models <- vic_elec_2013 |>
  model(
    Manual_ARIMA = ARIMA(
      Demand ~
        pdq(1, 0, 1) +
        PDQ(0, 1, 0, period = 7)
    ),
    Auto_ARIMA = ARIMA(Demand)
  )

arima_models
# A mable: 1 x 2
              Manual_ARIMA               Auto_ARIMA
                   <model>                  <model>
1 <ARIMA(1,0,1)(0,1,0)[7]> <ARIMA(2,0,0)(2,1,2)[7]>
report(arima_models)
Warning in report.mdl_df(arima_models): Model reporting is only supported for
individual models, so a glance will be shown. To see the report for a specific
model, use `select()` and `filter()` to identify a single model.
# A tibble: 2 × 8
  .model       sigma2 log_lik   AIC  AICc   BIC ar_roots   ma_roots  
  <chr>         <dbl>   <dbl> <dbl> <dbl> <dbl> <list>     <list>    
1 Manual_ARIMA   262.  -1504. 3014. 3014. 3026. <cpl [1]>  <cpl [1]> 
2 Auto_ARIMA     169.  -1429. 2872. 2873. 2900. <cpl [16]> <cpl [14]>

The manually specified ARIMA(1,0,1)(0,1,0)[7] model was chosen based on the ACF and PACF plots, which showed strong lag-1 autocorrelation and partial autocorrelation after seasonal differencing. However, the automatically selected ARIMA model achieved substantially lower AIC, AICc, and residual variance, indicating a much better fit to the data. This demonstrates that while ACF and PACF are useful for proposing candidate models, automatic model selection can identify more appropriate ARIMA structures by evaluating a wider range of parameter combinations.

Train/Test Split

train <- vic_elec_2013 |>
  filter(Date < as_date("2013-12-01"))

test <- vic_elec_2013 |>
  filter(Date >= as_date("2013-12-01"))
arima_models_train <- train |>
  model(
    Manual_ARIMA = ARIMA(
      Demand ~
        pdq(1, 0, 1) +
        PDQ(0, 1, 0, period = 7)
    ),
    
    Auto_ARIMA = ARIMA(Demand)
  )

arima_forecasts <- arima_models_train |>
  forecast(new_data = test)
arima_forecasts |>
  autoplot(vic_elec_2013) +
  labs(
    title = "ARIMA Forecasts for Daily Electricity Demand",
    subtitle = "Training period: January–November 2013; Test period: December 2013",
    x = "Date",
    y = "Daily electricity demand (GWh)"
  )

Accuracy Results

accuracy_results <- arima_forecasts |>
  accuracy(test) |>
  select(.model, RMSE, MAE, MAPE)

accuracy_results
# A tibble: 2 × 4
  .model        RMSE   MAE  MAPE
  <chr>        <dbl> <dbl> <dbl>
1 Auto_ARIMA    23.8  17.4  8.59
2 Manual_ARIMA  24.1  17.5  8.46

The winning Accuracy results of the model prediction was Auto ARIMA. All the accuracy scores of Auto Arima was higher than manual one. It was expected to have such results because of its metric that was already represented as AIC, AICc and BIC score.

Part 2. Dynamic Regression

vic_elec_2013|>
  ggplot(aes(x= Temperature, y= Demand))+
  geom_point()+
  geom_smooth(method="lm", formula = y~x+I(x^2))+labs(
     title = "Electricity Demand and Temperature",
    subtitle = "Quadratic relationship",
    x = "Daily Maximum Temperature",
    y = "Daily Electricity Demand (GWh)"
  )

As the temperature was going extreme value to 10 degree and 40 degree, the demand of the electricity has increased, so we can assume that there is a correlation of demand between temperature. Also a quadratic temperature term was included because electricity demand may increase during both extremely hot and relatively cold conditions.

Regression Model

dynamic_regression <- vic_elec_2013 |>
  model(
    TSLM = TSLM(
      Demand ~   I(Temperature^2) 
    )
  )

report(dynamic_regression)
Series: Demand 
Model: TSLM 

Residuals:
    Min      1Q  Median      3Q     Max 
-54.257 -14.096   1.035  18.842  75.347 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      2.155e+02  2.500e+00  86.193  < 2e-16 ***
I(Temperature^2) 1.589e-02  4.403e-03   3.608 0.000352 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 24.96 on 363 degrees of freedom
Multiple R-squared: 0.03462,    Adjusted R-squared: 0.03196
F-statistic: 13.02 on 1 and 363 DF, p-value: 0.00035197

Same as the expected relationship from the graph, there is a positive relationship of temperature and Demand. The hypothesis that the temperature is causing demand increase.

Diagnostics and Testing

dynamic_regression|>
  gg_tsresiduals()

dynamic_regression |>
  augment() |>
  features(
    .innov,
    ljung_box,
  )
# A tibble: 1 × 3
  .model lb_stat lb_pvalue
  <chr>    <dbl>     <dbl>
1 TSLM      137.         0

The residual diagnostics indicate that the dynamic regression model does not fully account for the autocorrelation in electricity demand. Therefore, an ARIMAX model is used to combine external predictors with errors, capturing the remaining temporal dependence and improving forecast performance.

ARIMAX model

models <- train |>
  model(
    TSLM = TSLM(
      Demand ~
        I(Temperature^2)+
        Temperature
    ),
    
    ARIMAX = ARIMA(
      Demand ~
        I(Temperature^2)+
        Temperature
    )
  )

forecast_models<- models|>
  forecast(new_data = test)

forecast_models
# A fable: 62 x 7 [1D]
# Key:     .model [2]
   .model Date      
   <chr>  <date>    
 1 TSLM   2013-12-01
 2 TSLM   2013-12-02
 3 TSLM   2013-12-03
 4 TSLM   2013-12-04
 5 TSLM   2013-12-05
 6 TSLM   2013-12-06
 7 TSLM   2013-12-07
 8 TSLM   2013-12-08
 9 TSLM   2013-12-09
10 TSLM   2013-12-10
# ℹ 52 more rows
# ℹ 5 more variables: Demand <dist>, .mean <dbl>, Temperature <dbl>,
#   Holiday <lgl>, Day_Type <chr>
forecast_models|>
  autoplot(vic_elec_2013)+
  labs(
    title= "Forecast Comparison",
    y="Daily Electricity Demand"
  )

Forecast Accuracy

accuracy_results <- forecast_models |>
  accuracy(test)

accuracy_results
# A tibble: 2 × 10
  .model .type     ME  RMSE   MAE   MPE  MAPE  MASE RMSSE  ACF1
  <chr>  <chr>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ARIMAX Test   -5.52  19.4  13.8 -3.36  7.00   NaN   NaN 0.710
2 TSLM   Test  -14.5   27.8  22.1 -8.15 11.4    NaN   NaN 0.434

The ARIMAX model achieved lower RMSE and MAE than the dynamic regression model. By incorporating ARIMA errors, the model successfully accounted for the residual autocorrelation that remained after fitting the regression model. As a result, ARIMAX produced more accurate forecasts while retaining the influence of external predictors such as temperature and day type.