# Negative instantaneous frequency with hilbert transform using scipy hilbert

When I produce a chirp signal with a duration of 2 sec, sampling frequency of 200Hz, with frequencies ranging from 20Hz-100Hz, I get negative instantaneous frequencies using the scipy(Python) Hilbert transform. Here is my signal code:

    self.duration = 2
self.fs = 200.0
samples = int(self.fs * self.duration)
self.time = np.arange(samples) / self.fs
carrier = chirp(self.time, 20, self.time[-1], 100)
self.signal = carrier * (
1 + 0.5 * np.cos(10 * 2.0 * np.pi * self.time)
)


Here is my Hilbert transform code:

demean = data - np.mean(data)
hil = hilbert(demean)
inst_amplitude = np.abs(hil)
inst_phase = np.unwrap(np.angle(hil))
inst_frequency = np.diff(inst_phase) / (2 * np.pi) * sampling_frequency
inst_frequency = np.insert(inst_frequency, 0, 0)
regen_carrier = np.cos(inst_phase)


Here is the plot of the Hilbert properties: Why are there negative instantaneous frequencies? I have been Googling the issue with zero luck finding an answer. Any input would be greatly appreciated.

UPDATE: I applied the exact same Hilbert transform code as above to a "real life" dataset with a sampling frequency of 800Hz. I am getting negative instantaneous frequencies in the middle of the data, not just at the edges. I don't think I am allowed to show the data as it is proprietary, so sorry on that front. Any insight as to why I am seeing negative frequencies in the middle of my data and not just at the edges?

UPDATE #2 I went ahead and generated a synthetic chirp signal with multiple frequency sweeps using scipy.signal.sweep_poly and I am getting negative instantaneous frequencies in the middle of my data.

Here is the code for my new signal (duration of 50sec and sampling frequency of 100):

    self.duration = 50
self.fs = 100.0
samples = int(self.fs * self.duration)
self.time = np.arange(samples) / self.fs
p = np.poly1d([0.025, -0.36, 1.25, 2.0])
self.signal = sweep_poly(self.time, p)



Here is the plot of the new signal's Hilbert properties: Here's the same image, but zoomed in where there are negative instantaneous frequencies in the middle of my data: Why are there negative instantaneous frequencies in the middle of my data? There are definitely no edges in the middle of my data, so it's not an edge effect.

UPDATE #3 I added random noise to the exact same signal using the following code:

    self.duration = 50
self.fs = 100.0
samples = int(self.fs * self.duration)
self.time = np.arange(samples) / self.fs
p = np.poly1d([0.025, -0.36, 1.25, 2.0])
carrier = sweep_poly(self.time, p)  # Carrier signal
noise = np.random.normal(0, 1, carrier.shape)
self.signal = carrier + noise


Here is the plot of the noisy Hilbert transform properties: Even more negative frequencies! Why is this happening?

UPDATE #4 I decided to multiply 2 sine functions with two different constant frequencies together to see what I get. There is no noise in this instance. Here is my code:

    self.duration = 2
self.fs = 100
samples = int(self.fs * self.duration)
self.time = np.arange(samples) / self.fs
self.signal1 = np.sin(1 * 2.0 * np.pi * self.time)
self.signal2 = np.sin(9 * 2.0 * np.pi * self.time)
self.signal = self.signal1 * self.signal2


Here is the plot of the resulting Hilbert properties: If I change the sampling frequency, the magnitude and sign of the instantaneous frequency spikes change! Here are a few examples (the sampling frequency used is in the title of the image):   Something about the phase calculation is unstable and the sampling frequency seems to alter things. No I am even more curious as to what is happening.

Thanks!

• This looks pretty good to me. An "exact" Hilbert transform requires an infinitely long non-causal filter which is impossible to implement. So all practical implementations are approximations and you are always going to see significant "edge" effects both in time and frequency. What specific artifacts you are going to get depends on the details of the Hilbert approximation. Mar 1, 2021 at 16:19
• Thanks! I was half expecting it to have to do something with edge effects, but couldn't find a definitive answer. Mar 1, 2021 at 16:51
• One more question for you. I have a "real life" signal which has a sampling frequency of 800Hz. When I apply the same code as shown above to the "real life" signal, I am getting negative frequencies in the middle of the data, not just at the edges. I don't think I am allowed to show the data, as it is proprietary, so sorry on that front. Any insight? Mar 1, 2021 at 17:25
• You can keep updating your post until there's no more space left on the servers: as long as you have discontinuities in the phase response, you will get spikes after the derivative. Was there something not clear from what I said? Mar 3, 2021 at 22:29
• No, that is very clear. I would like to know more, though, as to why there are discontinuities in the phase response. What is causing these? From my tests, it is obviously that it is some combination of the frequencies in the signal and the sampling frequencies used. Just knowing that there are discontinuities is not satisfactory to me. I would like to understand this problem at a level where I am able to predict when and where these discontinuities will occur. Mar 4, 2021 at 15:37

It looks like artifacts due to the derivative. I used this code in Octave:

fs=300;
t=[0:1/fs:2];
c=chirp(t, 20, 2, 100);
s=c.*(1+0.5*cos(2*pi*10*t));
h=hilbert(s);
m=abs(h);
a=diff(unwrap(arg(h)))/2/pi*fs;
plot(m,"",a)


and this is what comes out: I also tested the equivalent of this in LTspice and, without any form of unwrapping, this is the result: where V(mag) is the result of hypot() (m), V(signal) is s, V(freq) is the frequency for the FM, and V(demod) is a. All are delayed (for matching I/O) by 23/2/200 which represents the order of the Hilbert transformer (Hamming window), divided by two (group delay) and by the sampling frequency (200).

My guess: don't take for granted the result of the differentiator and try to take a finer comb to it (is this a pun? not intended).

• Right, when instantaneous phase has a negative slope, we get negative instantaneous frequencies. But, why is this happening? Why does the phase have a negative slope? Mar 3, 2021 at 18:44
• @amyaner Isn't the phase calculated based on an atan() or atan2()? What are their limits? What happens when they are reached? unwrap() is meant to take care of them, but if the phase is noisy, the derivative will amplify the imperfections. Mar 3, 2021 at 18:48
• Good questions! It looks like np.angle uses arctan2, which is based on the underlying atan2 function in the C library. I am not sure about it's limitations. Googling now. Mar 3, 2021 at 20:13
• The domain of atan() is -pi/2...pi/2; atan2() extends that to all the four quadrants. But what I was hinting at was that the wrapping of the phase is not your only problem, it's (probably) the noise, too. And the reason I said "my guess" in the conclusion is because I can't know for sure, you have your data, you can analyze it, and determine if the noise is enough such that the differentiation can cause those high spikes. It could also be that around the wrapping the phase has jitter, and it wraps back and forth. Or other causes. Mar 3, 2021 at 20:18
• I ran some tests with multiplying 2 different constant frequency sine functions together and got negative instantaneous frequencies in some spots. There is no noise in this case. I will update the question with some images so you can see what I am observing. Learning so much by running a bunch of tests! This is fascinating stuff! Thanks for your feedback! Mar 3, 2021 at 21:44