using StatsOrdinalPatterns, Random, Statistics, Printf, DataFrames, DistributionsReplication: Weiß and Kim (2024)
This page reproduces Table B.8 of Weiß and Kim (2024). The table reports simulated rejection rates of the four asymptotic spatial ordinal pattern (SOP) tests at level \(\alpha = 0.05\), together with the spatial autocorrelation at lag \(\mathbf{1}\) as a benchmark, for DGP 8 and growing field size.
Libraries
Statistics of the paper and their types in the package
Each column of Table B.8 corresponds to one chart_choice of test_sop, which returns the statistic, the asymptotic critical value, the \(p\) value and the reject decision. The benchmark column is test_sacf.
| Table B.8 column | Package call | Rejects \(H_0\) when | Label below |
|---|---|---|---|
| \(\hat{\tau}\) | test_sop(data, 1, 1; chart_choice=TauHat()) |
\(\lvert\hat{\tau}\rvert > c\) | tau_hat |
| \(\hat{\kappa}\) | test_sop(data, 1, 1; chart_choice=KappaHat()) |
\(\lvert\hat{\kappa}\rvert > c\) | kappa_hat |
| \(\tilde{\tau}\) | test_sop(data, 1, 1; chart_choice=TauTilde()) |
\(\lvert\tilde{\tau}\rvert > c\) | tau_tilde |
| \(\tilde{\kappa}\) | test_sop(data, 1, 1; chart_choice=KappaTilde()) |
\(\lvert\tilde{\kappa}\rvert > c\) | kappa_tilde |
| \(\hat{\rho}(\mathbf{1})\) | test_sacf(data, 1, 1) |
\(\lvert\hat{\rho}\rvert > c\) | rho_1 |
All four SOP statistics are linear in the same vector of type frequencies \(\hat{\mathbf{p}} = (\hat p_1, \hat p_2, \hat p_3)\) of the classical SOP classification, which stat_sop returns as its second output.
The header of the table asks for critical values computed with the exact variance. crit_val_sop uses exactly that: its variances carry the finite sample factor \(1 - 1/(2m) - 1/(2n)\), so no asymptotic simplification enters the critical values below.
Setup
DGP 8 is the spatial quadratic moving average process SQMA(1,1) with \((\beta_1, \beta_2, \beta_3) = (0.8, 0.8, 0.8)\) and i.i.d. standard normal errors. The SQMA11 type of the package holds it: dgp_params are the \(\beta_j\), eps_params are the exponents \((a, b, c)\) of the three error terms, written “\(1^a\,2^b\,3^c\)” in the paper, and M_rows, N_cols are the dimensions of the simulated field.
m and n in the table are the dimensions of the SOP matrix, so the field is \((m+1) \times (n+1)\).
Parameters of Table B.8 and one field of DGP 8
β = (0.8, 0.8, 0.8) # dependence parameters of Table B.8
level = 0.05 # nominal significance level
sizes = ((10, 10), (15, 15), (20, 20), (40, 25)) # SOP matrix dimensions (m, n)
reps = 10_000 # Monte Carlo replications
models = [
("DGP8-1", "1² 2² 3²", (2, 2, 2)),
("DGP8-2", "1² 2¹ 3²", (2, 1, 2)),
("DGP8-3", "1¹ 2¹ 3²", (1, 1, 2)),
("DGP8-4", "1² 2¹ 3¹", (2, 1, 1)),
]
charts = [
("tau_hat", TauHat()),
("kappa_hat", KappaHat()),
("tau_tilde", TauTilde()),
("kappa_tilde", KappaTilde()),
]
# One field of the DGP. `fill_mat_dgp_sop!` is the routine the ARL functions of the
# package use internally; it writes into the caller's buffers and returns a view of the
# field. `mat` receives the field, `mat_ma` the errors, `mat_ao` the additive outliers,
# which DGP 8 does not have.
sqma11(eps, m, n) = SQMA11(β, eps, m + 1, n + 1, Normal(0, 1), nothing)
buffers(dgp) = ntuple(_ -> zeros(dgp.M_rows + 1, dgp.N_cols + 1), 3)
draw_field!(dgp, mat, mat_ao, mat_ma) =
StatsOrdinalPatterns.fill_mat_dgp_sop!(dgp, dgp.dist, dgp.dist_ao, mat, mat_ao, mat_ma)A single test
One field of DGP 8 and one SOP test
Random.seed!(1)
dgp = sqma11((2, 2, 2), 10, 10)
Y = collect(draw_field!(dgp, buffers(dgp)...)) # copy out of the buffer
test_sop(Y, 1, 1; chart_choice=TauTilde(), alpha=level)SOPTestResult Chart: TauTilde() Statistic: -0.0633 ───────────────────────────── Asymptotic test Critical value: 0.1004 p-value: 0.2162 Reject H₀: false
The SACF benchmark on the same field
test_sacf(Y, 1, 1; alpha=level)SACFTestResult
Statistic: -0.1425
─────────────────────────────
Asymptotic test
Critical value: 0.1782
p-value: 0.1171
Reject H₀: false
Running the power study
crit_val_sop and crit_val_sacf return the critical values alone. They depend on the field size and the level, not on the data, so they are computed once per row of the table. test_sop additionally computes the \(p\) value, which a power study does not use. Each replication then costs one pass over the field: stat_sop counts the SOP frequencies once, and the four statistics are read off the resulting \(\hat{\mathbf{p}}\) with chart_stat_sop.
Rejection rates of the five tests for one field size
function power(m, n, eps, reps; level=0.05)
dgp = sqma11(eps, m, n)
M, N = dgp.M_rows, dgp.N_cols
crits = [crit_val_sop(M, N, 1, 1; chart_choice=cc, alpha=level) for (_, cc) in charts]
crit_rho = crit_val_sacf(M, N; alpha=level)
tasks = map(Iterators.partition(1:reps, cld(reps, Threads.nthreads()))) do idx
Threads.@spawn begin
bufs = buffers(dgp) # one set of buffers per task
hits = zeros(Int, length(charts) + 1)
for _ in idx
Y = draw_field!(dgp, bufs...)
p̂ = stat_sop(Y, 1, 1; chart_choice=Shannon(base=exp(1)))[2]
for (j, (_, cc)) in enumerate(charts)
hits[j] += abs(chart_stat_sop(p̂, cc)) > crits[j]
end
hits[end] += abs(stat_sacf(Y, 1, 1)) > crit_rho
end
hits
end
end
return sum(fetch.(tasks)) ./ reps
endThe shortcut has to give the same statistic and the same decision as test_sop and test_sacf, not merely a similar one:
Check against test_sop and test_sacf
let dgp = sqma11((1, 1, 2), 20, 20), M = 21, N = 21
Y = collect(draw_field!(dgp, buffers(dgp)...)) # copy out of the buffer
p̂ = stat_sop(Y, 1, 1; chart_choice=Shannon(base=exp(1)))[2]
ok = all(
begin
r = test_sop(Y, 1, 1; chart_choice=cc, alpha=level)
s = chart_stat_sop(p̂, cc)
s ≈ r.stat && crit_val_sop(M, N, 1, 1; chart_choice=cc, alpha=level) ≈ r.asymp_crit &&
(abs(s) > r.asymp_crit) == r.asymp_reject
end for (_, cc) in charts
)
ok &= (abs(stat_sacf(Y, 1, 1)) > crit_val_sacf(M, N; alpha=level)) ==
test_sacf(Y, 1, 1; alpha=level).asymp_reject
(shortcut_matches_test_sop=ok,)
end(shortcut_matches_test_sop = true,)
With reps = 10_000 the page runs in a few seconds and the standard error of a rejection rate is at most \(\sqrt{0.25 / 10^4} = 0.005\).
Table B.8: DGP 8, \((\beta_1, \beta_2, \beta_3) = (0.8, 0.8, 0.8)\)
Run the study for every model and field size
res = Dict(
(name, m, n) => power(m, n, eps, reps; level=level)
for (name, _, eps) in models for (m, n) in sizes
)| Row | model | label | m | n | tau_hat | kappa_hat | tau_tilde | kappa_tilde | rho_1 |
|---|---|---|---|---|---|---|---|---|---|
| String | String | Int64 | Int64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | DGP8-1 | 1² 2² 3² | 10 | 10 | 0.142 | 0.165 | 0.25 | 0.059 | 0.068 |
| 2 | DGP8-1 | 1² 2² 3² | 15 | 15 | 0.223 | 0.362 | 0.469 | 0.071 | 0.091 |
| 3 | DGP8-1 | 1² 2² 3² | 20 | 20 | 0.352 | 0.574 | 0.766 | 0.064 | 0.094 |
| 4 | DGP8-1 | 1² 2² 3² | 40 | 25 | 0.716 | 0.93 | 0.991 | 0.079 | 0.107 |
| 5 | DGP8-2 | 1² 2¹ 3² | 10 | 10 | 0.249 | 0.412 | 0.591 | 0.076 | 0.083 |
| 6 | DGP8-2 | 1² 2¹ 3² | 15 | 15 | 0.412 | 0.806 | 0.926 | 0.075 | 0.103 |
| 7 | DGP8-2 | 1² 2¹ 3² | 20 | 20 | 0.65 | 0.967 | 0.997 | 0.073 | 0.115 |
| 8 | DGP8-2 | 1² 2¹ 3² | 40 | 25 | 0.958 | 1.0 | 1.0 | 0.08 | 0.126 |
| 9 | DGP8-3 | 1¹ 2¹ 3² | 10 | 10 | 0.269 | 0.196 | 0.382 | 0.088 | 0.077 |
| 10 | DGP8-3 | 1¹ 2¹ 3² | 15 | 15 | 0.447 | 0.446 | 0.702 | 0.12 | 0.096 |
| 11 | DGP8-3 | 1¹ 2¹ 3² | 20 | 20 | 0.696 | 0.711 | 0.943 | 0.145 | 0.097 |
| 12 | DGP8-3 | 1¹ 2¹ 3² | 40 | 25 | 0.975 | 0.984 | 1.0 | 0.295 | 0.113 |
| 13 | DGP8-4 | 1² 2¹ 3¹ | 10 | 10 | 0.161 | 0.126 | 0.215 | 0.064 | 0.468 |
| 14 | DGP8-4 | 1² 2¹ 3¹ | 15 | 15 | 0.238 | 0.289 | 0.419 | 0.075 | 0.883 |
| 15 | DGP8-4 | 1² 2¹ 3¹ | 20 | 20 | 0.399 | 0.453 | 0.694 | 0.09 | 0.991 |
| 16 | DGP8-4 | 1² 2¹ 3¹ | 40 | 25 | 0.753 | 0.848 | 0.974 | 0.13 | 1.0 |
The paper prints the maximal rejection rate of each row in bold:
| Row | model | m | n | max_stat | rate |
|---|---|---|---|---|---|
| String | Int64 | Int64 | String | Float64 | |
| 1 | DGP8-1 | 10 | 10 | tau_tilde | 0.25 |
| 2 | DGP8-1 | 15 | 15 | tau_tilde | 0.469 |
| 3 | DGP8-1 | 20 | 20 | tau_tilde | 0.766 |
| 4 | DGP8-1 | 40 | 25 | tau_tilde | 0.991 |
| 5 | DGP8-2 | 10 | 10 | tau_tilde | 0.591 |
| 6 | DGP8-2 | 15 | 15 | tau_tilde | 0.926 |
| 7 | DGP8-2 | 20 | 20 | tau_tilde | 0.997 |
| 8 | DGP8-2 | 40 | 25 | kappa_hat, tau_tilde | 1.0 |
| 9 | DGP8-3 | 10 | 10 | tau_tilde | 0.382 |
| 10 | DGP8-3 | 15 | 15 | tau_tilde | 0.702 |
| 11 | DGP8-3 | 20 | 20 | tau_tilde | 0.943 |
| 12 | DGP8-3 | 40 | 25 | tau_tilde | 1.0 |
| 13 | DGP8-4 | 10 | 10 | rho_1 | 0.468 |
| 14 | DGP8-4 | 15 | 15 | rho_1 | 0.883 |
| 15 | DGP8-4 | 20 | 20 | rho_1 | 0.991 |
| 16 | DGP8-4 | 40 | 25 | rho_1 | 1.0 |
Comparison with the published rates
largest absolute deviation: 0.012
mean absolute deviation: 0.003
Monte Carlo standard error: at most 0.005
The five largest deviations across all 80 entries:
| Row | model | m | n | statistic | paper | replicated | dev |
|---|---|---|---|---|---|---|---|
| String | Int64 | Int64 | String | Float64 | Float64 | Float64 | |
| 1 | DGP8-3 | 20 | 20 | tau_hat | 0.708 | 0.696 | -0.012 |
| 2 | DGP8-3 | 15 | 15 | kappa_hat | 0.457 | 0.446 | -0.011 |
| 3 | DGP8-2 | 15 | 15 | tau_hat | 0.422 | 0.412 | -0.01 |
| 4 | DGP8-2 | 10 | 10 | kappa_hat | 0.403 | 0.412 | 0.009 |
| 5 | DGP8-3 | 20 | 20 | kappa_tilde | 0.154 | 0.145 | -0.009 |