Repository: scipy/scipy

Issue:
Incorrect edge case in scipy.stats.triang.pdf
When `c = 1`, the pdf of the triangular distribution should just be `f(x) = 2x`.  But as of version 0.17, [the pdf is computed using the formula](https://github.com/scipy/scipy/blob/v0.17.0/scipy/stats/_continuous_distns.py#L4121),

```
where(x < c, 2*x/c, 2*(1-x)/(1-c))
```

which incorrectly returns `nan`, rather than 2, in the case `x = 1`, `c = 1`.

Incorrect edge case in scipy.stats.triang.pdf
When `c = 1`, the pdf of the triangular distribution should just be `f(x) = 2x`.  But as of version 0.17, [the pdf is computed using the formula](https://github.com/scipy/scipy/blob/v0.17.0/scipy/stats/_continuous_distns.py#L4121),

```
where(x < c, 2*x/c, 2*(1-x)/(1-c))
```

which incorrectly returns `nan`, rather than 2, in the case `x = 1`, `c = 1`.



Discussion:
The cdf has the same problem.

`_lazywhere( c != 0, (x, c), lambda x, c: np.where(x < c, ..., ...), f2=lambda x: 2*x )`

I tried this approach, but it fails the `test_continous_basic.test_cont_basic.check_cmplx_deriv` test due to a difference in behavior between `where` and `_lazywhere` (which is a bug in the latter, maybe?).  I made a separate issue for it, https://github.com/scipy/scipy/issues/6043.

Yup, that's a bug --- there's a fix in https://github.com/scipy/scipy/pull/5869. Feel free to take over if it helps.

The cdf has the same problem.

`_lazywhere( c != 0, (x, c), lambda x, c: np.where(x < c, ..., ...), f2=lambda x: 2*x )`

I tried this approach, but it fails the `test_continous_basic.test_cont_basic.check_cmplx_deriv` test due to a difference in behavior between `where` and `_lazywhere` (which is a bug in the latter, maybe?).  I made a separate issue for it, https://github.com/scipy/scipy/issues/6043.

Yup, that's a bug --- there's a fix in https://github.com/scipy/scipy/pull/5869. Feel free to take over if it helps.


Resolving patch:
diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py
--- a/scipy/stats/_continuous_distns.py
+++ b/scipy/stats/_continuous_distns.py
@@ -5240,13 +5240,31 @@ def _argcheck(self, c):
         return (c >= 0) & (c <= 1)
 
     def _pdf(self, x, c):
-        return np.where(x < c, 2*x/c, 2*(1-x)/(1-c))
+        r = _lazyselect([c == 0,
+                         c == 1,
+                         x < c,
+                         x >= c],
+                        [lambda x, c: 2 - 2*x,
+                         lambda x, c: 2 * x,
+                         lambda x, c: 2 * x / c,
+                         lambda x, c: 2 * (1-x) / (1-c)],
+                        (x, c))
+        return r
 
     def _cdf(self, x, c):
-        return np.where(x < c, x*x/c, (x*x-2*x+c)/(c-1))
+        r = _lazyselect([c == 0,
+                         c == 1,
+                         x < c,
+                         x >= c],
+                        [lambda x, c: 2*x - x*x,
+                         lambda x, c: x * x,
+                         lambda x, c: x * x / c,
+                         lambda x, c: (x*x - 2*x + c) / (c-1)],
+                        (x, c))
+        return r
 
     def _ppf(self, q, c):
-        return np.where(q < c, np.sqrt(c*q), 1-np.sqrt((1-c)*(1-q)))
+        return np.where(q < c, np.sqrt(c * q), 1-np.sqrt((1-c) * (1-q)))
 
     def _stats(self, c):
         return ((c+1.0)/3.0,


Test patch:
diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py
--- a/scipy/stats/tests/test_distributions.py
+++ b/scipy/stats/tests/test_distributions.py
@@ -2347,6 +2347,15 @@ def test_trapz_vect(self):
         assert_allclose(v, res.reshape(v.shape), atol=1e-15)
 
 
+class TestTriang(object):
+    def test_edge_cases(self):
+        assert_equal(stats.triang.pdf(0.5, 0), 1.)
+        assert_equal(stats.triang.pdf(0.5, 1), 1.)
+
+        assert_equal(stats.triang.cdf(0.5, 0.), 0.75)
+        assert_equal(stats.triang.cdf(0.5, 1.), 0.25)
+
+
 def test_540_567():
     # test for nan returned in tickets 540, 567
     assert_almost_equal(stats.norm.cdf(-1.7624320982), 0.03899815971089126,