Repository: scipy/scipy
Pull request: BUG: signal: Fix scalar input issue of signal.lfilter
<!-- 
Thanks for contributing a pull request! Please ensure that
your PR satisfies the checklist before submitting:
http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#checklist-before-submitting-a-pr

Also, please name and describe your PR as you would write a
commit message:
http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#writing-the-commit-message

Depending on your changes, you can skip CI operations and save time and energy: 
http://scipy.github.io/devdocs/dev/contributor/continuous_integration.html#skipping

Note that we are a team of volunteers; we appreciate your
patience during the review process.

Again, thanks for contributing!
-->

#### Reference issue
<!--Example: Closes gh-WXYZ.-->
Fix #11359 

#### What does this implement/fix?
<!--Please explain your changes.-->
Fixed the scalar input issue of `signal.lfilter` and add a test for it.

#### Additional information
<!--Any additional information you think is important.-->


Problem statement:
lfilter error when input b is 0-dim
When using lfilter to apply an auto-regressive filter, i.e., with feedback coefficients only, then there is an error if b is either just a single number or a 0-dim numpy array. Note that if b is a single element list or a 1-dim numpy array, there is no error.

I am not sure if this behaviour is intended for some reason? anyway, it could be simply solved by adding a `b = np.atleast_1d(b)` at the top of the lfilter function where there is already an equivalent call for the `a` input. 

Note the existing `a = np.atleast_1d(a)` allows the use of lfilter for moving-average filters so `a=1` works fine. Although there is an error if `a` and `b` are both 0-dim, but this is not a realistic use case so I guess it doesn't matter.

#### Reproducing code example:
```
import numpy as np
import scipy.signal as spsig
# b as just a number gives an error
spsig.lfilter(1.0, [1.0, -0.5, 0.3], np.random.randn(100))

# b as a list works fine
spsig.lfilter([1.0], [1.0, -0.5, 0.3], np.random.randn(100))

# b as a 0-d numpy array also gives an error
spsig.lfilter(np.array(1.0), [1.0, -0.5, 0.3], np.random.randn(100))

# b as a 1-d numpy array works fine
spsig.lfilter(np.array([1.0]), [1.0, -0.5, 0.3], np.random.randn(100))
```

#### Error message:
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python36\lib\site-packages\scipy\signal\signaltools.py", line 1397, in lfilter
    return sigtools._linear_filter(b, a, x, axis)
ValueError: could not convert b, a, and x to a common type
  ...
```

#### Scipy/Numpy/Python version information:
```
import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
1.3.1 1.17.3 sys.version_info(major=3, minor=6, micro=0, releaselevel='final', serial=0)
```

Hints from issue discussion:
I also have the same issue. Have you found a way to resolve this?
yes, the solution is just to add `b = np.atleast_1d(b)` at line 1817 of signaltools.py in scipy.signal
 HI!  I added b = np.atleast_1d(b) at line 1817 of signaltools.py in scipy.signal but I have always the same problem. Can you help me, please!!
@lucascolley  , may I handle this issue ?
yes @forchag ! Feel free to look into any issues you want, no need to ask :)
This can be reproducible in the latest code (1.12.0), but the error message was a little bit changed.

```
In [15]: spsig.lfilter(np.array(1.0), 1.0, np.random.randn(100))

>    2141     ind[axis] = slice(zi.shape[axis])
   2142     out_full[tuple(ind)] += zi
-> 2144 ind[axis] = slice(out_full.shape[axis] - len(b) + 1)
   2145 out = out_full[tuple(ind)]
   2147 if zi is None:
TypeError: len() of unsized object
```

Commit message: BUG: signal: Fix scalar input issue of signal.lfilter

Patch to review:
diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py
index 0ce436853bd7..214b35c596dd 100644
--- a/scipy/signal/_signaltools.py
+++ b/scipy/signal/_signaltools.py
@@ -2087,6 +2087,7 @@ def lfilter(b, a, x, axis=-1, zi=None):
     >>> plt.show()
 
     """
+    b = np.atleast_1d(b)
     a = np.atleast_1d(a)
     if len(a) == 1:
         # This path only supports types fdgFDGO to mirror _linear_filter below.
diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py
index 88d6840015b3..9a669d9b30f5 100644
--- a/scipy/signal/tests/test_signaltools.py
+++ b/scipy/signal/tests/test_signaltools.py
@@ -1864,6 +1864,14 @@ def test_do_not_modify_a_b_FIR(self):
         assert_equal(b, b0)
         assert_equal(a, a0)
 
+    @pytest.mark.parametrize("a", [1.0, [1.0], np.array(1.0)])
+    @pytest.mark.parametrize("b", [1.0, [1.0], np.array(1.0)])
+    def test_scalar_input(self, a, b):
+        data = np.random.randn(10)
+        assert_array_almost_equal(
+            lfilter(np.array([1.0]), np.array([1.0]), data),
+            lfilter(b, a, data))
+
 
 class TestLinearFilterFloat32(_TestLinearFilter):
     dtype = np.dtype('f')


Code review comments:
diff_hunk: @@ -1864,6 +1864,14 @@ def test_do_not_modify_a_b_FIR(self):
         assert_equal(b, b0)
         assert_equal(a, a0)
 
+    @pytest.mark.parametrize("a", [1.0, [1.0], np.array(1.0)])
+    @pytest.mark.parametrize("b", [1.0, [1.0], np.array(1.0)])
+    def test_scalar_input(self, a, b):
+        data = np.random.randn(10)
+        assert_array_almost_equal(
path: scipy/signal/tests/test_signaltools.py
text: @user1:
```suggestion
        assert_allclose(
```