[Rd] [PATCH] PR#19144: wilcox.test / .psignrank upper tail precision and out-of-domain fix
Ahmed S. Jabbar
@hmed@@t@te@me @end|ng |rom gm@||@com
Sat Oct 3 01:03:46 CEST 2026
Dear R-devel team,
I would like to provide an analysis, a minimal patch, and regression tests
for PR#19144 <https://bugs.r-project.org/show_bug.cgi?id=19144>
The issue is that wilcox.test(exact = TRUE) can return a negative p-value
in an extreme-tail case.
Diagnosis
The issue arises in .psignrank() in src/library/stats/R/wilcox.test.R. When
zeros or ties are present, .psignrank() obtains a discrete probability mass
vector d from .dsignrank(). The probabilities are computed using
floating-point arithmetic, so their sum can differ slightly from one. For
example, in the n = 100 reproducer, I obtain:
sum(d) = 1.0000000000000038
on x86_64.
For an observation near the upper end of the support, the current
implementation computes the lower-tail probability as:
sum(d[s < e + 1e-08])
and obtains the upper tail by complementing this value:
1 - y
In the reproducing case, the cumulative sum is slightly greater than one,
so the complement becomes:
-3.774758283725532e-15
and the corresponding two-sided p-value is consequently negative as well.
More generally, when the cumulative probability is close to one, forming a
small tail probability by subtraction can also lead to substantial loss of
relative precision.
Proposed fix
Instead of obtaining the upper tail by complementing the lower-tail sum,
the proposed change sums the probability masses belonging directly to the
requested tail:
Index: src/library/stats/R/wilcox.test.R
===================================================================
--- src/library/stats/R/wilcox.test.R (revision 87400)
+++ src/library/stats/R/wilcox.test.R (working copy)
@@ -1101,13 +1101,12 @@
seq.int(0, n * (n + 1)) / 2
## Density
d <- .dsignrank(s, n, z)
- y[i] <- vapply(q[i],
- function(e) {
- ## NOTE: C code in src/nmath uses a fuzz of 1e-7.
- sum(d[s < e + 1e-8])
- },
- 0)
- if(lower.tail) y else 1 - y
+ if(lower.tail) {
+ y[i] <- vapply(q[i], function(e) sum(d[s < e + 1e-8]), 0)
+ } else {
+ y[i] <- vapply(q[i], function(e) sum(d[s > e - 1e-8]), 0)
+ }
+ y
}
.qsignrank <-
===================================================================
This avoids the subtraction of two nearly equal floating-point quantities
when the upper tail is requested.
Verification
I tested the change using the reproducer from PR#19144 and additional cases
involving zeros and ties.
For the n = 100 reproducer, with rank sum q = 4755, an independently
computed integer generating-polynomial dynamic-programming calculation
gives the exact upper-tail probability:
819334086710 / 2^99 = 1.292681258640963e-18.
With the proposed change, .psignrank() returns:
1.292681258640968e-18
for this tail, corresponding to a relative error of approximately 3.9e-15.
The patch also removes the negative result in the original reproducing case
without requiring clipping or other post-hoc correction of the probability.
The accompanying regression tests cover:
- the original PR#19144 reproducer;
- p-value domain checks;
- comparison with the independently computed exact combinatorial reference;
- tail monotonicity; and
- additional cases involving zeros and ties.
I have attached the following files:
- wilcox_bug19144.patch — minimal patch against the R sources;
- reg-tests-wilcox.R — regression tests for the reported issue and the
numerical behavior described above.
I would appreciate any comments on the diagnosis, the proposed
implementation, or the form and placement of the regression tests.
Best regards,
--
Ahmed Sattar Jabbar
Department of Statistics | Mustansiriyah University
Baghdad, Iraq
Email: ahmed.state.me using gmail.com
ORCID: https://orcid.org/0009-0006-4614-735X
GitHub: https://github.com/amjed-droid
More information about the R-devel
mailing list