Understanding Signal Sampling and Sampling Theorem (Signals and Systems Review Part3)

2025.10.04 パラディ島

Before we start

This article is the third article in the series of Signals and Systems Review. It will cover signal sampling, reconstructing, sampling frequency and Nyquist frequency.

What is sampling?

Before understanding signal sampling, let’s get to know the difference between continuous and discrete signals first. A continuous signal means that for any point on the x-axis, there is a corresponding y-value. Conversely, for any y-value, there is also a corresponding x-value. As shown in the figure below:

Fig. 1. Continuous-time signal

However, a continuous signal contains infinite amount of values (imagine you can always find a point between any two points), and our computers aren’t strong enough to store infinite data. Therefore, we can sample the continuous signal to make it discrete, and reconstruct it back later on.

Fig. 2. Discrete signal

How to sample a signal?

As shown in Fig. 1 and Fig. 2, sampling is a process that turns a continuous signal to a discrete signal. If the sampling interval is short, there will be more sampling points. If the sampling interval is long, there will be less sampling points. From the perspective of time domain, it can be represented by the multiplication between a continuous signal and an impulse train. The impulse train is a series of impules, shown as below:

Fig. 3. Impulse train

Because the impulse train has an amplitude of 1 only at fixed intervals and 0 at all other times, multiplying it by a continuous signal can be understood as preserving the signal’s value at those fixed time instants while setting all other values to zero. In this way, we obtain a discrete signal.

Fig. 4. Continuous signal to discrete signal

We define the frequency of sampling per second as sampling frequency. Sampling frequency is a crucial parameter, and it has to be larger than a certain value so the sampled signal can be reconstructed.

Assume there’s a sinusoidal wave with period 2π, and its frequency is 1/2πHz. Let’s take 8Hz as our sampling frequency, below is our result:

Fig. 5. Signal frequency 1/2πHz, and sampling frequency 8Hz

As long as we connect the dots together, we get the original signal. But if we make the sampling frequency a bit lower to 4Hz, we will obtain:

Fig. 6. Signal frequency 1/2πHz, and sampling frequency 4Hz

It’s obvious that the dots are more sparse now, but we can still tell the original signal. Let’s make the sampling frequency even lower, we get:

Fig. 7. Signal frequency 1/2πHz, and sampling frequency 1Hz

Now the dots are pretty sparse, but we can still tell the original signal by observing the signal over few periods. When we make the sampling frequency to 0.1Hz, we get:

Fig. 8. Signal frequency 1/2πHz, and sampling frequency 0.1Hz

We can not reconstruct the signal anymore.

From the demonstration above, we already know that sampling frequency plays a crucial role in sampling. When the samping frequency becomes lower than a certain value, the signal cannot be reconstructed. This is called Sampling Theorem. To better understand this, let’s change our view from time domain to frequency domain.

Sampling frequency and signal frequency

As mentioned earlier, sampling a continuous signal in the time domain is equivalent to multiplying the signal by an impulse train. Now, what does the multiplication of two signals in the time domain correspond to in the frequency domain? Multiplying two signals in the time domain is equivalent to convolving them in the frequency domain. Although this conclusion may seem counterintuitive at first, it can be easily derived once we understand the Fourier transform.

To prove this theorem, let’s first review the Fourier transform. The Fourier transform converts a signal from the time domain to the frequency domain, while the inverse Fourier transform converts it back from the frequency domain to the time domain. The following are the formulas for the Fourier transform, the inverse Fourier transform, and convolution:

Equ. 1. Fourier transform, Equ. 2. Inverse Fourier transform, Equ. 3. Convolution

Let’s now derive the meaning of the multiplication of two time domain signals in frequency domain. Define the two time domain signals as f_1(t) and f_2(t), the multiplication of them is z(t). Their Fourier transforms are F_1(f), F_2(f) and Z(f).

Equ. 4. Derivation of convolution theorem

