Time Series Analysis of U.S. Monthly Tourist Arrivals and Spending
Author
Anthony Le
Published
October 27, 2024
I. Introduction
The economic impact of international tourism in the United States is substantial, with global travelers contributing billions annually to the U.S. economy. Before the COVID-19 pandemic, international visitors spent approximately $233.5 billion in 2019 alone, which averages to nearly $640 million daily (International Trade Administration, n.d.). With U.S. tourism accounting for 14.5% of total global spending on international travel (Zaheer, 2023), understanding the trends and drivers behind these expenditures is vital for sustaining this economic sector.
While tourist spending is influenced by various socioeconomic factors, one of the most reliable indicators of such expenditures is the volume of international arrivals, commonly referred to as “Tourist Arrivals.” Fluctuations in arrivals often correlate closely with factors like currency exchange rates, seasonal trends, and global geopolitical stability (Hinton, 2024). In an attempt to explore the dynamics between tourist arrivals and spending, this project seeks to find the best predictive models of both metrics. Leveraging upon the function of time and SARIMA models, this study aims to provide a robust and comprehensive understanding of U.S. tourism’s economic contributions, as well as a tool for predicting future trends in international arrivals and spending.
II. Methods
1. Working Data
a. Data Acquisition
Show the code
Arrivals <-read.csv("Monthly_Tourist_Arrivals_R.csv")Exports <-read.csv("Monthly_Exports_R.csv")Arrivals$Total <-as.numeric(Arrivals$Total)Arrivals$Time <-floor_date(as.Date(Arrivals$Time), "month")Exports$Time <-as.Date(Exports$Time)Masterdata0 <-merge(Arrivals, Exports, by ="Time", all =TRUE)
For this project, we sourced two datasets from the International Trade Administration (ITA) website, both available in Excel format. The first dataset, “Monthly Exports Imports Balance,” was retrieved from the ITA’s International Travel Receipts and Payments Program. It provides monthly tourism-related trade data from January 1999 to July 2024, including metrics like total travel exports (receipts, or tourist spending) and imports (payments, or US citizen spending abroad), which are further subdivided into categories such as Travel Spending, Medical/ Education/ Workers Spending, and Passenger Fare Receipts. All figures are in millions of dollars and are seasonally adjusted.
The second dataset, “Monthly Arrivals 2000 to Present – Country of Residence (COR),” was acquired from the ITA’s I-94 Arrivals Program. This file contains monthly data on the number of tourists arriving in the U.S. from January 2000 to July 2024, with a breakdown by world region (e.g., Central America, Western Europe) and individual countries of residence. This dataset also includes year-over-year changes for each category. No specific filters or exclusions were applied to the data before downloading.
b. Potential Biases
The datasets used in this study inherently exclude certain categories of visitors. For instance, the International Visitor Arrivals Program - ADIS I-94 does not count travelers who do not stay overnight in the U.S. or those who visit on certain visa types. This exclusion may introduce some problems, as short-term visitors, particularly those near borders or in airports, may still contribute significantly to tourism-related spending but are not included in official tourist arrival figures. Another potential bias is related to the seasonality of tourist arrivals, which can be influenced by holidays and business cycles. Significant anomalies like the COVID-19 pandemic have also impacted arrival and spending patterns.
c. Data Wrangling
To facilitate our analysis, we simplified both datasets by focusing on aggregated monthly data. For the “Monthly Exports Imports Balance” file, we retained only the total tourism-related exports (tourist spending). Similarly, for the “Monthly Arrivals” file, we retained only the total arrivals and subtotal information by world region, removing individual country data to streamline our analysis. The figure below shows a quick comparison of the two main time series remaining.
Given the unique impact of COVID-19 on travel and spending, we intentionally excluded data post-January 2020. This choice allows us to analyze trends under more typical conditions, thus providing a more stable basis for examining seasonal and trend-based patterns in tourist arrivals and spending from 2000 to 2019. The figure below illustrates such exclusion of data.
Show the code
Masterdata <-Masterdata0 %>%filter(Time <as.Date("2020-01-01"))Arrivals_ts =ts(Masterdata$Total,start=c(2000,1), frequency =12)Exports_ts =ts(Masterdata$Exports,start=c(2000,1), frequency =12)tstools::tsplot(list("Arrivals (millions), left axis"=Arrivals_ts/1000000),tsr =list("Exports (billions $), right axis"=Exports_ts/1000), theme =init_tsplot_theme(line_colors =c("darkblue", "darkred")),plot_title ="US Monthly Tourist Arrivals & Spending (2000-2019)")
2. Time Series Analysis
a. Assumption and Validation
To conduct a rigorous statistical analysis of U.S. monthly tourist arrivals and spending, we assume that each account in our data is independent of one another and meets all criteria described by the International Trade Administration for their corresponding reporting data. The following paragraphs outline our general approach to finding the best models for our data.
Before fitting any model, we will visualize the data with a time series plot and compute the Autocorrelation Function (ACF) and Partial Autocorrelation Function (PACF) plots to understand the underlying patterns if appropriate. We will then perform several stationarity tests, including the Augmented Dickey-Fuller (ADF) test, Phillips-Perron (PP) test, Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test, and the Elliott-Rothenberg-Stock (ERS) test. More information on these tests and beyond will be available in the Appendix.
After fitting the models, we will validate their effectiveness by plotting the fitted values with the original data and then analyzing the residuals. We will check for independence, normality, and stationarity of the residuals, employing tests such as the Shapiro-Wilk, Anderson-Darling, Jarque-Bera, and Lilliefors tests to ensure they follow a normal distribution. Additionally, ACF and density plots, Box-Ljung test, and other stationarity tests will inform us about potential issues with correlation and stationarity. In general, if the residuals are stationary, independent, and normally distributed, the model’s predictions can be deemed reliable.
When comparing models, we will utilize the Akaike Information Criterion (AIC), Bayesian Information Criterion (BIC), and R-squared values (for linear models). A lower AIC and BIC, along with a higher R-squared value, indicate a better-fitting model. If residuals are stationary, we may enhance the model by incorporating additional terms to improve its predictive capability.
For validation, we will implement a cross-validation approach by excluding data from 2019, training our models with the available data up to 2018, and subsequently comparing the model’s predictions against actual arrivals and spending for that year. Finally, we will forecast tourist arrivals and spending for two years into the future, assuming the absence of COVID-19-related disruptions.
b. Function of Time Models
In the event of identifying non-stationarity due to trends within the time series, we will apply function of time models to detrend the data. Specifically, we will utilize regression techniques to fit linear, quadratic, sinusoidal, and periodic trends with seasonal means. By identifying the appropriate model that best captures these trends, we will aim to derive residuals that should exhibit stationary behavior—constant variance and no serial correlation. Within this regard, we will also utilize decomposition techniques and plot accordingly to compare with our function of time models.
c. SARIMA Models
Seasonal Autoregressive Integrated Moving Average (SARIMA) models will be employed to account for seasonal behavior in the data. SARIMA models are denoted as SARIMA(p, d, q) × (P, D, Q) [s], where: p: Order of the autoregressive component d: Degree of differencing q: Order of the moving average component P: Seasonal autoregressive order D: Seasonal differencing order Q: Seasonal moving average order s: Length of the seasonal cycle
Prior to fitting a SARIMA model, we will first difference the data (normally and/or seasonally) as necessary to ensure stationarity. Different models will be fitted (based on ACF and PACF plots), validated by series of statistical tests, and analyzed in terms of residuals to confirm that they are stationary and uncorrelated. If the residuals follow a normal distribution, we can confidently utilize the model for forecasting future values.
d. Special Functions
To facilitate this analysis, I have self-defined new functions in R to streamline various processes, including:
check_stationarity(): This function performs stationarity tests (ADF, PP, KPSS, ERS) and summarizes the results in a user-friendly table.
four_plots() and two_plots(): These functions generate ACF and PACF plots for different segments of the data to visually assess autocorrelation.
Show the code
# ACF/PACF/First-Last Third Plottingfour_plots <-function(data) { n <-length(data) first_third <- data[1:floor(n /3)] last_third <- data[floor(2* n /3):n]par(mfrow =c(2, 2))acf(data, main ="ACF of Whole Data", lag.max =30)pacf(data, main ="PACF of Whole Data", lag.max =30)acf(first_third, main ="ACF of First Third")acf(last_third, main ="ACF of Last Third")par(mfrow =c(1, 1))}# ACF/PACF Plottingtwo_plots <-function(data) {par(mfrow =c(1, 2))acf(data, main ="ACF of Whole Data", lag.max =50)pacf(data, main ="PACF of Whole Data", lag.max =50)par(mfrow =c(1, 1))}# Differencing Plottingdiff_plots <-function(data, name) {layout(matrix(c(1,1,2,3), 2, 2, byrow =TRUE)) astsa::tsplot(data, main= name)abline(h =mean(data, na.rm =TRUE), col ="red", lty =2)acf(data, lag.max =30)pacf(data, lag.max =30)par(mfrow =c(1,1))}
model_summary_table() and sarima_summary_table( ): These functions summarize the key metrics of fitted models, including R^2 , AIC and BIC values, to facilitate comparison.
check_normality_residuals(): This function evaluates the residuals from fitted models through a series of different tests to ensure they conform to normality assumptions.
calculate_r2 <-function(predicted_values, actual_values) { ss_residual <-sum((actual_values - predicted_values)^2) ss_total <-sum((actual_values -mean(actual_values))^2) r_squared <-1- (ss_residual / ss_total)cat("R-squared of the model is:", round(r_squared, 4), "\n")}
These functions enhance the analysis process by providing clarity and efficiency. Each function’s details and the tests used are elaborated upon in the appendix.
III. Results & Conclusions
1. Descriptive Analysis
a. Tourist Arrivals
U.S. monthly tourist arrivals showed a notable increase over the observed period, beginning with 2,866,229 visitors in January 2000 and reaching 6,899,661 by July 2024, indicating an overall upward trend. The plot below illustrates the monthly arrival data from 2000 to July 2024, segmented by different world regions as well as the total aggregate. The mean monthly arrivals were approximately 4,709,139, with a median of 4,817,910. Notably, Western Europe accounted for an average of 902,992 monthly arrivals, Asia for 618,172, and South America for 301,831.
The lowest recorded arrivals occurred in April 2020, when the number of visitors dropped sharply to 248,486, corresponding to the onset of U.S. border closures and social distancing measures due to the COVID-19 pandemic (Gonzalez-Barrera, 2020). Conversely, the highest number of monthly arrivals was recorded in August 2014, with 8,418,370 visitors.
Seasonal patterns are evident in the plot above, with clear peaks and troughs repeating annually. The plot below signifies that the summer months of July and August consistently show the highest arrivals, while January and February experience the lowest. This trend aligns with travel behavior, as summer weather is more favorable for tourism, and winter months see a decrease in travel as people return to work or school after the holidays. Additionally, colder weather and snow in northern regions may deter travel during these months.
Monthly tourist spending, or “Tourist Exports,” also shows a marked increase, starting at 9.2 billion dollars in January 2000 and reaching 21.1 billion dollars in July 2024. The mean monthly spending over this period was 13.7 billion dollars, with a median of 13.1 billion dollars. The lowest recorded spending occurred in September 2020, at 3.7 billion dollars, while the highest was in May 2024, with 21.66 billion dollars. However, it is noteworthy that this dataset is seasonally adjusted, which means that nominal spending figures may vary; for instance, the lowest nominal spending month may have occurred earlier in 2020 when the tourism sector was effectively shut down.
For additional context on seasonal adjustments, please refer to the Appendix, where the seasonal adjustment methodology is outlined.
The table below compares statistical summaries of monthly tourist arrivals and exports for the entire period (2000-2024) versus the period excluding data from January 2020 onward.
In total, we evaluated five different models to capture trends in the total arrivals time series, incorporating linear, quadratic, seasonal, and sinusoidal components. In order to mitigate the multicollinearity problems associated with VIF, we intentionally used the centered time object in all of our models, if appropriate.
In this model, we centered the time variable and fit a linear trend to arrivals. The output reveals a significant upward trend, with an estimated increase of 215,025 arrivals per time unit (p < 0.05) and an intercept of 4,958,137. Residuals are centered and show a balanced spread. An adjusted R-squared of 0.7164 indicates that 71.64% of the variance in arrivals is explained by time, and a highly significant F-statistic (604.7, p < 0.05) supports the model’s strength and the importance of time in explaining arrival trends. While the residuals appear stationary, they are not independent and do not follow a normal distribution.
Show the code
t <-time(Arrivals_ts)t_centered <- t -mean(t)# Linearar_lm <-lm(Arrivals_ts ~ t_centered)summary(ar_lm)
Call:
lm(formula = Arrivals_ts ~ t_centered)
Residuals:
Min 1Q Median 3Q Max
-1828757 -525528 -55961 354770 2465742
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4958137 50483 98.22 <2e-16 ***
t_centered 215025 8744 24.59 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 782100 on 238 degrees of freedom
Multiple R-squared: 0.7176, Adjusted R-squared: 0.7164
F-statistic: 604.7 on 1 and 238 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Arrivals_ts/1000000,ar_lm$fitted.values/1000000), col=c("darkblue", "darkred"),ylab ="Arrivals (millions)", main ="Original Data with Fitted Values - Linear", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ar_lm$residuals)
Ljung-Box test
data: Residuals
Q* = 176.1, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ar_lm$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.456
0.01
Stationary
Phillips-Perron Test
-113.997
0.01
Stationary
KPSS Test
0.109
0.1
Stationary
ERS Test
-7.740
N/A
Stationary
Show the code
check_normality_residuals(ar_lm)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.973
0
Non-Normal
Anderson-Darling Test
2.066
0
Non-Normal
Jarque-Bera Test
13.541
0.001
Non-Normal
Lilliefors Test
0.084
0
Non-Normal
Skewness
0.570
N/A
Right-Skewed
Kurtosis
3.250
N/A
Mesokurtic
In this model, we included both a linear term (t_centered) and a quadratic term (I(t^2)) to fit arrivals. The output indicates that neither the intercept, linear, nor quadratic coefficients are statistically significant (p > 0.05), suggesting that the quadratic term does not add explanatory power to the model. However, an adjusted R-squared at 0.7152 means 71.52% of the variance in arrivals is still explained by the model. This is lower than the linear model alone, so we will not keep the quadratic term in following models. Meanwhile, a highly significant F-statistic (301.1, p < 0.05) confirms that the model overall is meaningful, despite the individual terms lacking significance. Residuals are stationary but lack independence and normality.
Call:
lm(formula = Arrivals_ts ~ t_centered + I(t^2))
Residuals:
Min 1Q Median 3Q Max
-1818018 -527921 -57131 355228 2463173
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 8.738e+08 6.855e+09 0.127 0.899
t_centered 1.080e+06 6.821e+06 0.158 0.874
I(t^2) -2.151e+02 1.697e+03 -0.127 0.899
Residual standard error: 783700 on 237 degrees of freedom
Multiple R-squared: 0.7176, Adjusted R-squared: 0.7152
F-statistic: 301.1 on 2 and 237 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Arrivals_ts/1000000,ar_quad$fitted.values/1000000), col=c("darkblue", "darkred"),ylab ="Arrivals (millions)", main ="Original Data with Fitted Values - Quadratic", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ar_quad$residuals)
Ljung-Box test
data: Residuals
Q* = 176.18, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ar_quad$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.480
0.01
Stationary
Phillips-Perron Test
-113.988
0.01
Stationary
KPSS Test
0.107
0.1
Stationary
ERS Test
-7.724
N/A
Stationary
Show the code
check_normality_residuals(ar_quad)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.973
0
Non-Normal
Anderson-Darling Test
2.087
0
Non-Normal
Jarque-Bera Test
13.648
0.001
Non-Normal
Lilliefors Test
0.083
0
Non-Normal
Skewness
0.570
N/A
Right-Skewed
Kurtosis
3.240
N/A
Mesokurtic
In this model, we included a centered time trend (t_centered) and monthly indicator variables (t_month) to capture both linear trends and seasonal patterns in arrivals. The output shows that both the intercept and time trend are highly significant (p < 0.05), indicating a clear upward trend over time. Many monthly coefficients are also significant, suggesting distinct seasonal effects on arrivals (e.g., increased arrivals in July and August). The adjusted R-squared is 0.9131, meaning 91.31% of the variance in arrivals is explained by the model, a marked improvement in explanatory power. A highly significant F-statistic (210.4, p < 0.05) confirms the model’s strong overall fit. However, the residuals show non-stationarity, non-independence, and non-normality.
Show the code
# Linear + Seasonal Trend (0.9128 with t^2 < 0.9131 without t^2)t_month <-factor(month(Masterdata$Time))ar_month <-lm(Arrivals_ts ~ t_centered + t_month)summary(ar_month)
ts.plot(cbind(Arrivals_ts/1000000,ar_month$fitted.values/1000000 ), col=c("darkblue", "darkred"),ylab ="Arrivals (millions)", main ="Original Data with Fitted Values - Linear + Seasonal", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ar_month$residuals)
Ljung-Box test
data: Residuals
Q* = 713.33, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ar_month$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-3.270
0.08
Non-Stationary
Phillips-Perron Test
-44.256
0.01
Stationary
KPSS Test
0.233
0.1
Stationary
ERS Test
-1.551
N/A
Non-Stationary
Show the code
check_normality_residuals(ar_month)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.992
0.204
Normal
Anderson-Darling Test
0.757
0.048
Non-Normal
Jarque-Bera Test
0.225
0.894
Normal
Lilliefors Test
0.063
0.022
Non-Normal
Skewness
0.010
N/A
Approximately Symmetric
Kurtosis
3.150
N/A
Mesokurtic
In this model, we fit a linear trend (t_centered) alongside a seasonal cosine term (cosine_t) to capture both the trend and periodic seasonal effects in arrivals. The intercept and time trend are highly significant (p < 0.05), suggesting a consistent upward trend, with an estimated increase of 215,116 arrivals per time unit. However, the cosine term is not significant (p = 0.155), indicating it does not add meaningful explanatory power for seasonality. The adjusted R-squared value of 0.7176 shows that the model explains 71.76% of the variance, while a highly significant F-statistic (304.7, p < 0.05) confirms the model’s overall fit. Residuals are stationary, yet they lack independence and normality.
Show the code
# Linear + Cosine Trendcosine_t <-cos(2* pi * t /12)ar_cos <-lm(Arrivals_ts ~ t_centered + cosine_t)summary(ar_cos)
Call:
lm(formula = Arrivals_ts ~ t_centered + cosine_t)
Residuals:
Min 1Q Median 3Q Max
-1852455 -526048 -64173 376932 2527897
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4976231 51946 95.797 <2e-16 ***
t_centered 215116 8725 24.655 <2e-16 ***
cosine_t -109418 76702 -1.427 0.155
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 780400 on 237 degrees of freedom
Multiple R-squared: 0.72, Adjusted R-squared: 0.7176
F-statistic: 304.7 on 2 and 237 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Arrivals_ts/1000000,ar_cos$fitted.values/1000000 ), col=c("darkblue", "darkred"),ylab ="Arrivals (millions)", main ="Original Data with Fitted Values - Linear + Cosine", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ar_cos$residuals)
Ljung-Box test
data: Residuals
Q* = 177.01, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ar_cos$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.472
0.01
Stationary
Phillips-Perron Test
-114.949
0.01
Stationary
KPSS Test
0.103
0.1
Stationary
ERS Test
-7.842
N/A
Stationary
Show the code
check_normality_residuals(ar_cos)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.975
0
Non-Normal
Anderson-Darling Test
1.907
0
Non-Normal
Jarque-Bera Test
14.711
0.001
Non-Normal
Lilliefors Test
0.078
0.001
Non-Normal
Skewness
0.590
N/A
Right-Skewed
Kurtosis
3.310
N/A
Mesokurtic
In this model, we include both a linear time trend (t_centered) and seasonal components (cosine_t and t_month) to capture the trend and monthly variations in tourist arrivals. The intercept and trend term are highly significant (p < 0.05), with an estimated increase of 213,407 arrivals per time unit. The cosine term and many monthly indicators are also significant, suggesting both trend and seasonal effects are present in the data. The high adjusted R-squared of 0.9154 indicates the model explains 91.54% of the variance in arrivals, supported by the significant F-statistic (199.9, p < 0.05) for overall model fit. Residuals are largely normal and stationary, though they show some autocorrelation, indicating residual dependence over time.
Of these, two models with seasonal adjustments, “Linear + Seasonal” and “Linear + Seasonal + Cosine,” demonstrated the best performance in terms of trend accuracy and seasonal variability. These models also achieved the lowest AIC values (6925.19 and 6919.86), lowest BIC values (6973.92 and 6972.07), and the highest R-squared values (0.91 and 0.92). Based on these criteria, the “Linear + Seasonal + Cosine” model is the preferred fit for the tourist arrivals data.
However, it is important to note that this model’s residuals do not meet assumptions of stationarity, normality, or independence, which limits our confidence in the forecast intervals.
Show the code
# Check modelcheck_model(ar_month)
Here, we try to decompose the time series. As shown in the plots, there are significant seasonality of frequency 12 months.
Show the code
# Classical Seasonal Decomposition by Moving Averagesplot(decompose(Arrivals_ts/1000000))
Here, we trained the model with data up to 2018, and then tested against actual data in 2019. This technique is called Leave One Out Cross Validation (LOOCV) (Olivo, 2024). The predicted values and intervals miss the actual data almost every time and the \(R^2\) is negative, suggesting a poor fit.
Show the code
# Train on Data Up to 2018 and Predict for 2019 (with 95% CI)train_data <-window(Arrivals_ts, end =c(2018, 12))test_data <-window(Arrivals_ts, start =c(2019, 1), end =c(2019, 12))t_train <-time(train_data)t_month_train <- t_month[1:228]cosine_t_train <- cosine_t[1:228]model_train <-lm(train_data ~ t_train + cosine_t_train + t_month_train)t_test <-time(test_data)t_month_test <- t_month[229:240]cosine_t_test <- cosine_t[229:240]predictions_2019 <-predict(model_train,newdata =data.frame(t_train = t_test,cosine_t_train = cosine_t_test,t_month_train = t_month_test),interval ="confidence", level =0.95)# Combine actual and predicted datatime_full_2019 <-data.frame(Date =c(time(train_data), time(test_data)),Actual =c(as.numeric(train_data), as.numeric(test_data)),Predicted =c(rep(NA, length(train_data)), predictions_2019[,"fit"]),Lower_CI =c(rep(NA, length(train_data)), predictions_2019[,"lwr"]),Upper_CI =c(rep(NA, length(train_data)), predictions_2019[,"upr"]))time_only_2019 <-data.frame(Date =as.Date(time(test_data)),Actual = test_data,Predicted = predictions_2019[,"fit"],Lower_CI = predictions_2019[,"lwr"],Upper_CI = predictions_2019[,"upr"])ar_pred_2019 <-ggplot(time_only_2019, aes(x = Date)) +geom_line(aes(y = Actual/1000000, color ="Actual")) +geom_line(aes(y = Predicted/1000000, color ="Predicted")) +geom_ribbon(aes(ymin = Lower_CI/1000000, ymax = Upper_CI/1000000), fill ="grey30", alpha =0.4) +labs(title ="Actual and Predicted Arrivals (2019) with 95% CI",y ="Tourist Arrivals (millions)")+scale_x_date(date_labels ="%b %Y", date_breaks ="2 months") +theme_minimal() +scale_color_manual(values =c("Actual"="darkblue", "Predicted"="red"))ar_pred_full <-ggplot(time_full_2019, aes(x = Date)) +geom_line(aes(y = Actual/1000000, color ="Actual")) +geom_line(aes(y = Predicted/1000000, color ="Predicted")) +geom_ribbon(aes(ymin = Lower_CI/1000000, ymax = Upper_CI/1000000), fill ="grey30", alpha =0.4) +labs(title ="Actual and Predicted Arrivals (2019) with 95% CI",y ="Tourist Arrivals (millions)") +theme_minimal() +scale_color_manual(values =c("Actual"="darkblue", "Predicted"="red"))grid.arrange(ar_pred_full, ar_pred_2019, nrow=2)
Based on the poor result in the cross-validation test, when we applied the model to forecast arrivals for hypothetical years 2020 and 2021 (assuming no impact from COVID-19), the predictions likely exceeded what actual values would have been in such a scenario.
Show the code
# Fit Model on Full Data and Forecast Two Years Ahead (with 95% CI)ar_pred <-lm(Arrivals_ts ~ t + cosine_t + t_month)future_dates <-ts(rep(NA, 24),start=c(2020,1), frequency =12)t_future <-time(future_dates)cosine_t_future <-cos(2* pi *time(future_dates) /12)t_month_future <-factor(month(seq(as.Date("2020-01-01"),as.Date("2021-12-01"), by ="month")))predictions_future <-predict(ar_pred,newdata =data.frame(t = t_future,cosine_t = cosine_t_future,t_month = t_month_future),interval ="confidence", level =0.95)# Combine actual data with forecastforecast_2020_2021 <-data.frame(Date =c(time(Arrivals_ts), time(future_dates)),Actual =c(as.numeric(Arrivals_ts), rep(NA, length(future_dates))),Forecast =c(rep(NA, length(Arrivals_ts)), predictions_future[,"fit"]),Lower_CI =c(rep(NA, length(Arrivals_ts)), predictions_future[,"lwr"]),Upper_CI =c(rep(NA, length(Arrivals_ts)), predictions_future[,"upr"]))# Plotggplot(forecast_2020_2021, aes(x = Date)) +geom_line(aes(y = Actual/1000000, color ="Actual")) +geom_line(aes(y = Forecast/1000000, color ="Forecast")) +geom_ribbon(aes(ymin = Lower_CI/1000000, ymax = Upper_CI/1000000), fill ="grey30", alpha =0.4) +labs(title ="Tourist Arrivals Forecast (2020-2021) with 95% CI",y ="Tourist Arrivals (millions)") +theme_minimal() +scale_color_manual(values =c("Actual"="darkblue", "Forecast"="red"))
This linear model for monthly tourist exports data reveals a significant upward trend over time, with an estimated increase of 736.80 in exports per time unit (p < 0.05). The intercept, estimated at 13,864.95, represents the baseline level of exports. The high adjusted R^2 value of 0.9211 indicates that 92.11% of the variance in exports is explained by time, supporting a strong fit. Additionally, the significant F-statistic (2792, p < 0.05) affirms the model’s relevance. However, residuals display non-normality, lack independence, and are non-stationary, signaling potential limitations for forecasting or inference reliability in this model.
Show the code
t <-time(Exports_ts)t_centered <- t -mean(t)# Linearex_lm <-lm(Exports_ts ~ t_centered)summary(ex_lm)
Call:
lm(formula = Exports_ts ~ t_centered)
Residuals:
Min 1Q Median 3Q Max
-2551.6 -929.9 -169.9 701.6 3326.2
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13864.95 80.50 172.23 <2e-16 ***
t_centered 736.80 13.94 52.84 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1247 on 238 degrees of freedom
Multiple R-squared: 0.9215, Adjusted R-squared: 0.9211
F-statistic: 2792 on 1 and 238 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Exports_ts/1000,ex_lm$fitted.values/1000), col=c("darkblue", "darkred"), ylab ="Exports (billions $)", main ="Original Data with Fitted Values - Linear", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ex_lm$residuals)
Ljung-Box test
data: Residuals
Q* = 1464.4, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ex_lm$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-3.005
0.15
Non-Stationary
Phillips-Perron Test
-8.956
0.61
Non-Stationary
KPSS Test
0.515
0.04
Non-Stationary
ERS Test
-0.496
N/A
Non-Stationary
Show the code
check_normality_residuals(ex_lm)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.963
0
Non-Normal
Anderson-Darling Test
2.712
0
Non-Normal
Jarque-Bera Test
15.144
0.001
Non-Normal
Lilliefors Test
0.085
0
Non-Normal
Skewness
0.620
N/A
Right-Skewed
Kurtosis
3.010
N/A
Mesokurtic
In this quadratic model for monthly tourist exports, both the linear and quadratic time terms significantly predict exports, indicating a curved upward trend. The intercept, estimated at -5.899e+07, reflects the baseline export level when accounting for both terms. The quadratic term’s positive estimate (1.461 per time unit squared) suggests an accelerating increase over time. The high R^2 of 0.9305 implies that 93.05% of the variance in exports is explained by the model, and the substantial F-statistic (1602, p < 0.05) highlights its strong fit. Nonetheless, residuals remain non-normal, non-independent, and non-stationary, which may impact the model’s robustness for further interpretation or predictive use.
Note that the adjusted R^2 here is higher than that of the linear model alone, so we will keep this quadratic term in the following models
Call:
lm(formula = Exports_ts ~ t_centered + I(t^2))
Residuals:
Min 1Q Median 3Q Max
-2504.8 -837.3 -230.1 968.2 2436.5
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -5.899e+07 1.024e+07 -5.762 2.57e-08 ***
t_centered -5.797e+04 1.019e+04 -5.691 3.71e-08 ***
I(t^2) 1.461e+01 2.534e+00 5.763 2.55e-08 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1170 on 237 degrees of freedom
Multiple R-squared: 0.9311, Adjusted R-squared: 0.9305
F-statistic: 1602 on 2 and 237 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Exports_ts/1000,ex_quad$fitted.values/1000), col=c("darkblue", "darkred"), ylab ="Exports (billions $)", main ="Original Data with Fitted Values - Quadratic", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ex_quad$residuals)
Ljung-Box test
data: Residuals
Q* = 1407.8, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ex_quad$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-2.229
0.48
Non-Stationary
Phillips-Perron Test
-7.588
0.68
Non-Stationary
KPSS Test
0.421
0.07
Stationary
ERS Test
-0.611
N/A
Non-Stationary
Show the code
check_normality_residuals(ex_quad)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.969
0
Non-Normal
Anderson-Darling Test
2.727
0
Non-Normal
Jarque-Bera Test
8.759
0.013
Non-Normal
Lilliefors Test
0.098
0
Non-Normal
Skewness
0.220
N/A
Approximately Symmetric
Kurtosis
2.180
N/A
Platykurtic (Light Tails)
This model incorporates both linear and quadratic time terms, along with monthly indicators, to capture trend and seasonality in the tourist exports data. Significant coefficients for the intercept, linear, and quadratic terms highlight a strong upward trend with a slight curvature. Monthly indicators are not statistically significant, suggesting that monthly seasonality has minimal impact on exports. The model’s adjusted R^2 of 0.9276 demonstrates strong explanatory power, supported by a high F-statistic (236.6, p < 0.05), indicating a good overall fit. However, residuals remain non-normal, non-independent, and non-stationary.
ts.plot(cbind(Exports_ts/1000,ex_month$fitted.values/1000 ), col=c("darkblue", "darkred"), ylab ="Exports (billions $)", main ="Original Data with Fitted Values - Quadratic + Seasonal", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ex_month$residuals)
Ljung-Box test
data: Residuals
Q* = 1411, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ex_month$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-2.284
0.46
Non-Stationary
Phillips-Perron Test
-7.907
0.67
Non-Stationary
KPSS Test
0.420
0.07
Stationary
ERS Test
-0.705
N/A
Non-Stationary
Show the code
check_normality_residuals(ex_month)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.967
0
Non-Normal
Anderson-Darling Test
2.920
0
Non-Normal
Jarque-Bera Test
8.708
0.013
Non-Normal
Lilliefors Test
0.113
0
Non-Normal
Skewness
0.220
N/A
Approximately Symmetric
Kurtosis
2.180
N/A
Platykurtic (Light Tails)
This model includes linear and quadratic time components, as well as a cosine term to capture cyclical effects in the tourist exports data. The significant coefficients for the intercept, linear, and quadratic terms suggest a strong upward trend with a curvature, while the cosine term is not statistically significant, indicating that cyclical seasonality is not a prominent factor in this model. The model’s adjusted R^2 of 0.9304 indicates high explanatory power, supported by a substantial F-statistic (1066, p < 0.05), reflecting a good model fit. However, residuals remain non-normal, non-independent, and non-stationary.
Show the code
# Linear + Cosine Trendcosine_t <-cos(2* pi * t /12)ex_cos <-lm(Exports_ts ~ t_centered +I(t^2) + cosine_t)summary(ex_cos)
Call:
lm(formula = Exports_ts ~ t_centered + I(t^2) + cosine_t)
Residuals:
Min 1Q Median 3Q Max
-2418.1 -871.3 -246.4 984.6 2525.5
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -5.640e+07 1.080e+07 -5.223 3.86e-07 ***
t_centered -5.539e+04 1.074e+04 -5.156 5.34e-07 ***
I(t^2) 1.396e+01 2.673e+00 5.224 3.84e-07 ***
cosine_t 9.239e+01 1.213e+02 0.762 0.447
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1171 on 236 degrees of freedom
Multiple R-squared: 0.9313, Adjusted R-squared: 0.9304
F-statistic: 1066 on 3 and 236 DF, p-value: < 2.2e-16
Show the code
ts.plot(cbind(Exports_ts/1000,ex_cos$fitted.values/1000 ), col=c("darkblue", "darkred"),ylab ="Exports (billions $)", main ="Original Data with Fitted Values - Quadratic + Cosine", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ex_cos$residuals)
Ljung-Box test
data: Residuals
Q* = 1404.9, df = 10, p-value < 2.2e-16
Model df: 0. Total lags used: 10
Show the code
check_stationarity(ex_cos$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-2.327
0.44
Non-Stationary
Phillips-Perron Test
-7.893
0.67
Non-Stationary
KPSS Test
0.421
0.07
Stationary
ERS Test
-0.594
N/A
Non-Stationary
Show the code
check_normality_residuals(ex_cos)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.969
0
Non-Normal
Anderson-Darling Test
2.727
0
Non-Normal
Jarque-Bera Test
9.600
0.008
Non-Normal
Lilliefors Test
0.096
0
Non-Normal
Skewness
0.260
N/A
Approximately Symmetric
Kurtosis
2.170
N/A
Platykurtic (Light Tails)
This model includes linear, quadratic, and cosine terms, along wih monthly indicators. Both the linear and quadratic terms are statistically significant (p < 0.05), suggesting that the trend follows a parabolic pattern. The cosine term and all monthly indicators, however, are not statistically significant (p > 0.05), indicating that it may not add meaningful cyclical variation. With an adjusted R^2of 0.9275, this model explains a substantial portion of the variance in the data. The F-statistic (219.3, p < 0.05) supports the model’s overall significance. However, residual issues persist, with non-normality, non-independence, and signs of non-stationarity.
Here, a quadratic model emerged as the best fit, with an R-squared of 0.93, an AIC of 4077.33, and a BIC of 4091.25. However, these metrics across all five models were relatively similar, suggesting no model captured the data particularly better than the others.
Additionally, the quadratic model’s residuals, like those of the arrivals model, failed to meet stationarity, normality, and independence assumptions, limiting the reliability of its forecast intervals. It also possesses problem of collinearity.
Show the code
check_model(ex_both)
Here, we try to decompose the time series. As shown in the plots, while there seems to be signs of seasonality with frequency of 12 months, the magnitude of these seasonality signals are not so significant.
Show the code
# Classical Seasonal Decomposition by Moving Averagesplot(decompose(Exports_ts/1000))
Based on all of the poor statistics and test results above with this quadratic model, we know for certain that this model’s predictive power should not be deemed reliable and all predictions have to be interpreted with caution. Hence, we skipped the cross-validation test and proceeded with the prediction.
Show the code
# Fit Model on Full Data and Forecast Two Years Ahead (with 95% CI)ex_pred <-lm(Arrivals_ts ~ t +I(t^2))future_dates <-ts(rep(NA, 24), start =c(2020, 1), frequency =12)t_future <-time(future_dates)predictions_future <-predict(ex_pred,newdata =data.frame(t = t_future,`I(t^2)`= t_future^2),interval ="confidence", level =0.95)forecast_2020_2021 <-data.frame(Date =c(time(Arrivals_ts), time(future_dates)),Actual =c(as.numeric(Arrivals_ts), rep(NA, length(future_dates))),Forecast =c(rep(NA, length(Arrivals_ts)), predictions_future[,"fit"]),Lower_CI =c(rep(NA, length(Arrivals_ts)), predictions_future[,"lwr"]),Upper_CI =c(rep(NA, length(Arrivals_ts)), predictions_future[,"upr"]))ggplot(forecast_2020_2021, aes(x = Date)) +geom_line(aes(y = Actual /1000000, color ="Actual")) +geom_line(aes(y = Forecast /1000000, color ="Forecast")) +geom_ribbon(aes(ymin = Lower_CI /1000000, ymax = Upper_CI /1000000), fill ="grey30", alpha =0.4) +labs(title ="Tourist Arrivals Forecast (2020-2021) with 95% CI",y ="Tourist Arrivals (millions)") +theme_minimal() +scale_color_manual(values =c("Actual"="darkblue", "Forecast"="red"))
Auto.arima() suggests an ARIMA(3,0,2)(2,1,0)[12] with drift. From the outset, this model seems to fit the model well, with stationary and independent residuals. Yet, the residuals are not normal, suggesting poor reliability of forecasts. This step is only for reference, and we will attempt to build our only models.
Show the code
# Auto.arima() - ARIMA(3,0,2)(2,1,0)[12] with driftar_initial <-auto.arima(Arrivals_ts)ar_initial
ts.plot(cbind(Arrivals_ts/1000000,ar_initial$fitted/1000000), col=c("darkblue", "darkred"), ylab ="Arrivals (millions)", main ="Original Data with Fitted Values - Auto ARIMA", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ar_initial)
Ljung-Box test
data: Residuals from ARIMA(3,0,2)(2,1,0)[12] with drift
Q* = 18.224, df = 17, p-value = 0.3748
Model df: 7. Total lags used: 24
Show the code
two_plots(ar_initial$residuals)
Show the code
check_stationarity(ar_initial$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.533
0.01
Stationary
Phillips-Perron Test
-237.742
0.01
Stationary
KPSS Test
0.135
0.1
Stationary
ERS Test
-7.112
N/A
Stationary
Show the code
check_normality_residuals(ar_initial)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.967
0
Non-Normal
Anderson-Darling Test
2.200
0
Non-Normal
Jarque-Bera Test
51.371
0
Non-Normal
Lilliefors Test
0.074
0.003
Non-Normal
Skewness
-0.230
N/A
Approximately Symmetric
Kurtosis
5.220
N/A
Leptokurtic (Heavy Tails)
Recall the ACF plot with a slow decay in both seasonal and normal lags, we will try both techniques here.
Show the code
# Seasonal Differencingsd_ar <-diff(Arrivals_ts,12)diff_plots(sd_ar/1000000,"Seasonally Differenced Time Series")
Show the code
check_stationarity(sd_ar)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-3.026
0.14
Non-Stationary
Phillips-Perron Test
-70.739
0.01
Stationary
KPSS Test
0.305
0.1
Stationary
ERS Test
-4.092
N/A
Stationary
By only seasonally differencing, the series does not pass all stationarity tests.
Show the code
# Normal Differencingdiff_ar <-diff(Arrivals_ts)diff_plots(diff_ar, "Normally Differenced Time Series")
Show the code
check_stationarity(diff_ar)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-12.989
0.01
Stationary
Phillips-Perron Test
-221.516
0.01
Stationary
KPSS Test
0.009
0.1
Stationary
ERS Test
-5.811
N/A
Stationary
By only normally differencing, the series is already stationary, based on the “by-eye” test and othr statistical tests. Looking at the seasonal lags 12 and 24 decaying slowly in the ACF and the signicant lag 12 in the PACF, if we start fitting with only normal differencing, we will start with SAR 1. Note that the mean of residuals is zero, so we don’t need to include a drift.
Show the code
# Normal & Seasonal Differencingar_nsd <-diff(diff(Arrivals_ts,12))diff_plots(ar_nsd, "Normally & Seasonally Differenced Time Series")
Show the code
check_stationarity(ar_nsd)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-6.957
0.01
Stationary
Phillips-Perron Test
-302.729
0.01
Stationary
KPSS Test
0.018
0.1
Stationary
ERS Test
-2.911
N/A
Stationary
By differencing both ways, we achieved a stationary series, with smaller variance and less autocorrelations. Based on the ACF and PACF, a next move could be MA 1. Note that the mean of residuals is zero, so we don’t need to include a drift.
Ljung-Box test
data: Residuals from ARIMA(0,1,1)(1,0,0)[12]
Q* = 54.975, df = 22, p-value = 0.0001198
Model df: 2. Total lags used: 24
Show the code
two_plots(ar_011100$residuals)
Show the code
check_stationarity(ar_011100$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-6.288
0.01
Stationary
Phillips-Perron Test
-251.548
0.01
Stationary
KPSS Test
0.022
0.1
Stationary
ERS Test
-7.745
N/A
Stationary
Show the code
check_normality_residuals(ar_011100)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.973
0
Non-Normal
Anderson-Darling Test
1.573
0
Non-Normal
Jarque-Bera Test
40.733
0
Non-Normal
Lilliefors Test
0.067
0.011
Non-Normal
Skewness
0.040
N/A
Approximately Symmetric
Kurtosis
5.020
N/A
Leptokurtic (Heavy Tails)
Here, we fit an ARIMA(0,1,1)(1,0,1)[12]. Based on the ACF, PACF, and other tests, we have finally found a model with independent and stationary residuals. All coefficients in the model are significant with small standard errors. However, the residuals are not normal; hence, we cannot trust the forecast intervals.
Ljung-Box test
data: Residuals from ARIMA(0,1,1)(0,1,0)[12]
Q* = 79.472, df = 23, p-value = 3.865e-08
Model df: 1. Total lags used: 24
Show the code
two_plots(ar_011010$residuals)
Show the code
check_stationarity(ar_011010$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.740
0.01
Stationary
Phillips-Perron Test
-258.543
0.01
Stationary
KPSS Test
0.023
0.1
Stationary
ERS Test
-7.880
N/A
Stationary
Show the code
check_normality_residuals(ar_011010)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.963
0
Non-Normal
Anderson-Darling Test
2.302
0
Non-Normal
Jarque-Bera Test
66.311
0
Non-Normal
Lilliefors Test
0.072
0.004
Non-Normal
Skewness
0.030
N/A
Approximately Symmetric
Kurtosis
5.570
N/A
Leptokurtic (Heavy Tails)
Here, we fit an ARIMA(0,1,1)(0,1,1)[12]. Based on the ACF, PACF, and other statistical tests, we have found a model with independent and stationary residuals. However, the residuals are not normal; hence, we cannot trust the forecast intervals.
Based on the summary table below, the best model appears to be ARIMA(0,1,1)(0,1,1)[12], with the lowest AIC and BIC values. Still, we recall that the residuals for this model are not normal, and thus we should not trust the forecast intervals with much reliability.
Show the code
ar_sarima_models <-list("auto.arima()"= ar_initial, "SARIMA(0,1,0)(1,0,0)[12] without drift"= ar_010100,"SARIMA(0,1,1)(1,0,0)[12] without drift"= ar_011100,"SARIMA(0,1,1)(1,0,1)[12] without drift"= ar_011101,"SARIMA(0,1,1)(0,1,0)[12] without drift"= ar_011010, "SARIMA(0,1,1)(0,1,1)[12] without drift"= ar_011011)sarima_summary_table(ar_sarima_models)
Model Summary
Model
AIC
BIC
auto.arima()
6320.56
6351.42
SARIMA(0,1,0)(1,0,0)[12] without drift
6764.97
6771.92
SARIMA(0,1,1)(1,0,0)[12] without drift
6716.69
6727.11
SARIMA(0,1,1)(1,0,1)[12] without drift
6647.36
6661.27
SARIMA(0,1,1)(0,1,0)[12] without drift
6366.59
6373.44
SARIMA(0,1,1)(0,1,1)[12] without drift
6288.87
6299.15
Show the code
check_model(ar_011011)
After verifying that ARIMA(0,1,1)(0,1,1)[12] is also the best fit for the time period between 2000 and 2018, we used this model to predict the 2019 value and tested against the actual data. The result is astonishing with the fitted values following closely actual values. Moreover, an R^2 value of 0.9707 suggests that 97.07% of the variability in actual data can be explained by our model. We now want to leverage this model to predict the 2020 and 2021 hypothetical scenarios of no COVID.
Here, we can see that the model predicts monthly arrivals to be relatively stable, exhibiting historical seasonal trends with a slightly upward motion. This makes sense as without COVID-19, the US should have remained its attractiveness as an ideal place for travel, business, and education in 2020 and 2021.
Show the code
# Run SARIMA forecast for 24 months ahead (2020 and 2021)ar_sarima_for <-sarima.for(Arrivals_ts, n.ahead =24, 0,1,1,0,1,1, S =12, plot =FALSE)forecasted_values <- ar_sarima_for$predlower_bound <- ar_sarima_for$pred -1.96* ar_sarima_for$seupper_bound <- ar_sarima_for$pred +1.96* ar_sarima_for$setime_forecast <-ts(rep(NA,24), start =c(2020, 1), end =c(2021, 12), frequency =12)forecast_ts <-ts(forecasted_values, start =c(2020, 1), frequency =12)lower_bound_ts <-ts(lower_bound, start =c(2020, 1), frequency =12)upper_bound_ts <-ts(upper_bound, start =c(2020, 1), frequency =12)combined_ts <-ts(c(Arrivals_ts, forecast_ts), start =start(Arrivals_ts), frequency =12)time_full_forecast <-data.frame(Date =c(time(Arrivals_ts), time(time_forecast)),Actual =c(as.numeric(Arrivals_ts), rep(NA, length(forecast_ts))),Predicted =c(rep(NA, length(Arrivals_ts)), forecasted_values),Lower_CI =c(rep(NA, length(Arrivals_ts)), lower_bound),Upper_CI =c(rep(NA, length(Arrivals_ts)), upper_bound))time_forecast_2020_2021 <-data.frame(Date =as.Date(time(time_forecast)),Predicted = forecasted_values,Lower_CI = lower_bound,Upper_CI = upper_bound)# Plotting full dataset with forecastsar_pred_full_forecast <-ggplot(time_full_forecast, aes(x = Date)) +geom_line(aes(y = Actual /1000000, color ="Actual")) +geom_line(aes(y = Predicted /1000000, color ="Predicted")) +geom_ribbon(aes(ymin = Lower_CI /1000000, ymax = Upper_CI /1000000), fill ="grey30", alpha =0.4) +labs(title ="Full Data with Forecasted Arrivals (2020-2021) with 95% CI",y ="Tourist Arrivals (millions)") +theme_minimal() +scale_color_manual(values =c("Actual"="darkblue", "Predicted"="red"))# Plotting only 2020 and 2021ar_pred_2020_2021 <-ggplot(time_forecast_2020_2021, aes(x = Date)) +geom_line(aes(y = Predicted /1000000, color ="Predicted")) +geom_ribbon(aes(ymin = Lower_CI /1000000, ymax = Upper_CI /1000000), fill ="grey30", alpha =0.4) +labs(title ="Forecasted Tourist Arrivals (2020-2021) with 95% CI",y ="Tourist Arrivals (millions)") +scale_x_date(date_labels ="%b %Y", date_breaks ="3 months") +theme_minimal() +scale_color_manual(values =c("Predicted"="red"))grid.arrange(ar_pred_full_forecast, ar_pred_2020_2021, nrow =2)
Auto.arima() suggests an ARIMA(2,1,2)(2,0,0)[12] with drift. From the outset, this model seems to fit the model well, with stationary and independent residuals. Yet, the residuals are not normal, suggesting poor reliability of forecasts. This step is only for reference, and we will attempt to build our only models.
Show the code
# Auto.arima() - ARIMA(2,1,2)(2,0,0)[12] with driftex_initial <-auto.arima(Exports_ts)ex_initial
ts.plot(cbind(Exports_ts/1000,ex_initial$fitted/1000), col=c("darkblue", "darkred"), ylab ="Exports (billions $)", main ="Original Data with Fitted Values - Auto ARIMA", lwd =1.5)legend("topleft", legend=c("Original Data", "Fitted Values"), col=c("darkblue", "darkred"), lty=1:1,lwd=2)
Show the code
checkresiduals(ex_initial)
Ljung-Box test
data: Residuals from ARIMA(2,1,2)(2,0,0)[12] with drift
Q* = 17.561, df = 18, p-value = 0.4849
Model df: 6. Total lags used: 24
Show the code
two_plots(ex_initial$residuals)
Show the code
check_stationarity(ex_initial$residuals)
Stationarity Test Summary
Test
Test.Statistic
P.Value
Interpretation
ADF Test
-5.506
0.01
Stationary
Phillips-Perron Test
-226.347
0.01
Stationary
KPSS Test
0.297
0.1
Stationary
ERS Test
-6.948
N/A
Stationary
Show the code
check_normality_residuals(ex_initial)
Normality Test Summary
Test
Test.Statistic
P.Value
Interpretation
Shapiro-Wilk Test
0.964
0
Non-Normal
Anderson-Darling Test
1.561
0.001
Non-Normal
Jarque-Bera Test
106.788
0
Non-Normal
Lilliefors Test
0.063
0.021
Non-Normal
Skewness
-0.450
N/A
Approximately Symmetric
Kurtosis
6.140
N/A
Leptokurtic (Heavy Tails)
Recall the slow decay in the ACF plot, normal differencing is the first thing we should try. Luckily, it appears to be all that we need to achieve stationarity. Noting the non-zero mean of residuals, we will include a drift term in our models. Based on the ACF and PACF, the best next move appears to be a SMA 1 (\(Q=1\))
Show the code
ex_diff <-diff(Exports_ts)diff_plots(ex_diff,"Normally Differenced Time Series")
From the suggestions during differencing, we first fit an ARIMA(0,1,0)(0,0,1)[12]. Luckily, we immediately arrived at a good model with residuals showing stationarity and independence. However, the residuals are not normal; hence, we cannot trust the forecast intervals.
From the ACF anf PACF plots during differencing, someone may suggest an SAR2 as well, i.e. (\(P=2\)). Hence, we fit an ARIMA(0,1,0)(2,0,0)[12]. Here, we also achieved stationarity and independence for residuals, though not normality. With two solid models, we wanted to see what would happen if we include more terms.
Of all five models, our first two models appear to be the best fits to the data. However, while the second one (SARIMA(0,1,0)(2,0,0)[12] with drift) is more favorable in terms of AIC, the first one (SARIMA(0,1,0)(0,0,1)[12] with drift) is superior in terms of BIC. Now, as we know that the time series is seasonally adjusted, which means that seasonality should not play too much of a role in our prediction; hence, the notion that tourist exports from two years ago may still have an impact on that of this current month seems unplausible. Therefore, we believe that the first model, SARIMA(0,1,0)(0,0,1)[12] with drift, should be deemed as best fit. Again, as the residuals are not normal, we should not blindly trust our forecast intervals.
Show the code
ex_sarima_models <-list("auto.arima() "= ex_initial,"SARIMA(0,1,0)(0,0,1)[12] with drift"= ex_010001,"SARIMA(0,1,0)(2,0,0)[12] with drift"= ex_010200,"SARIMA(0,1,1)(2,0,0)[12] with drift"= ex_011200,"SARIMA(1,1,1)(2,0,0)[12] with drift"= ex_111200)sarima_summary_table(ex_sarima_models)
Model Summary
Model
AIC
BIC
auto.arima()
3364.03
3391.84
SARIMA(0,1,0)(0,0,1)[12] with drift
3363.19
3373.62
SARIMA(0,1,0)(2,0,0)[12] with drift
3361.95
3375.86
SARIMA(0,1,1)(2,0,0)[12] with drift
3362.27
3379.65
SARIMA(1,1,1)(2,0,0)[12] with drift
3363.32
3384.18
Show the code
check_model(ex_010001)
Show the code
# Run SARIMA forecast for Exports_tsex_sarima_for <-sarima.for(Exports_ts, n.ahead =24, 0,1,0,2,0,0, S =12, plot =FALSE)forecasted_values <- ex_sarima_for$predlower_bound <- ex_sarima_for$pred -1.96* ex_sarima_for$seupper_bound <- ex_sarima_for$pred +1.96* ex_sarima_for$setime_forecast <-time(Exports_ts)[length(Exports_ts)] +seq(1/12, 2, by=1/12)ts_forecast <-ts(forecasted_values, start =c(2020, 1), frequency =12)combined_ts <-ts(c(Exports_ts, ts_forecast), start =start(Exports_ts), frequency =12)astsa::tsplot(combined_ts/1000, col ="darkblue", lwd =1, type ="l",ylab ="Exports (billions $)", main ="US Monthly Tourist Exports Forecast",ylim =c(0, max(upper_bound/1000)))lines(ts_forecast/1000, col ="red", lwd =1.5) # Forecasted valueslines(ts(lower_bound, start =c(2020, 1), frequency =12)/1000, lty =3, col ="blue")lines(ts(upper_bound, start =c(2020, 1), frequency =12)/1000, lty =3, col ="blue")legend("topleft", legend =c("Observed", "Forecast", "95% Confidence Interval"),col =c("darkblue", "red", "blue"), lty =c(1, 1, 3), lwd =c(1, 1.5, 1))