A time-domain trace can hide repeating components when several oscillations overlap. A frequency-domain view separates those components into bins, making the strongest periodic component visible without changing the original samples.
For real-valued input, np.fft.rfft() returns only the non-negative half of the discrete Fourier transform. np.fft.rfftfreq() produces the matching bin centers when it receives the same sample count and the reciprocal of the sample rate as its spacing.
Frequency resolution equals the sample rate divided by the sample count, so a one-second record produces bins spaced one hertz apart. Measured input must be evenly sampled, and sample_rate must match the acquisition rate for the reported frequencies to remain meaningful.
Related: Create linearly spaced values
Related: Create an array
import numpy as np sample_rate = 32 duration_seconds = 1 sample_count = sample_rate * duration_seconds time = np.arange(sample_count) / sample_rate signal = ( np.sin(2 * np.pi * 5 * time) + 0.25 * np.sin(2 * np.pi * 9 * time) )
spectrum = np.fft.rfft(signal) frequencies = np.fft.rfftfreq(signal.size, d=1 / sample_rate) magnitudes = np.abs(spectrum) dominant_index = np.argmax(magnitudes[1:]) + 1 dominant_frequency = frequencies[dominant_index]
Starting np.argmax() at index 1 excludes the zero-frequency or DC bin from the peak search.
expected_frequency = 5.0 matches_expected = np.isclose(dominant_frequency, expected_frequency) print(f"frequency resolution: {frequencies[1] - frequencies[0]:.1f} Hz") print(f"dominant frequency: {dominant_frequency:.1f} Hz") print(f"dominant magnitude: {magnitudes[dominant_index]:.1f}") print(f"matches expected: {matches_expected}") if not matches_expected: raise RuntimeError("dominant frequency did not match the known signal")
$ python3 fft-calculate.py frequency resolution: 1.0 Hz dominant frequency: 5.0 Hz dominant magnitude: 16.0 matches expected: True