Therefore, multiplying two signals in the time domain is equivalent to convolving them in the frequency domain. This theorem is known as the Convolution Theorem. The convolution theorem is dual in nature — convolving two signals in the time domain corresponds to multiplying them in the frequency domain (the proof of this part is included at the end of the article, as it is not the main focus of this chapter).

Now that we know that multiplying signals in the time domain is equivalent to convolution in the frequency domain, we can interpret sampling as the convolution of a continuous-time signal with an impulse train in the frequency domain. Let’s examine what happens when these two signals are convolved in the frequency domain. Since we are interested in understanding how the sampling frequency affects a continuous-time signal, the continuous signal can take various forms. Suppose the spectrum of the continuous signal is as shown in the first plot of Fig. 9:

Fig. 9. From top to down are respectively 1. The frequency spectrum of continuous-time signal 2. The frequency spectrum of the impulse train 3. The frequency spectrum of the discrete signal

An impulse train remains an impulse train in the frequency domain. If its period in the time domain is T_s​, then its period in the frequency domain is 1/T_s (which is also f_s, since frequency and period are reciprocals), as shown in the second plot of Fig .9. The proof of this property involves Fourier series, and interested readers can refer to Note 2 at the end of this article.

In our article introducing convolution, we mentioned that convolution can be thought of as “flipping a signal and sliding it through another signal.” In other words, when the continuous signal is flipped and passed through the impulse train, the result is that the continuous signal appears periodically, with intervals determined by the spacing of the impulse train in the frequency domain. The resulting pattern is shown in the third plot of Fig. 9.

Fig.9 illustrates the relationship between continuous and discrete signals. In an ideal (distortion-free) case, sampling a continuous signal produces a discrete signal in the time domain, while in the frequency domain, the continuous signal becomes a periodic signal. To reconstruct the continuous signal from the discrete one, we simply keep one period of the periodic signal and remove the rest using a filter. This process restores the original signal, as shown below:

Fig. 10. Pass the signal through a low pass filter. After the filter removes high frequency components and keeps low frequency components, we obtain the original signal.

From Fig. 9, we can see that after sampling, the discrete signal becomes periodic in the frequency domain, and its period is determined by the period of the impulse train in the frequency domain. Since the impulse train causes the signal to become periodic in the frequency domain, the spacing between the impulses becomes a crucial factor.

Now, suppose the spacing between the impulses is reduced, and we perform the convolution again, as shown in the figure below:

Fig. 11. From top to down are respectively 1. The frequency spectrum of continuous-time signal 2. The frequency spectrum of the impulse train 3. The frequency spectrum of the sampled signal. Note that due to the short period of the impulse train, the sampled signal looks completely different from the original.

From Fig. 11, we can see that when the period of the impulse train becomes shorter, the first and third plots in Fig. 11 become completely different. This happens because the rectangular spectra overlap, resulting in distortion. This phenomenon is known as aliasing.

Now that we know distortion occurs when the sampling frequency is too low, the next question is: how low is too low? In other words, is there a specific sampling frequency below which distortion will occur? The answer is yes.

Fig. 12. The frequency spectrum of the sampled signal

We denote the sampling frequency as f_s​ and the signal frequency as f. If we decrease the sampling frequency f_s, the two red lines indicated by the arrows in Fig. 12 will move closer together until they touch, at which point aliasing occurs.

Therefore, to preserve the original continuous signal, the sampling frequency must not be too low. It must ensure that the two red lines never overlap — the right red line must always remain to the right of the left red line. In other words,

Equ. 5. The condition for aliasing not to happen

By simplifying Equ. 5, we get

Equ. 6. The condition for aliasing not to happen

This conclusion is called sampling theorem, and half of the sampling frequency is called Nyquist Frequency. The sampling theorem has a lot of application in real life. For instance, the sampling frequency of a CD is 44.1kHz, meaning that the highest frequency of the signal cannot be greater than 44.4k/2Hz.

Let’s see in time domain what will happen if the sampling frequency is lower than twice of the signal frequency.

