Repository: scipy/scipy

Issue:
scipy.interpolate.interp1d should be able to take a single value
Currently, the `scipy.interpolate.interp1d` constructor raises `ValueError` if the inputs are of size 0 or 1.  I suggest that it accepts the inputs instead and either raises `ValueError` on `__call__`, or returns `fill_value` if `bounds_error` is `False` (for a value different of the actually input value, if the input is of size 1).



Discussion:
I'd say it's better to bail out early with sensible error message if the input does not make much sense. 

But it makes sense.  Say e.g. my data consists of a number of measurements at various time points (t_i, y_i) and I also know the boundary conditions y_+-\infty.  Then the result of interp1d can be used to find out the state of the system at any time (if a constant approximation is valid, which obviously depends on the specifics), even if there are no data points.

I can see how that makes sense, and it seems like another reason to special-case input points, as in https://github.com/scipy/scipy/issues/4060, rather than associating every in-bounds query point with exactly two input points for 1d linear interpolation for example.  @anntzer if a user provides 2 input points and requests cubic interpolation how would you expect that to work?  Would you expect it to fall back to linear interpolation between the 2 points?  Analogously, if 1 point is provided and linear interpolation is requested, interp1d could fall back to a 0th order interpolation that returns the input y for a query x that exactly matches an input x and that deals with non-input x as out-of-bounds.

Here's an example where I think we can agree that interp1d is too picky about its input.

``` python
from scipy.interpolate import interp1d

f = interp1d([1, 2], [6, 6], kind='nearest')
print f(1.5)

f = interp1d([1.5], [6], kind='nearest')
print f(1.5)
```

```
6.0
ValueError: x and y arrays must have at least 2 entries
```

While I do not use interpolations of order more than 1, I would think raising a ValueError is appropriate if the number of points is less than the degree plus one.  But this is (IMO) a different case -- for just 0 or 1 value, clearly there is a single reasonable answer.

OK so the proposal would be to generalize interp1d `kind="linear"` interpolation to work with length-1 inputs in which case it would treat only that single point as in-bounds as determined by exact floating point equality, and to also work with length-0 inputs in which case it would treat all points as out-of-bounds, and the generalization to small-length inputs for high-order interpolation would not be in scope for this issue.

I looked at the interp1d code to try writing a PR implementing this, but it was so littered with special cases that I'll let someone else deal with it...

Actually I do not care much about the behavior with a single input point because the output array needs to be post-processed anyways (to deal with the nans), although #4060 seems to suggest that the single point should be considered in-bounds.

Why not

```
def interp1d_patched(x, y, xp, fill_value):
    x, y, xp = map(np.asarray, (x, y, xp))
    if xp.size < 2:
        return fill_value
   else:
      return interp1d(x, y, kind='linear', fill_value=fill_value)(xp)
```

OTOH, interp1d is already so littered with special cases that adding more magic to it may be OK. Overall, I'd recommend against using interp1d in new code anyway so I'm -0 on adding more to it.

Kindly bumping the issue.
Also, what would you recommend to use instead of interp1d?

Maybe it's just me being dense, but I still fail to see a case for anything more than wrapper like above which a user can fit to their heart's content. Anyway, I'm only -0 on this, so I wouldn't be strongly opposed to a PR which would turn what is now an error into something which at least two persons can agree is sensible :-). 

Any such PR would need to carefully weigh the backcompat guarantee. My own experience touching interp1d is not very nice: I added two seemingly trivial enhancements for 0.17; one caused a regression which created a bunch of work for Eric to fix; the other one caused _another_ regression which we did not catch in the rc phase, so it's likely going to be 0.17.1.

