Normality tests: formula and validation ledger#
This ledger covers pysht.normality. The pinned SHT 0.1.9 implementation was
audited at commit 4e29cda, but its output is used only for differential
investigation. Primary formulas, independent NumPy calculations, SciPy’s
Shapiro implementation, invariance checks, and null simulation are the
correctness oracles.
Shapiro–Wilk and Shapiro–Francia#
shapiro_wilk delegates the statistic and p-value approximation to
scipy.stats.shapiro after a numerically safe location/scale normalization.
It accepts \(3\le n\le5000\). This limit avoids presenting the Royston p-value
approximation beyond its validated range.
For ordered observations \(x_{(i)}\), shapiro_francia uses
For \(5\le n\le5000\), its p-value follows Royston’s normal approximation to \(\log(1-W')\), with
The fixed fixture agrees with an independent normal-score correlation to
approximately \(10^{-14}\). Tests also cover the exact \(W'=1\) boundary,
reflection, translation, scales near \(10^{100}\), sample-size endpoints, and
constant samples. Standardization subtracts a sample anchor before dividing by
its scale. This ordering is necessary: scale-first centering changed a
Shapiro–Francia fixture near \(10^{100}\) by more than 0.01, even though all
within-sample differences remained representable.
A 20,000-sample \(N(0,1)\) null audit produced these raw rejection counts and rates:
seed |
\(n\) |
procedure |
0.01 count/rate |
0.05 count/rate |
0.10 count/rate |
|---|---|---|---|---|---|
20260815 |
20 |
Shapiro–Wilk |
184 (0.00920) |
1,034 (0.05170) |
2,030 (0.10150) |
20260815 |
20 |
Shapiro–Francia |
216 (0.01080) |
1,056 (0.05280) |
2,078 (0.10390) |
20260815 |
100 |
Shapiro–Wilk |
199 (0.00995) |
963 (0.04815) |
1,984 (0.09920) |
20260815 |
100 |
Shapiro–Francia |
218 (0.01090) |
1,045 (0.05225) |
2,003 (0.10015) |
20260816 |
20 |
Shapiro–Wilk |
162 (0.00810) |
925 (0.04625) |
2,011 (0.10055) |
20260816 |
20 |
Shapiro–Francia |
192 (0.00960) |
997 (0.04985) |
2,060 (0.10300) |
20260816 |
100 |
Shapiro–Wilk |
204 (0.01020) |
1,009 (0.05045) |
2,036 (0.10180) |
20260816 |
100 |
Shapiro–Francia |
217 (0.01085) |
1,065 (0.05325) |
2,060 (0.10300) |
For each procedure and seed, a fresh numpy.random.default_rng(seed) generated
the \(n=20\) block first and continued into the \(n=100\) block. Every replication
called the corresponding public function, and rejection meant
result.pvalue < alpha. The repository release-simulation runner preserves
that stream prelude and reproduces these counts exactly.
Every entry satisfies the release tolerance specified by the project plan.
Primary sources are Shapiro and Wilk (1965), Shapiro and Francia (1972), and the Royston approximations used by the implementations.
Moment statistics#
Let
The Jarque–Bera statistic is
Urzúa’s adjusted statistic replaces the asymptotic centers and variances by
The robust statistic uses
in the moment denominators and
The displayed Gel–Gastwirth statistic (2008, p. 31) uses 64. SHT 0.1.9
defaults to 24 in both its signature and help page; pySHT deliberately follows
the primary procedure rather than preserving that legacy defect. Custom positive constants remain
available with Monte Carlo calibration, but are rejected with the chi-square
option because the latter requires the published standardization. The AJB and RJB default calls in SHT
0.1.9 also fail before computation because a vector-valued default is compared
inside a scalar if; both Python defaults are executable.
Primary sources are Jarque and Bera (1980), Urzúa (1996), and Gel and Gastwirth (2008).
Calibration policy#
All three moment tests default to a parametric Monte Carlo normal null. The observed statistic is compared with \(B\) independent null statistics using
where ties count in the upper tail. Results include \(B\), \(b\), the
conditional Monte Carlo standard error, and a 95% Clopper–Pearson interval for
the underlying tail probability. An integer rng seed replays the full
calibration without touching NumPy’s global random state. Literal seeded
simulations independently reproduce the counts for JB, AJB, and RJB.
The explicit calibration="asymptotic" option reports a chi-square
approximation with two degrees of freedom. It has no advertised finite-sample
size guarantee. The following seeded \(N(0,1)\) diagnostic at nominal 0.05
illustrates why it is not the default; every cell is count/20,000 followed by
the rate:
\(n\) |
seed |
JB |
AJB |
RJB |
|---|---|---|---|---|
20 |
20260827 |
441 (0.02205) |
1,185 (0.05925) |
1,192 (0.05960) |
20 |
20260828 |
506 (0.02530) |
1,186 (0.05930) |
1,191 (0.05955) |
100 |
20260827 |
799 (0.03995) |
1,051 (0.05255) |
1,134 (0.05670) |
100 |
20260828 |
783 (0.03915) |
1,041 (0.05205) |
1,064 (0.05320) |
2,000 |
20260827 |
940 (0.04700) |
953 (0.04765) |
821 (0.04105) |
2,000 |
20260828 |
978 (0.04890) |
992 (0.04960) |
876 (0.04380) |
The generator was reset from the displayed integer seed for each \((n,\) seed\()\) design, then 20,000 samples were consumed in row order. The production batched row-statistic kernels and chi-square survival function were used; fixed draws were also checked against each public function’s statistic and asymptotic p-value. This is a replayable diagnostic, not a claimed release gate.
No asymptotic regime is marketed as validated until it passes both release seeds at nominal levels 0.01, 0.05, and 0.10.
Targeted alternative-power audit#
All five public routines were evaluated on the same 2,000 samples of size 100
from a Student \(t_3\) alternative, generated in row order by
numpy.random.default_rng(20260829). The Shapiro procedures were called
publicly once per row. For the three authoritative Monte Carlo moment tests, a
separate numpy.random.default_rng(20260830) generated a shared bank of
100,000 \(N(0,1)\) samples; ties entered the upper-tail exceedance count and
\((b+1)/(100000+1)<0.05\) defined rejection. Their production batched kernels
were used for tractability and fixed draws were matched to the complete public
Monte Carlo path.
Procedure |
Rejections/2,000 |
Rate |
|---|---|---|
|
1,749 |
0.8745 |
|
1,809 |
0.9045 |
|
1,807 |
0.9035 |
|
1,810 |
0.9050 |
|
1,849 |
0.9245 |
This named-seed audit supplies a direction-and-power check for one heavy-tailed alternative. It does not claim a minimum power over every nonnormal distribution.
Legacy mapping#
pySHT |
SHT 0.1.9 |
|---|---|
|
|
|
|
|
|
|
|
|
|