# Frequency spectrum of interpolated sine

When interpolating a sine wave in Python, it seems I get a lot of additional frequencies closely around the fundamental. Why is that the case? I would have expected a more or less similar spectrum, with images of the original spectrum at higher frequencies. How can I get a "clean" interpolated signal? I tried zero-order hold and linear interpolation, however that didn't seem to affect those frequencies around the fundamental.

import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.fftpack import fft
from scipy import interpolate

M = 32 # Oversampling
M_interp = 16
f_in = 1e6
amp_in = 1
f_s = f_in * M
f_s_interp = f_s * M_interp

N = int((f_s/f_in) * 1024) # Nr of samples
N_interp = N * M_interp

ax_t = np.linspace(0, float((1 / f_s) * (N - 1)), num=int(N), dtype=float)
ax_t_interp = np.linspace(0, float((1 / f_s) * (N - 1)), num=int(N_interp), dtype=float)
ax_f = np.fft.fftfreq(N, 1 / f_s)[0:N // 2]
ax_f_interp = np.fft.fftfreq(N_interp, 1 / f_s_interp)[0:N_interp // 2]

x = amp_in * np.cos(2 * math.pi * f_in * ax_t)

f_int_zero = interpolate.interp1d(ax_t, x, kind='zero')
f_int_lin = interpolate.interp1d(ax_t, x, kind='linear')
y_int_zero = f_int_zero(ax_t_interp)
y_int_lin = f_int_lin(ax_t_interp)

plt.step(ax_t, x, where='post', linewidth=2, label ='Sine')
plt.step(ax_t_interp, y_int_zero, linewidth=2, label ='Sine Interp Zero')
plt.step(ax_t_interp, y_int_lin, linewidth=2, label ='Sine Interp Lin')
plt.xlim(0, 1.25e-6)
plt.xlabel('Time [s]')
plt.ylabel('Amplitude')
plt.title('Time Domain')

plt.figure()
plt.scatter(ax_f, 20*np.log10(np.divide(np.abs(fft(x)), N/2))[0:N // 2], label = 'FFT Sine', marker='x', s = 80)
plt.scatter(ax_f_interp, 20 * np.log10(np.divide(np.abs(fft(y_int_zero)), N_interp / 2))[0:N_interp // 2], label ='FFT Sine Zero Interp', marker='*')
plt.scatter(ax_f_interp, 20 * np.log10(np.divide(np.abs(fft(y_int_lin)), N_interp / 2))[0:N_interp // 2], label ='FFT Sine Lin Interp', marker='*')
plt.ylim(-100, 5)
plt.xlim(0, 2.5e6)
plt.xlabel('Frequency [Hz]')
plt.ylabel('Magnitude')
plt.title('Freq Domain')

plt.show()


UPDATE: I coded the zero-order hold myself as shown below, which eliminated the frequencies closely around the fundamental.

y_int_zero = np.zeros(len(ax_t_interp))
for i in range(len(ax_t_interp)):
sample_index = int(i / M_interp)
y_int_zero[i] = x[sample_index]

• Well linear interpolation, done correctly, should perform a hella lot better than zero-order hold. You either need more points in your table or higher order interpolation. Oct 18, 2023 at 19:40
• Or you might consider coding a half of the sine with 5th or 7th order odd-symmetry polynomial. Oct 18, 2023 at 19:42
• And BTW, the points depicted in you spectrum of ZOH don't look correct at all. Oct 18, 2023 at 19:46
• The "correct" way of interpolating is sinc interpolation. See en.wikipedia.org/wiki/…. Oct 18, 2023 at 21:06
• Thanks @robertbristow-johnson due to your comment I tried to just hand-code the zero-order hold myself, result looks much better Oct 19, 2023 at 8:11

N = int((f_s/f_in) * 1024) # Nr of samples

To address spectral leakage concerns, window the waveform prior to computing the FFT. To interpolate with minimum distortion, zero insert to the new rate (insert $$I-1$$ zeros in between each sample). This will replicate the spectrum, and the replication in each Nyquist Zone (DC to the original sampling rate), will be exact with no distortion other than possible scaling. Then pass this waveform through an interpolation filter, which ideally passes the primary spectrum of interest with no distortion and completely rejects all the images. Doing this perfectly is not feasible, but at the expense of filter delay and complexity we can minimize the distortion to any level desired.
Least squares multiband filter design (using scipy.firls in Python) is ideal for this application as we can concentrate the nulls at all the image locations. This is demonstrated in the following graphic showing the replicated spectrum after a inserting 3 zeros between each sample (upsample by 4), along with the response of the multiband interpolation filter designed with firls: