Repository: scipy/scipy

Issue:
freqz() does not include evaluation at pi by default
The default, or when specifying worN as an integer, the frequencies are evaluated at

```
linspace(0, 2*pi if whole else pi, N, endpoint=False)
```

However, I believe it would make more sense to *include* the endpoint, as the complex value of the frequency at pi is valuable information that I believe should be visible by default. This would parallel the often-taken step of taking a N-point FFT, and, for even N, keeping the values at indexes 0..N/2 *inclusive*. The last value represents the frequency at the Nyquist frequency.


Discussion:
I agree in principle but changing this would be backward incompatible, and not worth introducing a deprecation cycle to change because the number of affected scripts and downstream libs would probably be quite large.

We could add a new keyword argument like `nyquist=False`, or perhaps simpler, allow `whole='nyquist'`, meaning "False but include Nyquist" or "only go to Nyquist". I might try to make a PR to do this to see how it would look.
Hello, I would like to work on this issue. Could you please point me in the right direction as to where I should start looking to fix this? Thank you!

Resolving patch:
diff --git a/scipy/signal/filter_design.py b/scipy/signal/filter_design.py
--- a/scipy/signal/filter_design.py
+++ b/scipy/signal/filter_design.py
@@ -272,7 +272,7 @@ def freqs_zpk(z, p, k, worN=200):
     return w, h
 
 
-def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi):
+def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, include_nyquist=False):
     """
     Compute the frequency response of a digital filter.
 
@@ -301,7 +301,7 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi):
         If a single integer, then compute at that many frequencies (default is
         N=512). This is a convenient alternative to::
 
-            np.linspace(0, fs if whole else fs/2, N, endpoint=False)
+            np.linspace(0, fs if whole else fs/2, N, endpoint=include_nyquist)
 
         Using a number that is fast for FFT computations can result in
         faster computations (see Notes).
@@ -321,6 +321,11 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi):
         radians/sample (so w is from 0 to pi).
 
         .. versionadded:: 1.2.0
+    include_nyquist : bool, optional
+        If `whole` is False and `worN` is an integer, setting `include_nyquist` to True
+        will include the last frequency (Nyquist frequency) and is otherwise ignored.
+
+        .. versionadded:: 1.5.0
 
     Returns
     -------
@@ -436,7 +441,8 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi):
         if N < 0:
             raise ValueError('worN must be nonnegative, got %s' % (N,))
         lastpoint = 2 * pi if whole else pi
-        w = np.linspace(0, lastpoint, N, endpoint=False)
+        # if include_nyquist is true and whole is false, w should include end point
+        w = np.linspace(0, lastpoint, N, endpoint=include_nyquist and not whole)
         if (a.size == 1 and N >= b.shape[0] and
                 sp_fft.next_fast_len(N) == N and
                 (b.ndim == 1 or (b.shape[-1] == 1))):


Test patch:
diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py
--- a/scipy/signal/tests/test_filter_design.py
+++ b/scipy/signal/tests/test_filter_design.py
@@ -810,6 +810,25 @@ def test_w_or_N_types(self):
             assert_array_almost_equal(w_out, [8])
             assert_array_almost_equal(h, [1])
 
+    def test_nyquist(self):
+        w, h = freqz([1.0], worN=8, include_nyquist=True)
+        assert_array_almost_equal(w, np.pi * np.arange(8) / 7.)
+        assert_array_almost_equal(h, np.ones(8))
+        w, h = freqz([1.0], worN=9, include_nyquist=True)
+        assert_array_almost_equal(w, np.pi * np.arange(9) / 8.)
+        assert_array_almost_equal(h, np.ones(9))
+
+        for a in [1, np.ones(2)]:
+            w, h = freqz(np.ones(2), a, worN=0, include_nyquist=True)
+            assert_equal(w.shape, (0,))
+            assert_equal(h.shape, (0,))
+            assert_equal(h.dtype, np.dtype('complex128'))
+
+        w1, h1 = freqz([1.0], worN=8, whole = True, include_nyquist=True)
+        w2, h2 = freqz([1.0], worN=8, whole = True, include_nyquist=False)
+        assert_array_almost_equal(w1, w2)
+        assert_array_almost_equal(h1, h2)
+
 
 class TestSOSFreqz(object):