Repository: scipy/scipy

Issue:
signal.group_delay fails for narrow band filters 
When doing

    sr = 96000
    b, a = scipy.signal.iirfilter(2, 2.0 / sr * np.array([980, 1020]), btype='bandpass')
    w, gd = scipy.signal.group_delay((b, a))

I get:

> UserWarning: The group delay is singular at frequencies [0.000], setting to 0

and then `tau = max(gd)` (which is what I'm looking for) is highly wrong: 527 samples, i.e. 5.5 ms @ 96 khz.

Let's now filter a signal (in blue) using `lfilter(b, a, x)`, and shift it forward on x-axis (time) by this highest group delay `tau` (in magenta):

![Imgur](https://i.imgur.com/0Lgk66i.png)

It's highly wrong: the time-shift should be of 10 or 11 ms for the delay to be compensated.

NB: this is very close to [this question](https://dsp.stackexchange.com/questions/8468/how-to-find-the-group-delay-of-iir-filters-in-python-scipy): of how many samples should he shift the signal to compensate the delay? It seems that  `tau = max(gd)` does not work.

NB2: The time-shift by group-delay at 1000hz should be correct at least for a single sinusoid of freq 1000hz. [See my next post for more details and reproducible code.](https://github.com/scipy/scipy/issues/9310#issuecomment-424989146)


Discussion:
If you look at the ZPK representation of the filter via
```
z,p,k = sig.iirfilter(2, np.array([980, 1020])/(fs/2), btype='bandpass', output='zpk')
```
you'll see that this filter has zeros at z=+-1 (and also by definition, since the default is `ftype='butter'`). Hence, the delay at this frequency is undefined; the `group_delay` docs tell you that it sets the delay to zero in this case, explaining the warning message. This does not necessarily invalidate any other computations going on inside of `group_delay`.

Now, regarding delay compensation, as the current top answer on the linked SO question states: "Group delay is a spectrum, so this doesn't make sense". If you produce that plot with sinusoids of different frequency, you will observe different delays. There is no single time shift that will perform the compensation you seek for all possible input signals.

You did not specify, however, if the plot you produced is of a sinusoid at the frequency of the maximum group delay; is it?
> If you produce that plot with sinusoids of different frequency, you will observe different delays. There is no single time shift that will perform the compensation you seek for all possible input signals.

Yes of course @e-q, it depends on the frequency, but if we use **a single sinusoid** of frequency 1000hz, at least, the group delay computed by `scipy.signal.group_delay`  for this frequency bin should be able to compensate the delay of *this* sinusoid.

It works for all kind of filters I've tested:
* lowpass, 
* bandpass like in the following code with `LOW, HIGH = 950, 1050`  or even  `LOW, HIGH = 970, 1030`  (then max group delay is 669, and the max is reached for the centre freq 1000hz):

![Imgur](https://i.imgur.com/9gjFJi4.png)

**except** it fails in some cases, even if there is one single sinusoid at 1000hz :

![Imgur](https://i.imgur.com/dHEE9yu.png)

Here is the full code to reproduce:

```
LOW, HIGH = 985, 1015

import numpy as np, matplotlib.pyplot as plt
from scipy import signal

sr = 96000
x = np.zeros(2 * sr)
f0 = 1000
x[sr:] = np.sin(2 * np.pi * f0 / sr * np.arange(0., sr))

b, a = signal.butter(2, 2.0/sr * np.asarray([LOW, HIGH]), btype="bandpass")
y = signal.lfilter(b, a, x)

w, gd = signal.group_delay((b,a))
tau = int(max(gd))
plt.plot(w, gd)
plt.show()

y_shifted = y[tau:]
r = range(int(0.950*sr), int(1.050*sr))  # zoom around t = 1 s
plt.plot(r, x[r], 'blue')
plt.plot(r, y[r], color='orange', marker='o')
plt.plot(r, y_shifted[r], 'green')
plt.legend(('Original', 'Filtered', 'Filtered and time-shifted by group-delay'))
plt.show()
```

In this latter case (bandpass [985, 1015]), the max group-delay is ridiculously small (87 samples)! It should be at least **higher** than the max group-delay (669 samples) for a bandpass of higher bandwidth [970, 1030].

How can we fix this in the case of narrow bandpass filters?
What's happening here is the singularity check in `group_delay` is setting all of the values in the pass band to zero.  For example:
```
In [179]: sr = 96000

In [180]: f0 = 1000

In [181]: center = 2 * np.pi * f0 / sr

In [182]: b, a = signal.butter(2, 2.0/sr * np.array([985, 1015]), btype='bandpass')

In [183]: w = np.linspace(center - 0.015, center + 0.015, 8192)

In [184]: w, gd = signal.group_delay((b,a), w)
C:\Users\eric.moore\AppData\Local\Continuum\anaconda3\lib\site-packages\scipy\signal\filter_design.py:631: UserWarning: The group delay is singular at frequencies [0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.064, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.065, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.066, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.067, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068, 0.068], setting to 0
  format(", ".join("{0:.3f}".format(ws) for ws in w[singular]))

In [185]: w, H = signal.freqz(b, a, w)

In [186]: gd1 = -np.diff(np.angle(H))/np.diff(w)

In [187]: w_gd1 = w[:-1] + (w[1]-w[0])/2

In [188]: plt.plot(w, gd, w_gd1, gd1)
Out[188]: 
[<matplotlib.lines.Line2D at 0x1fb13208>,
 <matplotlib.lines.Line2D at 0x1fb133c8>]

In [189]: plt.legend(['signal.group_delay', 'by hand'])
Out[189]: <matplotlib.legend.Legend at 0x1fbd36d8>
```
![figure_1](https://user-images.githubusercontent.com/2314641/46219903-94538b00-c316-11e8-9143-a1458f0ec379.png)

I haven't looked at what `group_delay` is doing. Perhaps it can be improved for narrow band filters.
I changed the title of this issue to better reflect the scipy defect reported.  Regardless of whether or not `signal.group_delay` is the right tool to solve your problem, the issue you've reported, @josephernest, is real.  Thanks for reporting it.
@ewmoore your proposed version to compute the group delay fixes it indeed in the case of narrow bandpass filters, thanks a lot :)

Here is an example:

```
LOW, HIGH = 985, 1015  

import numpy as np, matplotlib.pyplot as plt
from scipy import signal

sr = 96000
f0 = 1000
x = np.zeros(2 * sr)

x[sr:] = np.sin(2 * np.pi * f0 / sr * np.arange(0., sr))

b, a = signal.butter(2, 2.0/sr * np.asarray([LOW, HIGH]), btype="bandpass")
y = signal.lfilter(b, a, x)

w, gd = signal.group_delay((b,a), 8192)
w, H = signal.freqz(b, a, 8192)
w_gd1 = w[:-1] + (w[1]-w[0])/2
gd1 = -np.diff(np.angle(H))/np.diff(w)
gd1[0] = 0

plt.plot(w, gd)
plt.plot(w_gd1, gd1)
plt.legend(['signal.group_delay', 'by hand'])
plt.show()
tau = int(max(gd1))

y_shifted = y[tau:]
r = range(int(0.950*sr), int(1.050*sr))  # zoom around t = 1 s
plt.plot(r, x[r], 'blue')
plt.plot(r, y[r], color='orange', marker='o')
plt.plot(r, y_shifted[r], 'green')
plt.legend(('Original', 'Filtered', 'Filtered and time-shifted by group-delay'))
plt.show()
```

![Imgur](https://i.imgur.com/9yum2xH.png)
Conclusion: for now, we should use:

    w, H = scipy.signal.freqz(b, a, 50000)
    gd = -np.diff(np.angle(H))/np.diff(w)

instead of:

    w, gd = signal.group_delay((b,a), 50000)

and everything is fine!

Could we add a note at the end of the documentation, that in specific cases (like narrow bandpass filters), we should use these two lines instead of `group_delay`? Do you want to do a PR for this @ewmoore 
In #4549, when this function was introduced, the following link is given for the algorithm used: [stanford.edu](https://ccrma.stanford.edu/%7Ejos/fp/Numerical_Computation_Group_Delay.html) 

It implies that the "by hand" method of approximating the derivative of the phase response is not algorithmically efficient, so it may be worth making the singularity check more robust if possible.

The singularity-check code is:

https://github.com/scipy/scipy/blob/ad00a9e8dccc6c075e724d0ff4f05bc5c0147a8b/scipy/signal/filter_design.py#L677-L682

where `EPSILON` is set to `2e-16` elsewhere in `filter_design.py`.

Indeed, for the original filter given in this issue, here is what `np.absolute(den)` looks like, compared with `10*EPSILON`, which leads to what @ewmoore saw:
![9310](https://user-images.githubusercontent.com/6894112/46238903-38612480-c35f-11e8-9a01-8bb40f1cd6a2.png)

I don't current have an intuition for what it means that this quantity is small in the passband...

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
@@ -683,15 +683,25 @@ def group_delay(system, w=512, whole=False, fs=2*pi):
     z = np.exp(-1j * w)
     num = np.polyval(cr[::-1], z)
     den = np.polyval(c[::-1], z)
-    singular = np.absolute(den) < 10 * EPSILON
+    gd = np.real(num / den) - a.size + 1
+    singular = ~np.isfinite(gd)
+    near_singular = np.absolute(den) < 10 * EPSILON
+
     if np.any(singular):
+        gd[singular] = 0
         warnings.warn(
             "The group delay is singular at frequencies [{0}], setting to 0".
-            format(", ".join("{0:.3f}".format(ws) for ws in w[singular]))
+            format(", ".join("{0:.3f}".format(ws) for ws in w[singular])),
+            stacklevel=2
         )
 
-    gd = np.zeros_like(w)
-    gd[~singular] = np.real(num[~singular] / den[~singular]) - a.size + 1
+    elif np.any(near_singular):
+        warnings.warn(
+            "The filter's denominator is extremely small at frequencies [{0}], \
+            around which a singularity may be present".
+            format(", ".join("{0:.3f}".format(ws) for ws in w[near_singular])),
+            stacklevel=2
+        )
 
     w = w*fs/(2*pi)
 


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
@@ -3970,8 +3970,7 @@ def test_iir(self):
 
     def test_singular(self):
         # Let's create a filter with zeros and poles on the unit circle and
-        # check if warning is raised and the group delay is set to zero at
-        # these frequencies.
+        # check if warnings are raised at those frequencies.
         z1 = np.exp(1j * 0.1 * pi)
         z2 = np.exp(1j * 0.25 * pi)
         p1 = np.exp(1j * 0.5 * pi)
@@ -3981,7 +3980,6 @@ def test_singular(self):
         w = np.array([0.1 * pi, 0.25 * pi, -0.5 * pi, -0.8 * pi])
 
         w, gd = assert_warns(UserWarning, group_delay, (b, a), w=w)
-        assert_allclose(gd, 0)
 
     def test_backward_compat(self):
         # For backward compatibility, test if None act as a wrapper for default