Re What to use instead of `interp1d`. My take on this (my own view, there's nothing official here) is:
- for linear interpolation, I'd use `numpy.interp` directly
- for spline interpolation, there's a choice of `splev/splrep` (remember to use `s=0` for interpolation; also zero weights to handle missing values) and the bright-and-shiny `CubicSpline` (dev version only).
- for monotone interpolation, there's Akima1D and PCHIP.
- for something which is not handled by the above, construct your own interpolators from `PPoly` or `BPoly.from_derivatives`. 

I just realized that for linear interpolation, `np.interp` is actually quite a bit faster than `interpolate.interp1d.__call__` (i.e. not even including the time to construct the `interp1d` object), and handles the case that triggered this issue, so I'll stick to it instead.

Indeed, from 0.17.0 `interp1d` defers to `np.interp` where the latter applies (1D real-valued arrays). So, `interp1d` should also handle your case then, albeit with some extra overhead.

Actually, out of bounds values are still handled separately (in `interp1d._evaluate`).

For what it's worth, I also expected `interp1d([0], [[1,2]], axis=0)(0)` to return `[1,2]`.

My use case is interpolating the solution to a system of ODEs, which needs broadcasting in the dimensions of `y`. But if only the initial conditions have been computed so far, `interp1d` blows up.

I would suggest raising a warning instead of raising an error. There is a very practical reason. I would like to apply interp1d on pandas DataFrames after groupby. I cannot guarantee the number of data points in each group. It's quite harassing to see the entire program fail because one or two nonsense groups.
Just finding this thread. I have a situation similar to @argriffing (post on 9 Oct 2014). Is there any possibility of using `nearest` when the input is a single point?
@danielwlogan We can consider a pull request with an implementation. 
The onus will be on you to 1) explain the use case, and 2) avoid breaking backwards compatibility.
gh-16070 might have tilted the balance and this starts looking more welcome, even if from the consistency POV. 

Currently, the error in the consructor is due to https://github.com/scipy/scipy/blob/main/scipy/interpolate/_interpolate.py#L497
so the first thing to do is set `minval = 1` and then actually handle the cases described above. 
The change probably be localized in `_evaluate`:  https://github.com/scipy/scipy/blob/main/scipy/interpolate/_interpolate.py#L692
and it might be easier to starts with writing tests. 

Pull requests are welcome.  

Resolving patch:
diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py
--- a/scipy/interpolate/_interpolate.py
+++ b/scipy/interpolate/_interpolate.py
@@ -504,7 +504,7 @@ def __init__(self, x, y, kind='linear', axis=-1,
         if kind in ('linear', 'nearest', 'nearest-up', 'previous', 'next'):
             # Make a "view" of the y array that is rotated to the interpolation
             # axis.
-            minval = 2
+            minval = 1
             if kind == 'nearest':
                 # Do division before addition to prevent possible integer
                 # overflow


Test patch:
diff --git a/scipy/interpolate/tests/test_interpolate.py b/scipy/interpolate/tests/test_interpolate.py
--- a/scipy/interpolate/tests/test_interpolate.py
+++ b/scipy/interpolate/tests/test_interpolate.py
@@ -193,7 +193,6 @@ def test_validation(self):
         # Check for x and y having at least 1 element.
         assert_raises(ValueError, interp1d, self.x1, self.y10)
         assert_raises(ValueError, interp1d, self.x10, self.y1)
-        assert_raises(ValueError, interp1d, self.x1, self.y1)
 
         # Bad fill values
         assert_raises(ValueError, interp1d, self.x10, self.y10, kind='linear',
@@ -878,6 +877,19 @@ def test_read_only(self):
                 vals = f(xnew)
                 assert_(np.isfinite(vals).all())
 
+    @pytest.mark.parametrize(
+        "kind", ("linear", "nearest", "nearest-up", "previous", "next")
+    )
+    def test_single_value(self, kind):
+        # https://github.com/scipy/scipy/issues/4043
+        f = interp1d([1.5], [6], kind=kind, bounds_error=False,
+                     fill_value=(2, 10))
+        assert_array_equal(f([1, 1.5, 2]), [2, 6, 10])
+        # check still error if bounds_error=True
+        f = interp1d([1.5], [6], kind=kind, bounds_error=True)
+        with assert_raises(ValueError, match="x_new is above"):
+            f(2.0)
+
 
 class TestLagrange: