Repository: scipy/scipy
Pull request: MAINT: stats.nbinom.logcdf: fix vectorization
#### Reference issue
Closes gh-16159

#### What does this implement/fix?
The vectorization of `stats.nbinom.logcdf` was broken due to a mistake in the use of `_lazywhere`. This fixes it.


Problem statement:
BUG: scipy.stats.nbinom.logcdf returns wrong result for particular arguments
### Describe your issue.

The function `scipy.stats.nbinom.logcdf` returns wrong results for certain combinations of input arguments.

If we set `n=4.8` and `p=0.45` then for the input `[0, 5, 0, 5]` the function returns `[-3.832837, -0.647303, -0.647303, -0.647303]`. The third array element cannot be right because it must be equal to the first array element.

On the other hand, if we try the input `[0, 0, 5, 5]` the function returns the correct output `[-3.832837, -3.832837, -0.647303, -0.647303]`.

If we process the input `[0, 5, 0, 5]` element-wise one after another then the individual outputs are correct too.

Note that a non-integer `n` is valid for the mean-variance parameterization of the negative binomial distribution.

### Reproducing Code Example

```python
from scipy.stats import nbinom
from numpy.testing import assert_allclose
vals = nbinom.logcdf([0, 5, 0, 5], n=4.8, p=0.45)
assert_allclose(vals, [-3.83283694, -0.64730271, -3.83283694, -0.64730271])
```


### Error message

```shell
Traceback (most recent call last):
  File "/home/asnelt/test_nbinom.py", line 4, in <module>
    assert_allclose(vals, [-3.83283694, -0.64730271, -3.83283694, -0.64730271])
  File "/home/asnelt/.local/lib/python3.10/site-packages/numpy/testing/_private/utils.py", line 1530, in assert_allclose
    assert_array_compare(compare, actual, desired, err_msg=str(err_msg),
  File "/home/asnelt/.local/lib/python3.10/site-packages/numpy/testing/_private/utils.py", line 844, in assert_array_compare
    raise AssertionError(msg)
AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0

Mismatched elements: 1 / 4 (25%)
Max absolute difference: 3.18553423
Max relative difference: 0.83111655
 x: array([-3.832837, -0.647303, -0.647303, -0.647303])
 y: array([-3.832837, -0.647303, -3.832837, -0.647303])
```


### SciPy/NumPy/Python version information

1.8.0 1.22.3 sys.version_info(major=3, minor=10, micro=4, releaselevel='final', serial=0)

Hints from issue discussion:

Commit message: MAINT: stats.nbinom.logcdf: fix vectorization

Patch to review:
diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py
index 6f4d2ed1f58e..c7a2c514b100 100644
--- a/scipy/stats/_discrete_distns.py
+++ b/scipy/stats/_discrete_distns.py
@@ -344,11 +344,11 @@ def _logcdf(self, x, n, p):
         def f1(k, n, p):
             return np.log1p(-special.betainc(k + 1, n, 1 - p))
 
-        def f2(k, n, p):
-            return np.log(cdf)
-
+        logcdf = np.empty_like(cdf)
         with np.errstate(divide='ignore'):
-            return _lazywhere(cond, (x, n, p), f=f1, f2=f2)
+            logcdf[cond] = f1(k[cond], n[cond], p[cond])
+            logcdf[~cond] = np.log(cdf[~cond])
+        return logcdf
 
     def _sf(self, x, n, p):
         k = floor(x)
diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py
index 9c7a333a64a6..4419c3305fe1 100644
--- a/scipy/stats/tests/test_distributions.py
+++ b/scipy/stats/tests/test_distributions.py
@@ -361,6 +361,12 @@ def test_pmf(self):
         val = scipy.stats.nbinom.logpmf(0, 1, 1)
         assert_equal(val, 0)
 
+    def test_logcdf_gh16159(self):
+        # check that gh16159 is resolved.
+        vals = stats.nbinom.logcdf([0, 5, 0, 5], n=4.8, p=0.45)
+        ref = np.log(stats.nbinom.cdf([0, 5, 0, 5], n=4.8, p=0.45))
+        assert_allclose(vals, ref)
+
 
 class TestGenInvGauss:
     def setup_method(self):


Code review comments:
diff_hunk: @@ -344,11 +344,11 @@ def _logcdf(self, x, n, p):
         def f1(k, n, p):
             return np.log1p(-special.betainc(k + 1, n, 1 - p))
 
-        def f2(k, n, p):
-            return np.log(cdf)
-
+        logcdf = np.empty_like(cdf)
path: scipy/stats/_discrete_distns.py
text: @author:
```suggestion
        # do calc in place
        logcdf = cdf
```
This would be a little more efficient.

@user1:
I agree. Feel free to commit this.