Fig. 13. The sampling frequency is lower than 2f, making us unable to reconstruct the original signal.

From Fig. 5. we know that as long as the sampled points are dense enough, it is easy to reconstruct. However, Fig. 12. shows that as long as the sampling frequency is lower than 2f, we may have multiple candidates for the original, and we cannot even tell which one is the correct one.

In this article, we learnt that how signals are sampled, reconstructed, what is the limitation on sampling frequency. It is worth mentioning that in addition to low pass filters, there are other different types of filters used in different scenarios. This topic will also be covered in this series in the future.

Note 1: Sampling theorem

Sampling theorem contains two main ideas.

  1. The multiplication of two signals in time domain corresponds to their convolution in frequency domain.
  2. The convolution of two signals in time domain corresponds to their multiplication in frequency domain.

The first one was already proven above. Let’s now derive the second one.

Equ. 7. Derivation of convolution theorem

Note2: The Fourier transform of impulse train

Assume s(t) is an impulse train with period T_s, it can be written as

Equ. 8. Expression of impulse train

where δ is the impulse function, meaning that δ(t) is an impulse at t=0 and is zero everywhere else.

To understand how it looks like in frequency domain, we need to apply Fourier transform to this signal. However, the result will look hard to be interpreted. Therefore, we rewrite Equ. 8. to another form first.

Impulse train is a periodic function, can be expressed by Fourier series.

Equ. 9. Impulse train written by Fourier series

where

Equ. 10. The coefficient of Fourier series

Therefore,

Equ. 11. Impulse train written in Fourier series

Apply Fourier transform to it

Equ. 12. Fourier transform of impulse train

We can tell from Equ. 12. that the impulse train with period of T_s in time domain is still an impulse train with period of 1/T_s in frequency domain.

Python simulation

