SignedRankMargin
Exclusion count for one-sample signed-rank based bounds.
- Purpose — determines extreme pairwise averages to exclude when constructing bounds
- Based on — Wilcoxon signed-rank distribution under weak symmetry
- Returns — total margin split evenly between lower and upper tails
- Used by — to select appropriate order statistics
- Complexity — exact for , approximated for larger
- Domain — ,
- Unit — count
- Note — assumes weak symmetry and weak continuity
Properties
- Bounds
- Monotonicity lower misrate smaller margin wider bounds
Example
SignedRankMargin(10, 0.05) = 18SignedRankMargin(30, 1e-4) = 112SignedRankMargin(100, 1e-6) = 706
This is a supporting function that uses internally, so most users do not need to call it directly. It calculates how many extreme pairwise averages should be excluded when constructing bounds, based on sample size and the desired error rate. A lower misrate (higher confidence) results in a smaller margin, which produces wider bounds. The function automatically chooses between exact computation for small samples and a fast approximation for large samples.
Algorithm
The function determines how many extreme pairwise averages to exclude when constructing bounds around . Given a sample , the estimator computes all pairwise averages for and sorts them. The bounds select specific order statistics from this sorted sequence: . The challenge lies in determining which order statistics produce bounds that contain the true center with probability .
The margin function is the one-sample analog of . While uses the Mann-Whitney distribution for two-sample comparisons, uses the Wilcoxon signed-rank distribution for one-sample inference. Under the weak symmetry assumption, the signed-rank statistic has a known distribution that enables exact computation of bounds coverage.
For symmetric distributions, consider the signs of deviations from the center. The Wilcoxon signed-rank statistic sums the ranks of positive deviations:
where is the rank of among all , and is the true center. Under symmetry, each deviation is equally likely to be positive or negative, giving a discrete distribution over .
The connection to pairwise averages is fundamental: the -th order statistic of sorted pairwise averages corresponds to a specific threshold of the signed-rank statistic. By computing the distribution of , we determine which order statistics provide bounds with the desired coverage.
Two computational approaches provide the distribution of : exact calculation for small samples and approximation for large samples.
Exact method
Small sample sizes allow exact computation without approximation. The Wilcoxon signed-rank distribution has equally likely outcomes under symmetry, corresponding to all possible sign patterns for deviations from the center.
Dynamic programming builds the probability mass function efficiently. Define as the number of sign patterns producing signed-rank statistic equal to . The recurrence considers whether to include rank in the positive sum:
with base case . This builds the distribution incrementally, rank by rank.
The algorithm computes cumulative probabilities sequentially until the threshold is exceeded. For symmetric two-tailed bounds, the margin becomes . Memory is for storing the probability array, and time is for the full computation.
The sequential computation performs well for small misrates. For , the threshold typically remains small, requiring only iterations through the lower tail regardless of sample size.
Approximate method
Large samples make exact computation impractical. For , the Wilcoxon distribution is approximated using an Edgeworth expansion.
Under symmetry, the signed-rank statistic has:
The basic normal approximation uses these moments directly, but underestimates tail probabilities for moderate sample sizes.
The Edgeworth expansion refines this through moment-based corrections. The fourth central moment of is:
This enables kurtosis correction:
The approximated CDF becomes:
where includes a continuity correction.
Binary search locates the threshold efficiently. Each CDF evaluation costs , and evaluations suffice. The approximate method completes in constant time regardless of sample size.
The toolkit uses exact computation for and approximation for . At , the exact method requires arrays of size (), which remains practical on modern hardware. The transition at is determined by the requirement that fits in a 64-bit integer. The approximation achieves sub-1% accuracy for , making the transition smooth.
Minimum achievable misrate
The parameter controls how many extreme pairwise averages the bounds exclude. However, sample size limits how small misrate can meaningfully become.
The most extreme configuration occurs when all signs are positive (or all negative): or . Under symmetry, this extreme occurs with probability:
Setting makes bounds construction problematic. Pragmastat rejects such requests with a domain error.
The table below shows for small sample sizes:
| 2 | 3 | 5 | 7 | 10 | |
|---|---|---|---|---|---|
| 0.5 | 0.25 | 0.0625 | 0.0156 | 0.00195 | |
| max conf | 50% | 75% | 93.75% | 98.4% | 99.8% |
For meaningful bounds construction, choose . With , standard choices like become feasible. With , even is achievable.
using JetBrains.Annotations;
using Pragmastat.Exceptions;
namespace Pragmastat.Functions;
/// <summary>
/// SignedRankMargin function for one-sample bounds.
/// One-sample analog of PairwiseMargin using Wilcoxon signed-rank distribution.
/// </summary>
/// <param name="threshold">Maximum n for exact computation; larger n uses approximation</param>
internal class SignedRankMargin(int threshold = SignedRankMargin.MaxExactSize)
{
public static readonly SignedRankMargin Instance = new();
private const int MaxExactSize = 63;
[PublicAPI]
public int Calc(int n, double misrate)
{
if (n <= 0)
throw AssumptionException.Domain(Subject.X);
if (double.IsNaN(misrate) || misrate < 0 || misrate > 1)
throw AssumptionException.Domain(Subject.Misrate);
double minMisrate = MinAchievableMisrate.OneSample(n);
if (misrate < minMisrate)
throw AssumptionException.Domain(Subject.Misrate);
return n <= threshold
? CalcExact(n, misrate)
: CalcApprox(n, misrate);
}
internal int CalcExact(int n, double misrate)
{
int raw = CalcExactRaw(n, misrate / 2);
return checked(raw * 2);
}
internal int CalcApprox(int n, double misrate)
{
long raw = CalcApproxRaw(n, misrate / 2);
long margin = raw * 2;
if (margin > int.MaxValue)
throw new OverflowException($"Signed-rank margin exceeds supported range for n={n}");
return (int)margin;
}
/// <summary>
/// Compute one-sided margin using exact Wilcoxon signed-rank distribution.
/// Uses dynamic programming to compute the CDF.
/// </summary>
private static int CalcExactRaw(int n, double p)
{
ulong total = 1UL << n;
long maxW = (long)n * (n + 1) / 2;
var count = new ulong[maxW + 1];
count[0] = 1;
for (int i = 1; i <= n; i++)
{
for (long w = Min(maxW, (long)i * (i + 1) / 2); w >= i; w--)
count[w] += count[w - i];
}
ulong cumulative = 0;
for (int w = 0; w <= maxW; w++)
{
cumulative += count[w];
double cdf = (double)cumulative / total;
if (cdf >= p)
return w;
}
return (int)maxW;
}
/// <summary>
/// Compute one-sided margin using Edgeworth approximation for large n.
/// </summary>
private static long CalcApproxRaw(int n, double misrate)
{
long maxW = (long)n * (n + 1) / 2;
long a = 0;
long b = maxW;
while (a < b - 1)
{
long c = (a + b) / 2;
double cdf = EdgeworthCdf(n, c);
if (cdf < misrate)
a = c;
else
b = c;
}
return EdgeworthCdf(n, b) < misrate ? b : a;
}
/// <summary>
/// Edgeworth expansion for Wilcoxon signed-rank distribution CDF.
/// </summary>
private static double EdgeworthCdf(int n, long w)
{
double mu = (double)n * (n + 1) / 4.0;
double sigma2 = n * (n + 1.0) * (2 * n + 1) / 24.0;
double sigma = Sqrt(sigma2);
// +0.5 continuity correction: computing P(W ≤ w) for a left-tail discrete CDF
double z = (w - mu + 0.5) / sigma;
double phi = Exp(-z * z / 2) / Sqrt(2 * PI);
double Phi = AcmAlgorithm209.Gauss(z);
double nd = n;
double kappa4 = -nd * (nd + 1) * (2 * nd + 1) * (3 * nd * nd + 3 * nd - 1) / 240.0;
double e3 = kappa4 / (24 * sigma2 * sigma2);
double z2 = z * z;
double z3 = z2 * z;
double f3 = -phi * (z3 - 3 * z);
double edgeworth = Phi + e3 * f3;
return Min(Max(edgeworth, 0), 1);
}
}
Tests
The test suite contains 39 correctness test cases (4 demo + 6 boundary + 7 exact + 20 medium + 2 error).
Demo examples () — from manual introduction:
demo-1: , , expected output:demo-2: , , expected output:demo-3: , , expected output:demo-4: , , expected output:
These demo cases match the reference values used throughout the manual to illustrate construction.
Boundary cases — minimum achievable misrate validation:
boundary-n2-min: , (minimum misrate for , expected output: )boundary-n3-min: , (minimum misrate for )boundary-n4-min: , (minimum misrate for )boundary-loose: , (permissive misrate)boundary-tight: , (strict misrate)boundary-very-tight: , (very strict misrate)
These boundary cases validate correct handling of minimum achievable misrate (formula: ) and edge conditions.
Exact computation () — validates dynamic programming path:
exact-n5-mr1e1: ,exact-n6-mr1e1: ,exact-n6-mr5e2: ,exact-n10-mr1e1: , , expected output:exact-n10-mr1e2: ,exact-n10-mr5e2: ,exact-n10-mr5e3: ,
These cases exercise the exact Wilcoxon signed-rank CDF computation for small samples where dynamic programming is used.
Medium samples ( × 4 misrates) — 20 tests:
- Misrate values:
- Test naming:
medium-n{n}-mr{k}where encodes the misrate - Examples:
medium-n15-mr1e1: ,medium-n30-mr1e2: , , expected output:medium-n50-mr1e3: ,medium-n100-mr1e4: ,
The medium sample tests validate the transition region between exact computation () and approximate computation, ensuring consistent results across sample sizes and misrate values.
Error case — domain violation:
error-n1: , (invalid: misrate below minimum achievable )error-n0: , (invalid: n must be positive)
This error case verifies that implementations correctly reject with as invalid input, since the minimum achievable misrate for is .