DisparityBounds
where , , and (Bonferroni split).
Robust bounds on with specified coverage.
Input
- — first sample of measurements, where , requires sparity(x)
- — second sample of measurements, where , requires sparity(y)
- — probability that true disparity falls outside bounds in the long run (minimum depends on , ; see Algorithm)
Output
- Value interval bounding
- Unit dimensionless (spread units)
Notes
- 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], 10^(-3))returns bounds containingDisparity
See also: for comparing Disparity against practical thresholds with automatic verdict generation.
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.Algorithms;
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.NonWeighted("x", x);
Assertion.NonWeighted("y", y);
Assertion.CompatibleUnits(x, y);
(x, y) = Assertion.ConvertToFiner(x, 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;
if (FastSpread.Estimate(x.SortedValues, isSorted: true) <= 0)
throw AssumptionException.Sparity(Subject.X);
if (FastSpread.Estimate(y.SortedValues, isSorted: true) <= 0)
throw AssumptionException.Sparity(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, MeasurementUnit.Disparity);
}
if (ua <= 0.0)
{
if (ls == 0.0 && us == 0.0)
return new Bounds(0.0, 0.0, MeasurementUnit.Disparity);
if (ls >= 0.0)
return new Bounds(0.0, double.PositiveInfinity, MeasurementUnit.Disparity);
if (us <= 0.0)
return new Bounds(double.NegativeInfinity, 0.0, MeasurementUnit.Disparity);
return new Bounds(double.NegativeInfinity, double.PositiveInfinity, MeasurementUnit.Disparity);
}
if (ls > 0.0)
return new Bounds(ls / ua, double.PositiveInfinity, MeasurementUnit.Disparity);
if (us < 0.0)
return new Bounds(double.NegativeInfinity, us / ua, MeasurementUnit.Disparity);
if (ls == 0.0 && us == 0.0)
return new Bounds(0.0, 0.0, MeasurementUnit.Disparity);
if (ls == 0.0 && us > 0.0)
return new Bounds(0.0, double.PositiveInfinity, MeasurementUnit.Disparity);
if (ls < 0.0 && us == 0.0)
return new Bounds(double.NegativeInfinity, 0.0, MeasurementUnit.Disparity);
return new Bounds(double.NegativeInfinity, double.PositiveInfinity, MeasurementUnit.Disparity);
}
}
Notes
Width Convergence
The table below shows how narrows as grows, for ( evenly spaced points on ) and . Dashes indicate too small to achieve the target misrate.
| N | Width |
|---|---|
| 2 | — |
| 3 | — |
| 4 | — |
| 5 | — |
| 10 | — |
| 20 | — |
| 30 | 18.0000 |
| 40 | 5.0000 |
| 50 | 3.1429 |
| 100 | 2.3077 |
| 200 | 1.2000 |
| 300 | 0.9123 |
| 400 | 0.5918 |
| 500 | 0.5893 |
| 1000 | 0.3817 |
| 10000 | 0.1050 |


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: , , baseline fixture misratedemo-2: , , stricter fixture misrate, wider boundsdemo-3: , , looser fixture misrate
These cases illustrate how tighter misrates produce wider bounds.
Natural sequences (reference fixture misrates) 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: , , expected output:property-location-shift: , , expected output:property-scale-2x: , (= 2× location-shift), expected output: (scale invariance of disparity)property-scale-neg: , (negated), expected output: (anti-symmetry under sign flip)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 spanning progressively stricter fixture misrates:
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