#Fig. 1. Continuous-time signal
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 4 * np.pi, 1000)
y = np.sin(x)
plt.plot(x, y, label='sin(x)', color='blue')
plt.title('Continuous signal')
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.grid(True)
plt.legend()
plt.show()
#Fig. 2. Discrete signal
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 4 * np.pi, 40)
y = np.sin(x)
plt.stem(x, y, use_line_collection=True, basefmt=" ", label='sin(x)')
plt.title('Discrete signal')
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.grid(True)
plt.legend()
plt.show()
#Fig. 3. Impulse Train
import numpy as np
import matplotlib.pyplot as plt
# === Impulse Train parameters ===
Fs = 8 # Frequency
Ts = 1 / Fs # Period
T_total = 2 # Total time (s)
# Contrusting time axis and position of the impulse
t_impulse = np.arange(0, T_total, Ts)
amplitude = np.ones_like(t_impulse) # The amplitude of each impulse is 1
# === Figure ===
plt.figure(figsize=(10, 3))
plt.stem(t_impulse, amplitude, use_line_collection=True,
linefmt='gray', markerfmt='ko', basefmt=" ", label='Impulse Train')
plt.title('Impulse Train')
plt.xlabel('x')
plt.ylabel('Amplitude')
plt.ylim(0, 1.2)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
#Fig. 4. Continuous signal to discrete signal
import numpy as np
import matplotlib.pyplot as plt
# === Parameters ===
T = 2 * np.pi # Period of sin wave
N_cycles = 2 # Draw two periods
Fs = 8 # Sampling frequency
Ts = 1 / Fs # Sampling period
# Time axis
t_cont = np.linspace(0, N_cycles * T, 1000) # Continuous time axis
x_cont = np.sin(t_cont) # Continuous signal
t_samp = np.arange(0, N_cycles * T, Ts) # Discrete sampled points
x_samp = np.sin(t_samp) # Sampled values
# === Sampled signal = sin(x) × impulse train ===
plt.figure(figsize=(10, 4))
plt.plot(t_cont, x_cont, color='lightgray', label='Continuous signal')
plt.stem(t_samp, x_samp, use_line_collection=True, basefmt=" ",
linefmt='orange', markerfmt='ro', label='Continuous signal × Impulse train')
plt.title('Discrete signal = Continuous signal × Impulse train')
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
#Fig. 9. From top to down are respectively 1. The frequency spectrum of continuous-time signal 2. The frequency spectrum of the impulse train 3. The frequency spectrum of the discrete signal
import numpy as np
import matplotlib.pyplot as plt
# --- Parameter ---
# The highest frequency of the signal is B
B = 20 # Hz
# Set a sampling frequency which meets sampling theorem fs > 2*B
fs = 100 # Hz
# Constructing frequency axis
f = np.linspace(-200, 200, 2000)
# --- 1. Constructing frequency spectrum of the continuous signal ---
# This is our assumed x(t) converted to X(f) by Fourier transform
X_f = np.zeros_like(f)
X_f[(f >= -B) & (f <= B)] = 1
# --- 2. Constructing impulse train in frequency domain ---
impulse_locations = np.arange(min(f), max(f), fs)
# --- 3. Visualize the result after convolution --
Y_f = np.zeros_like(f)
for loc in impulse_locations:
replica = np.zeros_like(f)
replica[((f - loc) >= -B) & ((f - loc) <= B)] = 1
Y_f += replica
# --- Draw ---
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 9), sharex=True)
fig.suptitle('Frequency Domain View of Proper Sampling (fs > 2B)', fontsize=16)
# Sub fig. 1.
ax1.plot(f, X_f, 'b', label=f'Original Signal Spectrum (B = {B} Hz)')
ax1.set_title('1. Original Signal Spectrum')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
ax1.legend()
# Sub fig. 2.
ax2.vlines(impulse_locations, 0, 1, 'g', label=f'Impulse Train (fs = {fs} Hz)')
ax2.set_title('2. Impulse Train in Frequency Domain (from Sampling)')
ax2.set_ylabel('Amplitude')
ax2.grid(True)
ax2.legend()
# Sub fig. 3.
ax3.plot(f, Y_f, 'r', label='Replicated Spectrums')
ax3.set_title('3. Result of Convolution: Sampled Signal Spectrum')
ax3.set_xlabel('Frequency (Hz)')
ax3.set_ylabel('Amplitude')
ax3.grid(True)
ax3.legend()
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
#Fig. 11. From top to down are respectively 1. The frequency spectrum of continuous-time signal 2. The frequency spectrum of the impulse train 3. The frequency spectrum of the sampled signal. Note that due to the short period of the impulse train, the sampled signal looks completely different from the original.
import numpy as np
import matplotlib.pyplot as plt
B = 20 # Hz
fs_aliased = 30 # Hz
f = np.linspace(-80, 80, 2000)
X_f = np.zeros_like(f)
X_f[(f >= -B) & (f <= B)] = 1
impulse_locations_aliased = np.arange(min(f), max(f) + fs_aliased, fs_aliased)
Y_f_aliased = np.zeros_like(f)
for loc in impulse_locations_aliased:
replica = np.zeros_like(f)
replica[((f - loc) >= -B) & ((f - loc) <= B)] = 1
Y_f_aliased += replica
# --- Draw ---
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 9), sharex=True, sharey=True)
fig.suptitle('Frequency Domain View of Improper Sampling (fs < 2B) -> Aliasing', fontsize=16)
# Sub fig. 1.
ax1.plot(f, X_f, 'b', label=f'Original Signal Spectrum (B = {B} Hz)')
ax1.set_title('1. Original Signal Spectrum (Identical to Proper Sampling Case)')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
ax1.legend()
# Sub fig. 2.
ax2.vlines(impulse_locations_aliased, 0, 1, 'g', label=f'Impulse Train (fs = {fs_aliased} Hz)')
ax2.set_title(f'2. Impulse Train in Frequency Domain (Note: fs < 2B)')
ax2.set_ylabel('Amplitude')
ax2.grid(True)
ax2.legend()
# Sub fig. 3.
ax3.plot(f, Y_f_aliased, 'm', label='Overlapped Spectrums')
ax3.set_title('3. Result of Convolution: Aliasing Occurs')
ax3.set_xlabel('Frequency (Hz)')
ax3.set_ylabel('Amplitude')
ax3.grid(True)
ax3.legend()
# ax3.set_ylim(bottom=0)
# Setting the range of x-axis
plt.xlim(-55, 55)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
#Fig. 13. The sampling frequency is lower than 2f, making us unable to reconstruct the original signal.
import numpy as np
import matplotlib.pyplot as plt
# --- Parameters ---
f_signal = 10 # Signal frequency
fs = 19.0 # Sampling frequency (lower than 2*f_signal = 20 Hz)
T = 1.0 # 1 sec
t_samples = np.arange(0, T, 1/fs)
y_samples = np.sin(2 * np.pi * f_signal * t_samples)
# --- Constructing time axis ---
t_continuous = np.linspace(0, T, 2000)
y_original = np.sin(2 * np.pi * f_signal * t_continuous)
# --- Plot four subplots ---
# Use sharex/sharey to align the graphs
fig, (ax0, ax1, ax2, ax3) = plt.subplots(4, 1, figsize=(12, 16), sharex=True, sharey=True)
fig.suptitle('Aliasing', fontsize=18)
# ==============================================================================
# Sub fig. 0.
# ==============================================================================
ax0.plot(t_continuous, y_original, 'lightblue', linewidth=3, label='Original Signal')
ax0.plot(t_samples, y_samples, 'o', color='red', markersize=8, label='Sample Points')
ax0.set_title('Step 1: The Sampling Process Itself')
ax0.grid(True)
ax0.legend()
ax0.set_ylabel('Amplitude')
# Denote frequency in the graph
ax0.text(0.7, 0.8, f'Original Signal Freq: {f_signal} Hz', fontsize=12, bbox=dict(facecolor='lightblue', alpha=0.5))
ax0.text(0.7, 0.5, f'Sampling Freq (fs): {fs} Hz', fontsize=12, bbox=dict(facecolor='red', alpha=0.3))
# Possible candidate 1
f1 = f_signal
y1 = y_original
ax1.plot(t_continuous, y1, 'lightblue', linewidth=3, label=f'Candidate Signal ({f1} Hz)')
ax1.plot(t_samples, y_samples, 'o', color='red', markersize=8, label='Sample Points')
ax1.set_title(f'Step 2: Interpretation - Possibility #1 (The Original Signal)')
ax1.grid(True)
ax1.legend()
ax1.set_ylabel('Amplitude')
# Possible candidate 1
f2 = f_signal - fs # 10 - 19 = -9 Hz
y2 = np.sin(2 * np.pi * f2 * t_continuous)
ax2.plot(t_continuous, y2, '--', color='magenta', linewidth=2, label=f'Candidate Signal ({int(f2)} Hz)')
ax2.plot(t_samples, y_samples, 'o', color='red', markersize=8, label='Sample Points')
ax2.set_title(f'Step 2: Interpretation - Possibility #2 (The Aliased Signal)')
ax2.grid(True)
ax2.legend()
ax2.set_ylabel('Amplitude')
# Possible candidate 1
f3 = f_signal + fs # 10 + 19 = 29 Hz
y3 = np.sin(2 * np.pi * f3 * t_continuous)
ax3.plot(t_continuous, y3, ':', color='green', linewidth=2, label=f'Candidate Signal ({int(f3)} Hz)')
ax3.plot(t_samples, y_samples, 'o', color='red', markersize=8, label='Sample Points')
ax3.set_title(f'Step 2: Interpretation - Possibility #3 (Another Aliased Signal)')
ax3.grid(True)
ax3.legend()
ax3.set_xlabel('Time (s)')
ax3.set_ylabel('Amplitude')
plt.tight_layout(rect=[0, 0.03, 1, 0.96])
plt.show()

Learn more about Understanding Signal Sampling and Sampling Theorem (Signals and Systems Review Part3)

Leave a Reply