using StatsOrdinalPatterns, Random, Statistics, Printf, DataFrames
import StatsOrdinalPatterns: init_dgp_op!Replication: Adämmer and Weiß (2026)
This page reproduces Table B.20 in the appendix of Adämmer and Weiß (2026): rejection rates of the seven Box-Ljung type ordinal pattern (BL-OP) tests with \(m = 3\) and of the Box-Ljung test on the sample autocorrelations (BL-ACF) at level \(\alpha = 0.05\), for maximal delays \(w = 1, \ldots, 5\) and sample sizes \(T\).
Libraries
Statistics of the paper and their types in the package
| Table B.20 column | chart_choice |
Summand for delay \(d\) |
|---|---|---|
| \(\widehat{H}_{\text{BL}}\) | Shannon(base=exp(1)) |
\(\tfrac{2}{6}\, n_d \big(\ln 6 - \widehat{H}^{(d)}\big)\) |
| \(\widehat{H}^{\text{ex}}_{\text{BL}}\) | ShannonExtropy(base=exp(1)) |
\(\tfrac{10}{6}\, n_d \big(5 \ln \tfrac{6}{5} - \widehat{H}_{\text{ex}}^{(d)}\big)\) |
| \(\widehat{\Delta}^2_{\text{BL}}\) | DistanceToWhiteNoise() |
\(n_d\, \widehat{\Delta}^{2\,(d)}\) |
| \(\widehat{\beta}_{\text{BL}}\) | UpDownBalance() |
\(n_d\, \big(\hat{\beta}^{(d)}\big)^2\) |
| \(\widehat{\tau}_{\text{BL}}\) | Persistence() |
\(n_d\, \big(\hat{\tau}^{(d)}\big)^2\) |
| \(\widehat{\gamma}_{\text{BL}}\) | RotationalAsymmetry() |
\(n_d\, \big(\hat{\gamma}^{(d)}\big)^2\) |
| \(\widehat{\delta}_{\text{BL}}\) | UpDownScaling() |
\(n_d\, \big(\hat{\delta}^{(d)}\big)^2\) |
| \(\widehat{\rho}_{\text{BL}}\) | stat_acf |
\(T(T+2)\, \hat{\rho}(d)^2 / (T - d)\) |
Here \(n_d = T - (m-1)d\). stat_op_bp(data, w; chart_choice, m, ljung_box=true) sums the summands over \(d = 1, \ldots, w\), and crit_val_op_bp(w; chart_choice, m) returns the critical value. The BL-ACF statistic is compared with the \(\chi^2(w)\) quantile.
Setup
The DGP is \(Y_t = X_t + O_t\) with \(X_t = 0.5\, X_{t-2} + \epsilon_t\), \(\epsilon_t \sim \text{i.i.d. } N(0,1)\), and additive outliers \(O_t \sim \text{i.i.d. } 10 \cdot \text{Bernoulli}(0.1)\). As \(\alpha_1 = 0\), the values at odd and at even times are two independent AR1(0.5, Normal(0, 1)) processes, which init_dgp_op! fills in place.
dgp = AR1(0.5, Normal(0, 1))
dist_ao = 10 * Bernoulli(0.1) # additive outliers
m = 3 # pattern length
level = 0.05 # nominal significance level
ws = 1:5 # maximal delays
Ts = (100, 250, 500, 1000) # sample sizes
reps = 10_000 # Monte Carlo replications
function simulate!(x)
init_dgp_op!(dgp, view(x, 1:2:length(x)), dgp.dist, 1) # odd times
init_dgp_op!(dgp, view(x, 2:2:length(x)), dgp.dist, 1) # even times
for t in eachindex(x)
x[t] += rand(dist_ao)
end
return x
end
charts = [
("H", Shannon(base=exp(1))),
("Hex", ShannonExtropy(base=exp(1))),
("Δ²", DistanceToWhiteNoise()),
("β", UpDownBalance()),
("τ", Persistence()),
("γ", RotationalAsymmetry()),
("δ", UpDownScaling()),
]A single test
Random.seed!(1)
x = simulate!(zeros(500))
test_op_bp(x, 3; chart_choice=Persistence(), m=m, ljung_box=true)OPBPTestResult Chart: Persistence() Statistic: 4.1447 ───────────────────────────── Asymptotic test Critical value: 1.4059 p-value: not available (use test_op_bp_bootstrap) Reject H₀: true
Running the power study
The BL-OP statistic for \(w\) is the one for \(w - 1\) plus the summand of delay \(w\), and all summands of delay \(d\) are functions of the pattern frequencies returned by stat_op. One pass over \(d = 1, \ldots, 5\) therefore yields all charts and all \(w\).
# summand of delay d, as in the table above, without the weight n_d
bl_term(::Shannon, s) = (log(6) - s) / 3
bl_term(::ShannonExtropy, s) = 5 / 3 * (5 * log(6 / 5) - s)
bl_term(::DistanceToWhiteNoise, s) = s
bl_term(_, s) = s^2
function power(T, reps)
crits = [crit_val_op_bp(w; chart_choice=cc, m=m) for (_, cc) in charts, w in ws]
crits_acf = [quantile(Chisq(w), 1 - level) for w in ws]
tasks = map(Iterators.partition(1:reps, cld(reps, Threads.nthreads()))) do idx
Threads.@spawn begin
x = Vector{Float64}(undef, T)
bl = zeros(length(charts))
hits = zeros(Int, length(charts) + 1, length(ws))
for _ in idx
simulate!(x)
fill!(bl, 0.0)
q = 0.0
for d in ws
n = T - (m - 1) * d
p̂ = stat_op(x; chart_choice=Shannon(base=exp(1)), m=m, d=d)[2]
for (j, (_, cc)) in enumerate(charts)
bl[j] += n * bl_term(cc, chart_stat_op(p̂, cc))
hits[j, d] += bl[j] > crits[j, d]
end
q += T * (T + 2) * stat_acf(x, d)^2 / (T - d)
hits[end, d] += q > crits_acf[d]
end
end
hits
end
end
return sum(fetch.(tasks)) ./ reps
endCheck that the shortcut equals stat_op_bp:
let T = 400, x = simulate!(zeros(T))
ok = all(
begin
s = sum((T - (m - 1) * d) *
bl_term(cc, chart_stat_op(stat_op(x; chart_choice=cc, m=m, d=d)[2], cc))
for d in 1:w)
s ≈ stat_op_bp(x, w; chart_choice=cc, m=m, ljung_box=true)
end for (_, cc) in charts, w in ws
)
(shortcut_matches_stat_op_bp=ok,)
end(shortcut_matches_stat_op_bp = true,)
Table B.20: AR(2) with \(\alpha_2 = 0.5\) and additive outliers
Random.seed!(2026)
res = Dict(T => power(T, reps) for T in Ts)
names = vcat([n for (n, _) in charts], "ACF")| Row | w | T | H | Hex | Δ² | β | τ | γ | δ | ACF |
|---|---|---|---|---|---|---|---|---|---|---|
| Int64 | Int64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 1 | 100 | 0.214 | 0.179 | 0.178 | 0.02 | 0.44 | 0.05 | 0.048 | 0.059 |
| 2 | 1 | 250 | 0.523 | 0.496 | 0.498 | 0.027 | 0.777 | 0.058 | 0.04 | 0.066 |
| 3 | 1 | 500 | 0.849 | 0.837 | 0.839 | 0.028 | 0.964 | 0.062 | 0.045 | 0.069 |
| 4 | 1 | 1000 | 0.993 | 0.992 | 0.992 | 0.027 | 1.0 | 0.057 | 0.046 | 0.063 |
| 5 | 2 | 100 | 0.224 | 0.209 | 0.208 | 0.046 | 0.517 | 0.049 | 0.036 | 0.09 |
| 6 | 2 | 250 | 0.59 | 0.579 | 0.579 | 0.043 | 0.881 | 0.052 | 0.044 | 0.146 |
| 7 | 2 | 500 | 0.913 | 0.91 | 0.91 | 0.043 | 0.99 | 0.056 | 0.04 | 0.237 |
| 8 | 2 | 1000 | 0.999 | 0.999 | 0.999 | 0.046 | 1.0 | 0.055 | 0.042 | 0.429 |
| 9 | 3 | 100 | 0.215 | 0.198 | 0.198 | 0.049 | 0.477 | 0.054 | 0.043 | 0.085 |
| 10 | 3 | 250 | 0.54 | 0.526 | 0.526 | 0.049 | 0.851 | 0.054 | 0.048 | 0.134 |
| 11 | 3 | 500 | 0.882 | 0.877 | 0.878 | 0.044 | 0.988 | 0.056 | 0.041 | 0.204 |
| 12 | 3 | 1000 | 0.998 | 0.998 | 0.998 | 0.049 | 1.0 | 0.054 | 0.048 | 0.375 |
| 13 | 4 | 100 | 0.201 | 0.186 | 0.184 | 0.058 | 0.469 | 0.056 | 0.042 | 0.084 |
| 14 | 4 | 250 | 0.517 | 0.507 | 0.506 | 0.055 | 0.858 | 0.053 | 0.044 | 0.136 |
| 15 | 4 | 500 | 0.868 | 0.863 | 0.864 | 0.048 | 0.99 | 0.054 | 0.041 | 0.224 |
| 16 | 4 | 1000 | 0.999 | 0.999 | 0.999 | 0.052 | 1.0 | 0.054 | 0.044 | 0.406 |
| 17 | 5 | 100 | 0.206 | 0.19 | 0.188 | 0.065 | 0.443 | 0.058 | 0.05 | 0.081 |
| 18 | 5 | 250 | 0.488 | 0.477 | 0.476 | 0.058 | 0.834 | 0.056 | 0.049 | 0.129 |
| 19 | 5 | 500 | 0.845 | 0.84 | 0.84 | 0.05 | 0.987 | 0.055 | 0.046 | 0.208 |
| 20 | 5 | 1000 | 0.997 | 0.997 | 0.997 | 0.055 | 1.0 | 0.055 | 0.048 | 0.378 |
Comparison with the published rates
Published rates and comparison table
# published values, ordered (H, Hex, Δ², β, τ, γ, δ, ACF)
published = Dict(
(1, 100) => (0.222, 0.190, 0.189, 0.024, 0.436, 0.052, 0.050, 0.057),
(1, 250) => (0.515, 0.484, 0.487, 0.029, 0.774, 0.055, 0.041, 0.063),
(1, 500) => (0.845, 0.833, 0.835, 0.024, 0.966, 0.057, 0.045, 0.064),
(1, 1000) => (0.993, 0.992, 0.992, 0.027, 1.000, 0.053, 0.044, 0.065),
(2, 100) => (0.235, 0.218, 0.217, 0.049, 0.516, 0.052, 0.042, 0.091),
(2, 250) => (0.578, 0.565, 0.566, 0.042, 0.874, 0.052, 0.041, 0.146),
(2, 500) => (0.912, 0.909, 0.909, 0.044, 0.992, 0.053, 0.041, 0.236),
(2, 1000) => (0.999, 0.999, 0.999, 0.044, 1.000, 0.054, 0.041, 0.426),
(3, 100) => (0.223, 0.204, 0.203, 0.050, 0.480, 0.057, 0.046, 0.087),
(3, 250) => (0.528, 0.513, 0.513, 0.048, 0.846, 0.056, 0.044, 0.130),
(3, 500) => (0.879, 0.874, 0.874, 0.046, 0.988, 0.053, 0.044, 0.209),
(3, 1000) => (0.998, 0.998, 0.998, 0.046, 1.000, 0.055, 0.045, 0.375),
(4, 100) => (0.210, 0.195, 0.193, 0.057, 0.472, 0.057, 0.043, 0.086),
(4, 250) => (0.507, 0.496, 0.495, 0.053, 0.854, 0.052, 0.041, 0.135),
(4, 500) => (0.869, 0.865, 0.865, 0.051, 0.990, 0.053, 0.042, 0.222),
(4, 1000) => (0.998, 0.998, 0.998, 0.051, 1.000, 0.052, 0.043, 0.402),
(5, 100) => (0.210, 0.194, 0.192, 0.064, 0.449, 0.059, 0.049, 0.081),
(5, 250) => (0.477, 0.465, 0.465, 0.058, 0.831, 0.056, 0.048, 0.126),
(5, 500) => (0.838, 0.834, 0.834, 0.056, 0.988, 0.054, 0.046, 0.204),
(5, 1000) => (0.996, 0.996, 0.996, 0.054, 1.000, 0.052, 0.046, 0.372),
)
cmp = DataFrame(w=Int[], T=Int[], statistic=String[], paper=Float64[],
replicated=Float64[], dev=Float64[])
for w in ws, T in Ts, (j, nm) in enumerate(names)
r = round(res[T][j, w], digits=3)
push!(cmp, (w, T, nm, published[(w, T)][j], r, round(r - published[(w, T)][j], digits=3)))
end
cmp| Row | w | T | statistic | paper | replicated | dev |
|---|---|---|---|---|---|---|
| Int64 | Int64 | String | Float64 | Float64 | Float64 | |
| 1 | 1 | 100 | H | 0.222 | 0.214 | -0.008 |
| 2 | 1 | 100 | Hex | 0.19 | 0.179 | -0.011 |
| 3 | 1 | 100 | Δ² | 0.189 | 0.178 | -0.011 |
| 4 | 1 | 100 | β | 0.024 | 0.02 | -0.004 |
| 5 | 1 | 100 | τ | 0.436 | 0.44 | 0.004 |
| 6 | 1 | 100 | γ | 0.052 | 0.05 | -0.002 |
| 7 | 1 | 100 | δ | 0.05 | 0.048 | -0.002 |
| 8 | 1 | 100 | ACF | 0.057 | 0.059 | 0.002 |
| 9 | 1 | 250 | H | 0.515 | 0.523 | 0.008 |
| 10 | 1 | 250 | Hex | 0.484 | 0.496 | 0.012 |
| 11 | 1 | 250 | Δ² | 0.487 | 0.498 | 0.011 |
| 12 | 1 | 250 | β | 0.029 | 0.027 | -0.002 |
| 13 | 1 | 250 | τ | 0.774 | 0.777 | 0.003 |
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
| 149 | 5 | 500 | τ | 0.988 | 0.987 | -0.001 |
| 150 | 5 | 500 | γ | 0.054 | 0.055 | 0.001 |
| 151 | 5 | 500 | δ | 0.046 | 0.046 | 0.0 |
| 152 | 5 | 500 | ACF | 0.204 | 0.208 | 0.004 |
| 153 | 5 | 1000 | H | 0.996 | 0.997 | 0.001 |
| 154 | 5 | 1000 | Hex | 0.996 | 0.997 | 0.001 |
| 155 | 5 | 1000 | Δ² | 0.996 | 0.997 | 0.001 |
| 156 | 5 | 1000 | β | 0.054 | 0.055 | 0.001 |
| 157 | 5 | 1000 | τ | 1.0 | 1.0 | 0.0 |
| 158 | 5 | 1000 | γ | 0.052 | 0.055 | 0.003 |
| 159 | 5 | 1000 | δ | 0.046 | 0.048 | 0.002 |
| 160 | 5 | 1000 | ACF | 0.372 | 0.378 | 0.006 |
largest absolute deviation: 0.014
mean absolute deviation: 0.003
Monte Carlo standard error: at most 0.005