Consider the the number of pigs slaughtered in Victoria, available in
the aus_livestock dataset.
ETS() smoothing. Find the optimal values of
function to estimate the equivalent model for simple exponential
smoothing. Find the optimal values of \(\alpha\) and \(l_0\), and generate forecasts for the next
four months.Answer: As shown below, the optimal values are \(\alpha = 0.32\) and \(l_0 = 1.01 \times 10^5\).
Explanation:
First, I visualized the time series.
# Select the data of interest
victoria_pigs <- aus_livestock %>%
filter(Animal == 'Pigs', State == 'Victoria')
# Plot the time series
victoria_pigs %>%
autoplot(Count) +
ggtitle('Pigs slaughtered in Victoria Australia, 1972-2018') +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold')
)
Then I applied simple exponential smoothing and determined \(\alpha\) and \(l_0\)
# Fit the model
slaughter_fit <- victoria_pigs %>%
model(ETS(Count ~ error('A') + trend('N') + season('N')))
# Convert results to dataframe
slaughter_fit %>% tidy()
## # A tibble: 2 × 5
## Animal State .model term estimate
## <fct> <fct> <chr> <chr> <dbl>
## 1 Pigs Victoria "ETS(Count ~ error(\"A\") + trend(\"N\") + sea… alpha 3.22e-1
## 2 Pigs Victoria "ETS(Count ~ error(\"A\") + trend(\"N\") + sea… l[0] 1.01e+5
The forecast is shown below
# Generate forecast for 4 months in future
slaughter_forecast <- slaughter_fit %>%
forecast(h = 4)
# Plot forecast with historical data
slaughter_forecast %>%
autoplot(victoria_pigs) +
autolayer(
.vars = Count,
filter_index(victoria_pigs, '2019 Jan' ~ .)
) +
# Only plot subset of data so the forecast is visible
coord_cartesian(xlim = c(as.Date('2010-01-01'), as.Date('2020-01-01')),
ylim = c(40000, 120000)) +
# Aesthetics
labs(
title = 'Pigs slaughtered in Victoria Australia, 2010-2018 + 4-Month Forecast',
subtitle = 'Simple exponential smoothing',
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold')
)
b. Compute a 95% prediction interval for the first forecast using \(\hat{y} \pm 1.96s\) where \(s\) is the standard deviation of the residuals. Compare your interval with the interval produced by R.
Answer: The prediction intervals are very close. However, the prediction interval produced by R is more accurate because it accounts for changes in the forecast variance over time.
Explanation:
Prediction interval for first forecast using standard deviation of residuals
# Calculate standard deviation of residuals
slaughter_forecast_e <- augment(slaughter_fit)
slaughter_forecast_sd <- sd(slaughter_forecast_e$.resid)
# Calculate the 95% prediction interval for the first forecast
y_hat <- slaughter_forecast$.mean[1]
lower_bound95 <- y_hat - 1.96 * slaughter_forecast_sd
upper_bound95 <- y_hat + 1.96 * slaughter_forecast_sd
# Round bounds to nearest whole animal
sprintf('The calculated 95%% prediction interval is (%d, %d)',
floor(lower_bound95), ceiling(upper_bound95))
## [1] "The calculated 95% prediction interval is (76871, 113503)"
The 95% prediction interval produced by R is similar.
Note: Hyndman section 8.7 presents formulas for forecast variance,
which are used to calculate prediction intervals; however, the text does
not show the R function that is used to calculate the prediction
interval. After searching online, I found that the hilo()
function in the distributional package reports a 95%
prediction interval.
hilo(slaughter_forecast) %>%
as.data.frame() %>%
select(`95%`) %>%
head(n = 1) %>%
unlist()
## 95%.lower 95%.upper 95%.level
## 76854.79 113518.33 95.00
Data set global_economy contains the annual Exports from
many countries. Select one country to analyse.
The plot below shows that exports in the United States (as percentage of GDP) trended upward from 1960 to 2017. There is no seasonality. There is some evidence of cyclical behavior every 15 years or so (eg, 1970-1985).
# Select the data of interest
us_economy <- global_economy %>%
filter(Country == 'United States') %>%
drop_na(Exports)
# Plot the time series
us_economy %>%
autoplot(Exports) +
xlim(1960, 2020) +
labs(
y = '% of GDP',
title = 'US Exports, 1960-2017',
caption = 'GDP, gross domestic product.'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
plot.caption = element_text(color = 'darkgray', hjust = 0)
)
b. Use an ETS(A,N,N) model to forecast the series, and plot the forecasts.
# Fit the model
us_exports_fit <- us_economy %>%
model(ETS(Exports ~ error('A') + trend('N') + season('N')))
# Generate forecast for 3 years in future
us_exports_forecast <- us_exports_fit %>%
forecast(h = 3)
# Plot forecast with historical data
us_exports_forecast %>%
autoplot(us_economy) +
autolayer(
.vars = Exports,
filter_index(us_economy, '2018' ~ .)
) +
# Only plot subset of data so the forecast is visible
coord_cartesian(xlim = c(1990, 2020)) +
labs(
y = '% of GDP',
title = 'US Exports, 1990-2017 + 3-Year Forecast',
subtitle = 'Simple exponential smoothing',
caption = 'GDP, gross domestic product.'
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
plot.caption = element_text(color = 'darkgray', hjust = 0)
)
c. Compute the RMSE values for the training data.
Answer: The RMSE is 0.627.
accuracy(us_exports_fit)$RMSE
## [1] 0.6270672
d. Compare the results to those from an ETS(A,A,N) model. (Remember that the trended model is using one more parameter than the simpler model.) Discuss the merits of the two forecasting methods for this data set.
e. Compare the forecasts from both methods. Which do you think is best?
Answer: The ETS(A,A,N) model includes an additive trend component whereas the ETS(A,N,N) model does not have a trend component. As a result, the forecast from the ETS(A,A,N) model follows the trend of the time series data (ie, upward), whereas the forecast from the ETS(A,N,N) model is flat. Based on the forecast plot, I would say that the ETS(A,A,N) model is better because it follows the overall trend of the historical data. This conclusion is supported by the lower RMSE of the ETS(A,A,N) model compared with the ETS(A,N,N) model (0.61 vs 0.63).
Explanation:
# Fit the models
us_exports_fit2 <- us_economy %>%
model(ANN = ETS(Exports ~ error('A') + trend('N') + season('N')),
AAN = ETS(Exports ~ error('A') + trend('A') + season('N')))
# Generate forecasts for 3 years in future
h <- 3
us_exports_forecast2 <- us_exports_fit2 %>%
forecast(h = h)
# Plot forecast with historical data
us_exports_forecast2 %>%
autoplot(us_economy, level = NULL) +
autolayer(
.vars = Exports,
filter_index(us_economy, '2018' ~ .)
) +
# Only plot subset of data so the forecast is visible
coord_cartesian(xlim = c(1990, 2020)) +
labs(
y = '% of GDP',
title = 'US Exports, 1990-2017 + 3-Year Forecast',
subtitle = 'Simple exponential smoothing',
color = 'model',
caption = 'GDP, gross domestic product.'
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
plot.caption = element_text(color = 'darkgray', hjust = 0)
)
# Compare RMSE values
accuracy(us_exports_fit2) %>%
select(`.model`, RMSE)
## # A tibble: 2 × 2
## .model RMSE
## <chr> <dbl>
## 1 ANN 0.627
## 2 AAN 0.615
f. Calculate a 95% prediction interval for the first forecast for each model, using the RMSE values and assuming normal errors. Compare your intervals with those produced using R.
Answer: Similar to Exercise 8.1 part (b), the 95%
prediction intervals calculated from residuals
(calc_lower95 and calc_upper95) versus those
produced by R (hilo_lower95 and hilo_upper95)
are very close for both models.
Explanation:
# Calculate standard deviation of residuals
us_exports_forecast_e <- augment(us_exports_fit2)
us_exports_forecast_sd <- sd(us_exports_forecast_e$.resid)
# 95% prediction intervals for the first forecast for each model
us_exports_forecast2 %>%
mutate(
# Bounds of interval calculated from residuals
calc_lower95 = .mean - 1.96 * us_exports_forecast_sd,
calc_upper95 = .mean + 1.96 * us_exports_forecast_sd,
# Bounds of interval produced by hilo() function
hilo_interval95 = hilo(Exports, 95),
hilo_lower95 = hilo_interval95$lower,
hilo_upper95 = hilo_interval95$upper
) %>%
as.data.frame() %>%
# h is length of the forecast (defined in part (e)), so
# h+1 is the row number for the first forecast of the second model
slice(1, h+1) %>%
select(model = `.model`, calc_lower95, calc_upper95, hilo_lower95, hilo_upper95)
## model calc_lower95 calc_upper95 hilo_lower95 hilo_upper95
## 1 ANN 10.67539 13.10597 10.63951 13.14186
## 2 AAN 10.79132 13.22190 10.75667 13.25656
Forecast the Chinese GDP from the global_economy data
set using an ETS model. Experiment with the various options in the
ETS() function to see how much the forecasts change with
damped trend, or with a Box-Cox transformation. Try to develop an
intuition of what each is doing to the forecasts.
Answer: Simple exponential smoothing (SES) produces a flat forecast, while the Holt method produces a linear forecast with a positive slope. Box-Cox transformation produces an exponentially increasing forecast, which is consistent with the log transformation and subsequent back-transformation. As the term “dampening” suggests, the dampened Holt and Box-Cox methods produce forecasts that are less extreme (relative to the un-dampened versions), particular for long-range forecasts. The dampening effect is more pronounced for the Box-Cox forecast than the Holt forecast.
Comparison of the RMSEs for these forecasts indicates that the Holt, dampened Holt, and dampened Box-Cox models have a better fit with the historical data than the SES or un-dampened Box-Cox models.
Explanation:
The time series plot shows that the GDP of mainland China trended upward (exponentially) from 1960 to 2017.
# Select the data of interest
# The global_economy dataset for China includes data for Hong Kong and Macao
# Below, I filter for 'China', which is only mainland China
china_gdp <- global_economy %>%
filter(Country == 'China') %>%
mutate(
GDP = GDP / 1e12 # Express GDP in trillions
)
# Plot the time series
china_gdp %>%
autoplot(GDP) +
xlim(1960, 2020) +
labs(
y = 'GDP (USD, trillion)',
title = 'GDP of Mainland China, 1960-2017',
caption = 'GDP, gross domestic product; USD, US dollar.'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
plot.caption = element_text(color = 'darkgray', hjust = 0)
)
The optimal lambda value \(\lambda = -0.03\) for Box-Cox transformation is near zero, which suggests a log transformation.
# Calculate optimal value of lambda for Box-Cox transformation using Guerrero method
china_GDP_lambda <- china_gdp %>%
features(GDP, features = guerrero) %>%
pull(lambda_guerrero)
china_GDP_lambda
## [1] -0.03446284
# Fit various models
china_gdp_fit <- china_gdp %>%
model(
# simple exponential smoothing
SES = ETS(GDP ~ error('A') + trend('N') + season('N')),
# Holt's linear
Holt = ETS(GDP ~ error('A') + trend('A') + season('N')),
# Additive damped trend
Holt_damped = ETS(GDP ~ error('A') + trend('Ad') + season('N')),
# Box-Cox transformation
BoxCox = ETS(box_cox(GDP, china_GDP_lambda)),
# Box-Cox + dampening
BoxCox_damped = ETS(box_cox(GDP, china_GDP_lambda) ~ error('A') + trend('Ad') + season('N'))
)
# Generate forecast for 10 years in future
china_gdp_forecast <- china_gdp_fit %>%
forecast(h = 10)
# Plot forecasts with historical data
china_gdp_forecast %>%
autoplot(china_gdp, level = NULL) +
autolayer(
.vars = GDP,
filter_index(china_gdp, '2018' ~ .)
) +
# Only plot subset of data so the forecast is visible
coord_cartesian(xlim = c(2000, 2030)) +
labs(
y = 'GDP (USD, trillion)',
title = 'GDP of Mainland China, 2000-2017 + 10-Year Forecast',
color = 'model',
caption = 'GDP, gross domestic product; USD, US dollar.'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
plot.caption = element_text(color = 'darkgray', hjust = 0)
)
accuracy(china_gdp_fit) %>%
select(`.model`, RMSE)
## # A tibble: 5 × 2
## .model RMSE
## <chr> <dbl>
## 1 SES 0.416
## 2 Holt 0.190
## 3 Holt_damped 0.190
## 4 BoxCox 0.292
## 5 BoxCox_damped 0.206
Find an ETS model for the Gas data from aus_production
and forecast the next few years. Why is multiplicative seasonality
necessary here? Experiment with making the trend damped. Does it improve
the forecasts?
Answer: Multiplicative seasonality is needed because the seasonal variations change proportional to the level of the series. Comparison of the plots below shows that dampening does not have any effect on the forecast. The similarity is consistent with the estimated dampening parameter \(\phi = 0.98\) , which means that there is little dampening (because it is close to 1).
Explanation:
The time series plot below shows that gas production in Australia has trended upward since 1970 and that seasonal fluctuations in production vary with the level of the series.
# Plot the time series
aus_production %>%
autoplot(Gas) +
labs(
y = 'Production, petajoules',
title = 'Gas Production in Australia, 1956-2010'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold')
)
# Fit models
aus_gas_fit <- aus_production %>%
model(
# multiplicative
Holt = ETS(Gas ~ error('M') + trend('A') + season('M')),
# multiplicative + dampening
Holt_damped = ETS(Gas ~ error('M') + trend('Ad') + season('M'))
)
# Generate forecast for 12 quarters (3 years) in future
aus_gas_forecast <- aus_gas_fit %>%
forecast(h = 12)
# Plot forecasts with historical data
aus_gas_forecast %>%
autoplot(aus_production, level = NULL) +
autolayer(
.vars = Gas,
filter_index(aus_production, '2010 Q3' ~ .)
) +
facet_grid(.model ~ .) +
labs(
y = 'Production, petajoules',
title = 'Gas Production in Australia, 1956-2010 + 3-Year Forecast',
color = 'model'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
legend.position = 'none'
)
In the dampened model, the estimated dampening parameter \(\phi = 0.98\) is close to 1, which means that there is very little dampening.
aus_gas_fit %>%
tidy() %>%
filter(term == 'phi')
## # A tibble: 1 × 3
## .model term estimate
## <chr> <chr> <dbl>
## 1 Holt_damped phi 0.980
Recall your retail time series data (from Exercise 7 in Section 2.10).
# Generate the same series used previously
set.seed(144)
retail_ts <- aus_retail %>%
filter(`Series ID` == sample(aus_retail$`Series ID` , 1))
Why is multiplicative seasonality necessary for this series?
Answer: Multiplicative seasonality is needed because the seasonal variations in retail turnover change proportional to the level of the series.
# Plot the time series
retail_ts %>%
autoplot(Turnover) +
labs(
y = 'Turnover (AUD, million)',
title = 'Retail turnover in Australia, 1982-2018'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold')
)
Apply Holt -Winters’ multiplicative method to the data. Experiment with making the trend damped.
Answer: The plots below shows that dampening does not affect the forecast. The similarity is consistent with the estimated dampening parameter \(\phi = 0.98\), which means that there is little dampening.
# Fit models
aus_retail_fit <- retail_ts %>%
model(
# multiplicative
Holt = ETS(Turnover ~ error('M') + trend('A') + season('M')),
# Multiplicative damped
Holt_damped = ETS(Turnover ~ error('M') + trend('Ad') + season('M'))
)
# Generate forecast for 36 months (3 years) in future
aus_retail_forecast <- aus_retail_fit %>%
forecast(h = 36)
# Plot forecasts with historical data
aus_retail_forecast %>%
autoplot(retail_ts, level = NULL) +
autolayer(
.vars = Turnover,
filter_index(retail_ts, '2019 Jan' ~ .)
) +
facet_grid(.model ~ .) +
labs(
y = 'Turnover (AUD, million)',
title = 'Retail turnover in Australia, 1982-2018 + 3-Year Forecast',
color = 'model'
) +
guides(
x = guide_axis(minor.ticks = TRUE)
) +
theme_classic() +
theme(
axis.title = element_text(face = 'bold'),
plot.title = element_text(face = 'bold'),
legend.position = 'none'
)
aus_retail_fit %>%
tidy() %>%
filter(term == 'phi') %>%
select(`.model`, term, estimate)
## # A tibble: 1 × 3
## .model term estimate
## <chr> <chr> <dbl>
## 1 Holt_damped phi 0.980Compare the RMSE of the one-step forecasts from the two methods. Which do you prefer?
Answer: On the basis of lower RMSE, the dampened forecast is slightly better; however, given that its dampening parameter \(\phi\) is close to 1, the additional complexity of the model may not be justified. So, if I had to choose one model, I’d choose the un-dampened Holt model.
accuracy(aus_retail_fit) %>%
select(`.model`, RMSE)
## # A tibble: 2 × 2
## .model RMSE
## <chr> <dbl>
## 1 Holt 2.95
## 2 Holt_damped 2.93
Check that the residuals from the best method look like white noise.
Answer: The residuals from the un-dampened Holt model do not appear to be white noise. This means that the forecasting method could be improved.
Explanation: The time series plot shows some dependence on the level of the series, although magnitudes are small. In addition, the autocorrelation plot was not characteristic of white noise, which would be expected to have a single significant spike at lag=0. However, the data distribution (histogram) appears to be approximately normal with mean close to 0. The failure of two of the three residual diagnostics was also consistent with significant P values from Box-Pierce and Ljung-Box tests. Collectively, these results suggest that the residuals are not white noise.
# Residual plots for Holt model (un-dampened)
retail_ts %>%
model(Holt = ETS(Turnover ~ error('M') + trend('A') + season('M'))) %>%
gg_tsresiduals()
For Box-Pierce and Ljung-Box tests, the time series is seasonal with a monthly period, so \(l = 2m = 2 \times 12 = 24\).
Both tests had significant P-values (P<0.05), so the null hypothesis that the residuals are from a white noise series is rejected.
holt_model_residuals <- retail_ts %>%
model(Holt = ETS(Turnover ~ error('M') + trend('A') + season('M'))) %>%
residuals()
Box.test(holt_model_residuals$.resid, lag = 24, type = 'Box-Pierce')
##
## Box-Pierce test
##
## data: holt_model_residuals$.resid
## X-squared = 377.75, df = 24, p-value < 2.2e-16
Box.test(holt_model_residuals$.resid, lag = 24, type = 'Ljung-Box')
##
## Box-Ljung test
##
## data: holt_model_residuals$.resid
## X-squared = 391.27, df = 24, p-value < 2.2e-16Now find the test set RMSE, while training the model to the end of 2010. Can you beat the seasonal naïve approach from Exercise 7 in Section 5.11?
Answer: Yes. On the basis of lower RMSE, the ETS model forecast is better than the seasonal naive model forecast (14.7 vs 29.0).
# Create training and test datasets with chronological split
aus_retail_train <- retail_ts %>%
filter(year(Month) < 2011)
aus_retail_test <- retail_ts %>%
filter(year(Month) >= 2011)
# ETS model
# Fit training dataset
aus_retail_ets_fit <- aus_retail_train %>%
model(ETS = ETS(Turnover ~ error('M') + trend('A') + season('M')))
# Generate forecasts using test dataset
aus_retail_ets_forecast <- forecast(aus_retail_ets_fit,
new_data = aus_retail_test)
# Assess performance
accuracy(aus_retail_ets_forecast, aus_retail_test) %>%
select(model = `.model`, RMSE)
## # A tibble: 1 × 2
## model RMSE
## <chr> <dbl>
## 1 ETS 14.7
# Seasonal naive model
# Fit training dataset
aus_retail_snaive_fit <- aus_retail_train %>%
model(seasonal_naive = SNAIVE(Turnover))
# Generate forecasts using test dataset
aus_retail_snaive_forecast <- forecast(aus_retail_snaive_fit,
new_data = aus_retail_test)
# Assess performance
accuracy(aus_retail_snaive_forecast, aus_retail_test) %>%
select(model = `.model`, RMSE)
## # A tibble: 1 × 2
## model RMSE
## <chr> <dbl>
## 1 seasonal_naive 29.0For the same retail data, try an STL decomposition applied to the Box-Cox transformed series, followed by ETS on the seasonally adjusted data. How does that compare with your best previous forecasts on the test set?
Answer: STL decomposition of Box-Cox transformed data worsened the ETS model forecast using the test dataset, increasing RMSE from 14.7 in Exercise 8.8 part (e) to 19.2.
The reduced performance appears to be due to the data transformation; without transformation, the RMSE decreases to 9.8, which is 33% less than that from the ETS model from the previous exercise (14.7). This is consistent with the expectation that removal of seasonality enables the ETS model to better identify the long-term trend and make a more accurate forecast. Box-Cox transformation can be helpful, but it is not guaranteed to improve performance.
# Calculate lambda for Box-Cox transformation using training dataset
aus_retail_lambda <- aus_retail_train %>%
features(Turnover, features = guerrero) %>%
pull(lambda_guerrero)
# First apply STL decomposition to transformed training data,
# then use ETS model to fit seasonally adjust component ('season_adjust')
aus_retail_fit2 <- aus_retail_train %>%
model(
STL = decomposition_model(
STL(box_cox(Turnover, aus_retail_lambda)),
# Seasonally adjusted data no longer has seasonality
ETS(season_adjust ~ error('A') + trend('A') + season('N'))
)
)
# Forecast using test data
# Note: I specify the forecast() function from the fabletools package rather than
# using the version in R generics to ensure forecasts are back transformed
# (see Hyndman section 5.2)
aus_retail_ets_forecast2 <- fabletools::forecast(aus_retail_fit2, new_data = aus_retail_test)
# Assess model performance
accuracy(aus_retail_ets_forecast2, aus_retail_test) %>%
select(model = `.model`, RMSE)
## # A tibble: 1 × 2
## model RMSE
## <chr> <dbl>
## 1 STL 14.9
In contrast, a model without Box-Cox transformation has lower RMSE.
# Apply STL decomposition to training data (untransformed),
# then use ETS model to fit seasonally adjust component ('season_adjust')
aus_retail_fit3 <- aus_retail_train %>%
model(
STL = decomposition_model(
STL(Turnover),
ETS(season_adjust ~ error('A') + trend('A') + season('N'))
)
)
# Forecast using test data
aus_retail_ets_forecast3 <- fabletools::forecast(aus_retail_fit3, new_data = aus_retail_test)
# Assess model performance
accuracy(aus_retail_ets_forecast3, aus_retail_test) %>%
select(model = `.model`, RMSE)
## # A tibble: 1 × 2
## model RMSE
## <chr> <dbl>
## 1 STL 9.82
sessionInfo()
## R version 4.5.2 (2025-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.2
##
## Matrix products: default
## BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/New_York
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] distributional_0.6.0 fable_0.5.0 ggtime_0.1.0
## [4] forecast_9.0.0 feasts_0.4.2 fabletools_0.5.1
## [7] tsibbledata_0.4.1 tsibble_1.1.6 lubridate_1.9.4
## [10] forcats_1.0.1 stringr_1.6.0 dplyr_1.1.4
## [13] purrr_1.2.1 readr_2.1.6 tidyr_1.3.2
## [16] tibble_3.3.1 ggplot2_4.0.1 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 anytime_0.3.12 xfun_0.56
## [4] bslib_0.10.0 lattice_0.22-7 numDeriv_2016.8-1.1
## [7] tzdb_0.5.0 quadprog_1.5-8 vctrs_0.7.1
## [10] tools_4.5.2 generics_0.1.4 curl_7.0.0
## [13] parallel_4.5.2 xts_0.14.1 pkgconfig_2.0.3
## [16] RColorBrewer_1.1-3 S7_0.2.1 lifecycle_1.0.5
## [19] compiler_4.5.2 farver_2.1.2 htmltools_0.5.9
## [22] sass_0.4.10 yaml_2.3.12 pillar_1.11.1
## [25] jquerylib_0.1.4 ellipsis_0.3.2 cachem_1.1.0
## [28] nlme_3.1-168 fracdiff_1.5-3 tidyselect_1.2.1
## [31] digest_0.6.39 stringi_1.8.7 labeling_0.4.3
## [34] tseries_0.10-60 fastmap_1.2.0 grid_4.5.2
## [37] colorspace_2.1-2 cli_3.6.5 magrittr_2.0.4
## [40] utf8_1.2.6 withr_3.0.2 scales_1.4.0
## [43] rappdirs_0.3.4 timechange_0.3.0 TTR_0.24.4
## [46] rmarkdown_2.30 quantmod_0.4.28 otel_0.2.0
## [49] nnet_7.3-20 timeDate_4052.112 progressr_0.18.0
## [52] zoo_1.8-15 hms_1.1.4 urca_1.3-4
## [55] evaluate_1.0.5 knitr_1.51 ggdist_3.3.3
## [58] lmtest_0.9-40 rlang_1.1.7 Rcpp_1.1.1
## [61] glue_1.8.0 rstudioapi_0.18.0 jsonlite_2.0.0
## [64] R6_2.6.1