To simulate four MA models, study their time-series plots, estimate their means and autocorrelations, identify their orders using ACF plots, and examine the effect of increasing the white-noise variance.
The four models have the form
\[ Y_t = 3.3 + \varepsilon_t + \theta_1\varepsilon_{t-1} + \cdots + \theta_q\varepsilon_{t-q}, \]
where initially \(\varepsilon_t \sim N(0,1)\). Their orders are 1, 2, 3 and 4, respectively. The true mean of each process is 3.3.
generate_ma <- function(n, theta, mu = 3.3, noise_sd = 1) {
q <- length(theta)
e <- rnorm(n + q, mean = 0, sd = noise_sd)
y <- numeric(n)
for (t in seq_len(n)) {
y[t] <- mu + e[t + q]
for (j in seq_len(q)) {
y[t] <- y[t] + theta[j] * e[t + q - j]
}
}
y
}
ma_coefficients <- list(
"MA(1)" = c(0.75),
"MA(2)" = c(0.75, 0.2),
"MA(3)" = c(0.75, 0.2, 1.1),
"MA(4)" = c(0.75, 0.2, 1.1, 0.33)
)
ma_500 <- lapply(
ma_coefficients,
function(theta) generate_ma(500, theta)
)
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ma_500)) {
plot(
ma_500[[model]],
type = "l",
main = model,
xlab = "Time",
ylab = expression(Y[t])
)
abline(h = 3.3, col = "red", lty = 2)
}
par(old_par)
Observation: Each series fluctuates around 3.3. The observations are random, but the current value depends on a finite number of current and past white-noise terms. An MA process with finite order is stationary.
ma_mean_500 <- data.frame(
Model = names(ma_500),
True_Mean = 3.3,
Sample_Mean = sapply(ma_500, mean)
)
kable(ma_mean_500, digits = 4,
caption = "Process mean estimates (n = 500)")
| Model | True_Mean | Sample_Mean | |
|---|---|---|---|
| MA(1) | MA(1) | 3.3 | 3.3605 |
| MA(2) | MA(2) | 3.3 | 3.2917 |
| MA(3) | MA(3) | 3.3 | 3.3980 |
| MA(4) | MA(4) | 3.3 | 3.4741 |
The ACF function below returns sample autocorrelations at lags 1 to 20.
get_acf <- function(x, max_lag = 20) {
as.numeric(acf(x, lag.max = max_lag, plot = FALSE)$acf)[-1]
}
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ma_500)) {
acf(
ma_500[[model]],
lag.max = 20,
main = paste("ACF:", model)
)
}
par(old_par)
ma_acf_500 <- data.frame(Lag = 1:20)
for (model in names(ma_500)) {
ma_acf_500[[model]] <- get_acf(ma_500[[model]])
}
kable(ma_acf_500, digits = 4,
caption = "Sample ACF values (n = 500)")
| Lag | MA(1) | MA(2) | MA(3) | MA(4) |
|---|---|---|---|---|
| 1 | 0.4197 | 0.5537 | 0.3977 | 0.3662 |
| 2 | -0.0842 | 0.0998 | 0.3802 | 0.2343 |
| 3 | -0.0522 | -0.0284 | 0.4187 | 0.4186 |
| 4 | -0.0641 | 0.0196 | -0.0061 | 0.0035 |
| 5 | -0.0654 | 0.0294 | 0.0555 | -0.1004 |
| 6 | -0.0504 | 0.0435 | -0.0233 | -0.0556 |
| 7 | 0.0031 | 0.0150 | -0.0449 | -0.0908 |
| 8 | 0.0540 | 0.0052 | -0.0117 | -0.0493 |
| 9 | 0.0517 | 0.0486 | -0.1001 | -0.0077 |
| 10 | 0.0271 | 0.0489 | -0.0401 | -0.0492 |
| 11 | -0.0448 | 0.0000 | -0.0278 | -0.0081 |
| 12 | -0.1138 | -0.0274 | -0.0338 | 0.0168 |
| 13 | -0.0428 | 0.0103 | 0.0272 | 0.0146 |
| 14 | 0.0691 | -0.0052 | 0.0021 | 0.0619 |
| 15 | 0.0716 | -0.0202 | -0.0304 | 0.0605 |
| 16 | -0.0150 | -0.0187 | -0.0213 | 0.0447 |
| 17 | -0.0555 | 0.0086 | -0.0664 | -0.0295 |
| 18 | -0.0538 | 0.0535 | -0.0944 | -0.0136 |
| 19 | -0.0217 | 0.0623 | -0.0716 | -0.0980 |
| 20 | -0.0235 | 0.0120 | -0.1033 | -0.2144 |
Observation: For an MA(q) model, the theoretical ACF is zero at all lags greater than q. In a finite sample, some sample autocorrelations beyond q may still be non-zero because of sampling variation. The dashed limits in the ACF plots are approximate 95% reference limits.
ma_50000 <- lapply(
ma_coefficients,
function(theta) generate_ma(50000, theta)
)
ma_mean_comparison <- data.frame(
Model = names(ma_500),
True_Mean = 3.3,
Mean_n500 = sapply(ma_500, mean),
Mean_n50000 = sapply(ma_50000, mean)
)
kable(ma_mean_comparison, digits = 5,
caption = "Mean comparison for two sample sizes")
| Model | True_Mean | Mean_n500 | Mean_n50000 | |
|---|---|---|---|---|
| MA(1) | MA(1) | 3.3 | 3.36045 | 3.29610 |
| MA(2) | MA(2) | 3.3 | 3.29166 | 3.30637 |
| MA(3) | MA(3) | 3.3 | 3.39803 | 3.30976 |
| MA(4) | MA(4) | 3.3 | 3.47407 | 3.32218 |
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ma_50000)) {
acf(
ma_50000[[model]],
lag.max = 20,
main = paste("ACF:", model, "(n = 50000)")
)
}
par(old_par)
ma_acf_50000 <- data.frame(Lag = 1:20)
for (model in names(ma_50000)) {
ma_acf_50000[[model]] <- get_acf(ma_50000[[model]])
}
kable(ma_acf_50000, digits = 4,
caption = "Sample ACF values (n = 50000)")
| Lag | MA(1) | MA(2) | MA(3) | MA(4) |
|---|---|---|---|---|
| 1 | 0.4792 | 0.5586 | 0.3957 | 0.5031 |
| 2 | -0.0018 | 0.1216 | 0.3654 | 0.3712 |
| 3 | -0.0007 | 0.0013 | 0.3902 | 0.4633 |
| 4 | -0.0004 | -0.0034 | 0.0048 | 0.1159 |
| 5 | -0.0030 | -0.0111 | 0.0054 | 0.0053 |
| 6 | -0.0065 | -0.0134 | 0.0033 | 0.0077 |
| 7 | -0.0138 | -0.0142 | 0.0107 | 0.0058 |
| 8 | -0.0131 | -0.0078 | 0.0043 | -0.0023 |
| 9 | -0.0066 | 0.0020 | -0.0010 | -0.0005 |
| 10 | -0.0032 | 0.0042 | -0.0021 | -0.0051 |
| 11 | -0.0084 | 0.0025 | -0.0082 | -0.0114 |
| 12 | -0.0134 | -0.0046 | -0.0105 | -0.0025 |
| 13 | -0.0066 | -0.0056 | -0.0090 | -0.0016 |
| 14 | -0.0024 | -0.0031 | -0.0069 | -0.0007 |
| 15 | -0.0018 | 0.0018 | -0.0037 | 0.0037 |
| 16 | 0.0047 | 0.0063 | -0.0013 | 0.0040 |
| 17 | 0.0078 | 0.0066 | -0.0004 | 0.0038 |
| 18 | 0.0061 | 0.0010 | 0.0012 | 0.0040 |
| 19 | 0.0011 | -0.0022 | 0.0058 | 0.0007 |
| 20 | -0.0017 | -0.0032 | 0.0053 | 0.0005 |
Observation and explanation: The sample means for 50,000 observations should generally be closer to 3.3 than the means for 500 observations. This follows from the law of large numbers. The standard error of the sample mean decreases as the sample size increases. For a stationary MA process,
\[ \operatorname{Var}(\bar Y) \approx \frac{\gamma(0)}{n} \left(1+2\sum_{k=1}^{q}\rho(k)\right). \]
Thus, increasing the sample size makes the estimate more stable.
The series are given anonymous labels in a random display order. Their time order is kept unchanged, because shuffling the observations within a series would destroy the autocorrelation pattern.
set.seed(456)
display_order <- sample(names(ma_500))
unknown_series <- ma_500[display_order]
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (i in seq_along(unknown_series)) {
acf(
unknown_series[[i]],
lag.max = 20,
main = paste("Unknown series", i)
)
}
par(old_par)
Use the approximate cutoff in the ACF to propose the order:
The sample ACF may not show a perfectly sharp cutoff, so use the overall pattern and the approximate confidence limits.
identification_check <- data.frame(
Anonymous_Series = paste("Unknown", seq_along(display_order)),
Actual_Model = unname(display_order),
Actual_Order = sapply(ma_coefficients[display_order], length)
)
kable(identification_check,
caption = "Cross-check of the anonymous series")
| Anonymous_Series | Actual_Model | Actual_Order | |
|---|---|---|---|
| MA(1) | Unknown 1 | MA(1) | 1 |
| MA(4) | Unknown 2 | MA(4) | 4 |
| MA(3) | Unknown 3 | MA(3) | 3 |
| MA(2) | Unknown 4 | MA(2) | 2 |
Observation: The ACF is useful for identifying an MA order because its theoretical autocorrelations become zero after the model order. The table above reveals the true model labels after the visual identification exercise.
Here \(N(0,10)\) is interpreted as
mean 0 and variance 10. Therefore, the standard deviation supplied to
rnorm() is \(\sqrt{10}\).
set.seed(789)
ma_high_variance <- lapply(
ma_coefficients,
function(theta) {
generate_ma(
n = 500,
theta = theta,
mu = 3.3,
noise_sd = sqrt(10)
)
}
)
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ma_high_variance)) {
plot(
ma_high_variance[[model]],
type = "l",
main = paste(model, "with noise variance 10"),
xlab = "Time",
ylab = expression(Y[t])
)
abline(h = 3.3, col = "red", lty = 2)
}
par(old_par)
high_variance_means <- data.frame(
Model = names(ma_high_variance),
True_Mean = 3.3,
Sample_Mean = sapply(ma_high_variance, mean)
)
kable(high_variance_means, digits = 4,
caption = "Mean estimates with white-noise variance 10")
| Model | True_Mean | Sample_Mean | |
|---|---|---|---|
| MA(1) | MA(1) | 3.3 | 3.2914 |
| MA(2) | MA(2) | 3.3 | 3.2259 |
| MA(3) | MA(3) | 3.3 | 2.6959 |
| MA(4) | MA(4) | 3.3 | 3.5594 |
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ma_high_variance)) {
acf(
ma_high_variance[[model]],
lag.max = 20,
main = paste("ACF:", model, "with variance 10")
)
}
par(old_par)
Observation and explanation: Increasing the white-noise variance increases the variability of the observations. The process mean remains 3.3. The theoretical ACF does not change, because multiplying all autocovariances by the same variance factor leaves their ratios unchanged. Sample ACF values will differ slightly because of random sampling variation.
The four MA models were simulated and examined. Their series fluctuate around the common mean of 3.3. The theoretical ACF of an MA(q) process cuts off after lag q, which makes the ACF useful for identifying the MA order. A larger sample size gives a more stable estimate of the process mean. Increasing the white-noise variance increases the spread of the series but does not change its theoretical mean or ACF.
To simulate four AR models using random initialization and a burn-in period of 200 observations, examine their time-series plots, estimate the means of stationary processes, and compare their sample ACF plots.
The four models are:
\[ \begin{aligned} (a)\quad Y_t &= 3+0.7Y_{t-1}+\varepsilon_t,\\ (b)\quad Y_t &= 3+1.01Y_{t-1}+\varepsilon_t,\\ (c)\quad Y_t &= 0.74Y_{t-1}-0.9Y_{t-2}+\varepsilon_t,\\ (d)\quad Y_t &= 0.74Y_{t-1}+0.9Y_{t-2}+\varepsilon_t. \end{aligned} \]
For each model, \(\varepsilon_t\sim N(0,1)\). We generate 700 observations, use the first 200 as burn-in, and retain the remaining 500.
generate_ar1 <- function(n, intercept, phi, burn = 200) {
total <- n + burn
e <- rnorm(total)
y <- numeric(total)
y[1] <- rnorm(1)
for (t in 2:total) {
y[t] <- intercept + phi * y[t - 1] + e[t]
}
y[(burn + 1):total]
}
generate_ar2 <- function(n, phi1, phi2, burn = 200) {
total <- n + burn
e <- rnorm(total)
y <- numeric(total)
y[1:2] <- rnorm(2)
for (t in 3:total) {
y[t] <- phi1 * y[t - 1] +
phi2 * y[t - 2] + e[t]
}
y[(burn + 1):total]
}
set.seed(2026)
ar_data <- list(
"AR(1), phi = 0.7" = generate_ar1(500, 3, 0.7),
"AR(1), phi = 1.01" = generate_ar1(500, 3, 1.01),
"AR(2), phi1 = 0.74, phi2 = -0.9" =
generate_ar2(500, 0.74, -0.9),
"AR(2), phi1 = 0.74, phi2 = 0.9" =
generate_ar2(500, 0.74, 0.9)
)
old_par <- par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
for (model in names(ar_data)) {
plot(
ar_data[[model]],
type = "l",
main = model,
xlab = "Time",
ylab = expression(Y[t])
)
}
par(old_par)
Observation:
The stationarity of the AR(2) models depends on the roots of their AR characteristic polynomial, not simply on whether the individual coefficients are smaller than 1.
For a stationary AR(1) process,
\[ \mu=\frac{c}{1-\phi}. \]
For model (a),
\[ \mu=\frac{3}{1-0.7}=10. \]
For an AR(2) process without an intercept,
\[ \mu=\phi_1\mu+\phi_2\mu, \]
so its stationary mean is zero, provided the process is stationary.
The stationarity checks give:
ar_mean_results <- data.frame(
Model = c(
"AR(1), phi = 0.7",
"AR(1), phi = 1.01",
"AR(2), phi1 = 0.74, phi2 = -0.9",
"AR(2), phi1 = 0.74, phi2 = 0.9"
),
Stationarity = c(
"Stationary",
"Non-stationary",
"Stationary",
"Non-stationary"
),
True_Mean = c(
"10",
"Not defined as a stationary mean",
"0",
"Not defined as a stationary mean"
),
Sample_Mean = sapply(ar_data, mean)
)
kable(ar_mean_results, digits = 4,
caption = "AR process mean estimates and stationarity")
| Model | Stationarity | True_Mean | Sample_Mean | |
|---|---|---|---|---|
| AR(1), phi = 0.7 | AR(1), phi = 0.7 | Stationary | 10 | 1.007760e+01 |
| AR(1), phi = 1.01 | AR(1), phi = 1.01 | Non-stationary | Not defined as a stationary mean | 6.349008e+04 |
| AR(2), phi1 = 0.74, phi2 = -0.9 | AR(2), phi1 = 0.74, phi2 = -0.9 | Stationary | 0 | -3.230000e-02 |
| AR(2), phi1 = 0.74, phi2 = 0.9 | AR(2), phi1 = 0.74, phi2 = 0.9 | Non-stationary | Not defined as a stationary mean | -1.347282e+97 |
Observation and explanation: Only models (a) and (c) have stationary process means that can be compared with their sample means. Models (b) and (d) are non-stationary, so their sample averages should not be interpreted as estimates of a constant stationary process mean.
old_par <- par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))
acf(
ar_data[["AR(1), phi = 0.7"]],
lag.max = 20,
main = "Model (a): AR(1), phi = 0.7"
)
acf(
ar_data[["AR(1), phi = 1.01"]],
lag.max = 20,
main = "Model (b): AR(1), phi = 1.01"
)
par(old_par)
Observation:
For model (a), the ACF generally decreases gradually as the lag increases, which is the usual pattern for a stationary AR(1) process.
For model (b), the coefficient is greater than 1, so the process is explosive. Its sample ACF may remain very high across many lags. The usual stationary AR(1) ACF interpretation does not apply to this model.
old_par <- par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))
acf(
ar_data[["AR(2), phi1 = 0.74, phi2 = -0.9"]],
lag.max = 20,
main = "Model (c): AR(2), phi2 = -0.9"
)
acf(
ar_data[["AR(2), phi1 = 0.74, phi2 = 0.9"]],
lag.max = 20,
main = "Model (d): AR(2), phi2 = 0.9"
)
par(old_par)
Observation:
Model (c) is stationary and can show a damped oscillating ACF pattern because of its AR coefficients.
Model (d) is non-stationary and may show a persistent or increasing dependence pattern. The stationary ACF interpretation is not valid for this model.
The ACF of an AR process generally tails off rather than cutting off after a fixed lag. Therefore, ACF plots alone are not usually sufficient to identify the order of an AR process.
The partial autocorrelation function (PACF) is commonly used as an additional identification tool for stationary AR processes. For an AR(p) process, the theoretical PACF cuts off after lag p.
The four AR models were simulated using random initial values and a burn-in period of 200 observations. Models (a) and (c) are stationary, whereas models (b) and (d) are non-stationary. The sample mean was compared with the theoretical mean only for stationary models. The ACF plots show that AR processes generally have a gradual tailing-off pattern, unlike the cutoff pattern of MA processes. Hence, the PACF is a useful additional tool for identifying stationary AR model orders.
The simulations illustrate the main differences between Moving Average and Autoregressive time series models. MA processes have an ACF that cuts off after their order, while stationary AR processes generally have an ACF that tails off. Increasing the sample size improves mean estimation, and increasing white-noise variance changes the spread of a series without changing its theoretical mean or normalized autocorrelation structure.