DisparityBounds
Let and . Require . Let , , . Compute and .
If , return .
If , return the tightest single interval that is always valid:
- :
- :
- and :
- and :
- and :
- otherwise:
If , use the sign-only rule: if , if , otherwise (with when ).
Robust bounds on with specified coverage.
- Interpretation is probability that true disparity falls outside bounds
- Domain any real numbers, , ,
- Assumptions sparity(x), sparity(y)
- Unit dimensionless (spread units)
- Note Bonferroni split between shift and avg-spread bounds; no independence assumption needed; bounds may be unbounded when pooled spread cannot be certified positive
Properties
- Location invariance
- Scale invariance
- Antisymmetry (bounds reversed)
- Monotonicity in misrate smaller produces wider bounds
Example
DisparityBounds([1..30], [21..50], 0.02)returns bounds containingDisparity
Algorithm
The estimator constructs bounds on by combining and through a Bonferroni split.
Misrate allocation
The total budget is split between the shift and avg-spread components. Let (minimum for ) and (minimum for ). The extra budget beyond the minimums is split equally:
Component bounds
Compute and . By Bonferronis inequality, the probability that both intervals simultaneously contain their respective true values is at least .
Interval division
When , the disparity bounds are obtained by dividing the shift interval by the avg-spread interval. Since dividing by a positive interval can flip the ordering depending on the sign of the numerator endpoints, the algorithm computes all four combinations and takes the extremes:
Edge cases
When (the avg-spread interval includes zero), the bounds become partially or fully unbounded depending on the sign of :
- :
- :
- :
- otherwise:
When (the avg-spread interval collapses to zero), only the sign of the shift determines the result.
using Pragmastat.Exceptions;
using Pragmastat.Internal;
using Pragmastat.Metrology;
using static Pragmastat.Functions.MinAchievableMisrate;
namespace Pragmastat.Estimators;
/// <summary>
/// Distribution-free bounds for disparity using Bonferroni combination.
/// </summary>
public class DisparityBoundsEstimator : ITwoSampleBoundsEstimator
{
public static readonly DisparityBoundsEstimator Instance = new();
public Bounds Estimate(Sample x, Sample y, Probability misrate)
{
return Estimate(x, y, misrate, null);
}
public Bounds Estimate(Sample x, Sample y, Probability misrate, string? seed)
{
Assertion.MatchedUnit(x, y);
// Check validity (priority 0)
Assertion.Validity(x, Subject.X);
Assertion.Validity(y, Subject.Y);
if (double.IsNaN(misrate) || misrate < 0 || misrate > 1)
throw AssumptionException.Domain(Subject.Misrate);
int n = x.Size;
int m = y.Size;
if (n < 2)
throw AssumptionException.Domain(Subject.X);
if (m < 2)
throw AssumptionException.Domain(Subject.Y);
double minShift = TwoSample(n, m);
double minX = OneSample(n / 2);
double minY = OneSample(m / 2);
double minAvg = 2.0 * Math.Max(minX, minY);
if (misrate < minShift + minAvg)
throw AssumptionException.Domain(Subject.Misrate);
double extra = misrate - (minShift + minAvg);
double alphaShift = minShift + extra / 2.0;
double alphaAvg = minAvg + extra / 2.0;
// Check sparity (priority 2)
Assertion.Sparity(x, Subject.X);
Assertion.Sparity(y, Subject.Y);
var shiftBounds = ShiftBoundsEstimator.Instance.Estimate(x, y, alphaShift);
var avgBounds = seed == null
? AvgSpreadBoundsEstimator.Instance.Estimate(x, y, alphaAvg)
: AvgSpreadBoundsEstimator.Instance.Estimate(x, y, alphaAvg, seed);
double la = avgBounds.Lower;
double ua = avgBounds.Upper;
double ls = shiftBounds.Lower;
double us = shiftBounds.Upper;
if (la > 0.0)
{
double r1 = ls / la;
double r2 = ls / ua;
double r3 = us / la;
double r4 = us / ua;
double lower = Math.Min(Math.Min(r1, r2), Math.Min(r3, r4));
double upper = Math.Max(Math.Max(r1, r2), Math.Max(r3, r4));
return new Bounds(lower, upper, DisparityUnit.Instance);
}
if (ua <= 0.0)
{
if (ls == 0.0 && us == 0.0)
return new Bounds(0.0, 0.0, DisparityUnit.Instance);
if (ls >= 0.0)
return new Bounds(0.0, double.PositiveInfinity, DisparityUnit.Instance);
if (us <= 0.0)
return new Bounds(double.NegativeInfinity, 0.0, DisparityUnit.Instance);
return new Bounds(double.NegativeInfinity, double.PositiveInfinity, DisparityUnit.Instance);
}
if (ls > 0.0)
return new Bounds(ls / ua, double.PositiveInfinity, DisparityUnit.Instance);
if (us < 0.0)
return new Bounds(double.NegativeInfinity, us / ua, DisparityUnit.Instance);
if (ls == 0.0 && us == 0.0)
return new Bounds(0.0, 0.0, DisparityUnit.Instance);
if (ls == 0.0 && us > 0.0)
return new Bounds(0.0, double.PositiveInfinity, DisparityUnit.Instance);
if (ls < 0.0 && us == 0.0)
return new Bounds(double.NegativeInfinity, 0.0, DisparityUnit.Instance);
return new Bounds(double.NegativeInfinity, double.PositiveInfinity, DisparityUnit.Instance);
}
}
Tests
The test suite contains 39 test cases (3 demo + 5 natural + 6 property + 5 edge + 5 misrate + 2 distro + 6 unsorted + 7 error). Since returns bounds rather than a point estimate, tests validate that the bounds contain and satisfy equivariance properties. Each test case output is a JSON object with lower and upper fields representing the interval bounds. Because the denominator () uses randomized , tests fix a seed to keep outputs deterministic.
Demo examples (, ) from manual introduction:
demo-1: , ,demo-2: , , , wider bounds (tighter misrate)demo-3: , ,
These cases illustrate how tighter misrates produce wider bounds.
Natural sequences () 5 tests:
natural-10-10: , , bounds containingnatural-10-15: ,natural-15-10: ,natural-15-15: , , bounds containingnatural-20-20: , , bounds containing
Property validation (, ) 6 tests:
property-identity: , , bounds must containproperty-location-shift: and shifted by constant, same bounds as identity (location invariance)property-scale-2x: and scaled by 2, same bounds as identity (scale invariance)property-scale-neg: and negated, bounds preserved ( scaling)property-symmetry: , , observed boundsproperty-symmetry-swapped: and swapped, bounds negated (anti-symmetry)
Edge cases boundary conditions (5 tests):
edge-small: , (small samples)edge-negative: negative values for both samplesedge-mixed-signs: mixed positive/negative valuesedge-wide-range: extreme value rangeedge-asymmetric-10-20: , (unbalanced sizes)
Misrate variation (, ) 5 tests:
misrate-2e-1:misrate-1e-1:misrate-5e-2:misrate-2e-2:misrate-1e-2:
These tests validate monotonicity: smaller misrates produce wider bounds.
Distribution tests ( varies) 2 tests:
additive-20-20: ,uniform-20-20: ,
Unsorted tests verify independent sorting of and (6 tests):
unsorted-reverse-x: X reversed, Y sortedunsorted-reverse-y: X sorted, Y reversedunsorted-reverse-both: both reversedunsorted-shuffle-x: X shuffled, Y sortedunsorted-shuffle-y: X sorted, Y shuffledunsorted-wide-range: wide value range, both unsorted
These tests validate that produces identical results regardless of input order.
Error cases inputs that violate assumptions (7 tests):
error-empty-x: (empty X array)error-empty-y: (empty Y array)error-single-element-x: (too few elements for pairing)error-single-element-y: (too few elements for pairing)error-constant-x: constant violates sparity ()error-constant-y: constant violates sparity ()error-misrate-below-min: misrate below minimum achievable