The spectrum shows ripples that we can visually quantify as ~50 MHz ripples. I am looking for a method to calculate the frequency of these ripples other than by visual inspection of thousands of spectra. Since the function is in frequency domain, taking FFT would get it back into time domain (with time reversal if I am correct). How can we get frequency of these ripples?
-
@SiHa -- images uploaded. – Newbie gamer Jun 24 '20 at 10:13
-
I would use autocorrelation analysis instead of FFT for this. – manu190466 Jun 24 '20 at 10:31
-
@manu190466 -- imagine you only have a single spectrum, how will you compute the frequency of these ripples? – Newbie gamer Jun 24 '20 at 10:41
-
@LPGaming I got it. I am thinking about it and I will get back to you. I know where the confusion lies, but I am composing a decent answer, so bare with me. – jpnadas Jun 24 '20 at 10:50
-
Thanks for the heads up, I saw in your code the low-pass filter to smooth the signal and I am also using that in my final solution. – jpnadas Jun 24 '20 at 10:59
2 Answers
The problem arises from the fact that you are making a confusion between the term 'frequency' which you are measuring and the frequency of your data.
What you want is the ripple frequency, which actually is the period of your data.
With that out of the way, let's have a look at how to fix your fft.
As pointed out by Dmitrii's answer, you must determine the sampling frequency of your data and also get rid of the low frequency components in your FFT result.
To determine the sampling frequency, you can determine the sampling period by subtracting each sample by its predecessor and computing the average. The average sampling frequency will just be the inverse of that.
fs = 1 / np.mean(freq[1:] - freq[:-1])
For the high pass filter, you may use a butterworth filter, this is a good implementation.
# Defining a high pass filter
def butter_highpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
return b, a
def butter_highpass_filter(data, cutoff, fs, order=5):
b, a = butter_highpass(cutoff, fs, order=order)
y = signal.filtfilt(b, a, data)
return y
Next, when plotting the fft, you need to take the absolute value of it, that is what you are after. Also, since it gives you both the positive and negative parts, you can just use the positive one. As far as the x-axis is concerned, it will be from 0 to half of your sampling frequency. This is further explored on this answer
fft_amp = np.abs(np.fft.fft(amp, amp.size))
fft_amp = fft_amp[0:fft_amp.size // 2]
fft_freq = np.linspace(0, fs / 2, fft_amp.size)
Now, to determine the ripple frequency, simply obtain the peak of the FFT. The value you are looking for (around 50MHz) will be the period of the ripple peak (in GHz), since your original data was in GHz. For this example, it is actually around 57MHz.
peak = fft_freq[np.argmax(fft_amp)]
ripple_period = 1 / peak * 1000
print(f'The ripple period is {ripple_period} MHz')
And here is the complete code, which also plots the data.
import numpy as np
import pylab as plt
from scipy import signal as signal
# Defining a high pass filter
def butter_highpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
return b, a
def butter_highpass_filter(data, cutoff, fs, order=5):
b, a = butter_highpass(cutoff, fs, order=order)
y = signal.filtfilt(b, a, data)
return y
with open('ripple.csv', 'r') as fil:
data = np.genfromtxt(fil, delimiter=',', skip_header=True)
amp = data[:, 0]
freq = data[:, 1]
# Determine the sampling frequency of the data (it is around 500 Hz)
fs = 1 / np.mean(freq[1:] - freq[:-1])
# Apply a median filter to remove the noise
amp = signal.medfilt(amp)
# Apply a highpass filter to remove the low frequency components 5 Hz was chosen
# as the cutoff fequency by visual inspection. Depending on the problem, you
# might want to choose a different value
cutoff_freq = 5
amp = butter_highpass_filter(amp, cutoff_freq, fs)
_, ax = plt.subplots(ncols=2, nrows=1)
ax[0].plot(freq, amp)
ax[0].set_xlabel('Frequency GHz')
ax[0].set_ylabel('Intensity dB')
ax[0].set_title('Filtered signal')
# The FFT part is as follows
fft_amp = np.abs(np.fft.fft(amp, amp.size))
fft_amp = fft_amp[0:fft_amp.size // 2]
fft_freq = np.linspace(0, fs / 2, fft_amp.size)
ax[1].plot(fft_freq, 2 / fft_amp.size * fft_amp, 'r-') # the red plot
ax[1].set_xlabel('FFT frequency')
ax[1].set_ylabel('Intensity dB')
plt.show()
peak = fft_freq[np.argmax(fft_amp)]
ripple_period = 1 / peak * 1000
print(f'The ripple period is {ripple_period} MHz')
And here is the plot:

- 774
- 6
- 17
-
Thank you for an elaborate answer. I will go through your words in detail, understand every bit of the math and code etc., and revert. Meanwhile, I tried running the code, it threw an error of x & y array mismatch at line -- ax[1].plot(fft_freq, 2 / fft_amp.size * fft_amp, 'r-') # the red plot. Please let me know if you're getting the same. Thanks. – Newbie gamer Jun 24 '20 at 11:38
-
There was a typo on the original answer. I have edited it. Can you try again? – jpnadas Jun 24 '20 at 11:40
-
Fantastic! it works... Thank you for a quick reply. I will go through it in detail and revert in case I need more help. Cheers. – Newbie gamer Jun 24 '20 at 11:43
-
Sure, if it is working as expected, you might want to mark the answer as accepted, so people know it solved the problem when they find this thread. – jpnadas Jun 24 '20 at 11:43
-
I have marked it as the accepted answer with a green tick mark. I am new to this website, so please let me know in case I miss something. Thanks once again! – Newbie gamer Jun 24 '20 at 11:50
-
What I would do in this case is to convert the channels to their frequency levels, and then run the rest of the code as is. If you don't, you are going to have to determine the sampling frequency in a different manner. To select the cutoff frequency, I looked at the FFT of the unfiltered signal and visually determined that 5Hz would be enough to get rid of the low frequency components. – jpnadas Jun 25 '20 at 09:10
-
-
Just wondering, the biggest spike at ~17 in your red subplot...the x-axis says FFT frequency. Doesn't it mean 17 cycles per frequency? since our raw data x-axis was frequency. And if it's 17 cycles per frequency -- does it mean 17 cycles per 1 GHz? since my original frequency unit is GHz? I request more clarification. Thank you – Newbie gamer Jun 26 '20 at 20:11
To get a proper spectrum for the blue plot you need to do two things:
- Properly calculate frequencies for the spectrum plot (the red one)
- Remove bias in the data so the spectrum is less contaminated with low frequencies. That's because you're interested in the ripple, not in the slow fluctuations.
Note, that when you compute fft, you get complex values that contain information about both amplitude and phase of oscillations for each frequency. In your case, the red plot should be an amplitude spectrum (compared to the phase spectrum). To get that, we take absolute values of fft coefficients.
Also, the spectrum you get with fft is two-sided and symmetric (since the signal is real). You really need only one side to get the idea where your ripple peak frequency is. I've implemented this in code.
After playing with your data, here's what I've got:
import pandas as pd
import numpy as np
import pylab as plt
import plotly.graph_objects as go
from scipy import signal as sig
df = pd.read_csv("ripple.csv")
f = df.Frequency.to_numpy()
data = df.Data
data = sig.medfilt(data) # median filter to remove the spikes
fig = go.Figure()
fig.add_trace(go.Scatter(x=f, y=(data - data.mean())))
fig.update_layout(
xaxis_title="Frequency in GHz", yaxis_title="dB"
) # the blue plot with ripples
fig.show()
# Remove bias to get rid of low frequency peak
data_fft = np.fft.fft(data - data.mean())
L = len(data) # number of samples
# Compute two-sided spectrum
tssp = abs(data_fft / L)
# Compute one-sided spectrum
ossp = tssp[0 : int(L / 2)]
ossp[1:-1] = 2 * ossp[1:-1]
delta_freq = f[1] - f[0] # without this freqs computation is incorrect
freqs = np.fft.fftfreq(f.shape[-1], delta_freq)
# Use first half of freqs since spectrum is one-sided
plt.plot(freqs[: int(L / 2)], ossp, "r-") # the red plot
plt.xlim([0, 50])
plt.xticks(np.arange(0, 50, 1))
plt.grid()
plt.xlabel("Oscillations per frequency")
plt.show()
So you can see there are two peaks: low-freq. oscillations between 1 and 2 Hz and your ripple at around 17 oscillations per GHz.

- 91
- 6
-
Excellent answer. The only thing missing is how to get from the 17 Hz to the 50MHz ripple period he is seeking. Have a look at [my take](https://stackoverflow.com/a/62553978/8250094) on the problem. – jpnadas Jun 24 '20 at 11:28
-
Thank you Dmitrii for pointing out low-frequency peak removals. @jpnadas has explained further steps in his answer. Thank you both for sharing knowledge. – Newbie gamer Jun 24 '20 at 11:40
-
@LPGaming, you can only mark 1 answer as accepted at a time. Since I went further into your problem, maybe you should consider marking mine. – jpnadas Jun 24 '20 at 11:47
-
@jpnadas, It was confusing to me, how you get this ~50 MHz frequency, but from your answer I see, what is meant by that. Still, I don't really get the physical sense of it, since this ~50 MHz (or 57 as we know now) isn't really the frequency of the ripple. Rather it's the ripple period in the original (blue) spectrum, as you pointed out. It seems a bit strange to me since in the time domain we usually characterize narrow-band noise by its frequency, not by the period, although it's equivalent. – Dmitrii Altukhov Jun 24 '20 at 11:52
-
oops! my bad, I first clicked on your answer, then on Dmitrii's. The error has been fixed... @jpnadas answer is marked as the accepted answer since he provided the complete answer. – Newbie gamer Jun 24 '20 at 11:53
-
-
@DmitriiAltukhov From your red plot, if I understand it correctly -- there are 1 cycle per frequency and 17 cycles per frequency spikes. If we focus on spike at 17, does it mean 17 cycles per GHz?... that means 1 wave period is about 58.8 MHz ? – Newbie gamer Jun 26 '20 at 20:39
-
you have to think of your GHz frequencies as a "time" axis to understand this properly, and your ripples frequency will be the period of the ripple wave. So in that analogy, you get the frequency you find on the FFT, and that will be the frequency of occurrence of your ripple, so the period is the inverse of that. In the example, `1/17 = 0.057`, which means that the ripple frequency will be `0.057 GHz`, or `57 MHz`. It is a bit confusing, I know. – jpnadas Jun 26 '20 at 21:32