Contents
Deep Breakdown
What it tells me (Situation Awareness): The complex numbers extend by introducing , the imaginary unit. Every complex number has a real part and an imaginary part . When , reduces to a real number — so . The key algebraic property is that is algebraically closed: every polynomial with complex coefficients has all its roots in (the Fundamental Theorem of Algebra). In DA/DS, complex numbers are used in: Fast Fourier Transform (FFT) for time-series seasonality detection, eigenvalue analysis of non-symmetric matrices, signal processing, and stability analysis of dynamical systems.
Flowchart
Real-world use
Where it is used
FFT for detecting cyclical patterns in time series: np.fft.fft(signal) returns complex numbers representing amplitude and phase of each frequency component. Eigenvalue decomposition of non-symmetric matrices (covariance-like matrices of non-symmetric systems have complex eigenvalues). Stability analysis of AR models (eigenvalues of the companion matrix). Control systems modeling. Quantum computing simulation.
Problem it solves
is not algebraically closed: has no real solution. Many important computations — DFT/FFT, characteristic polynomials of non-symmetric matrices, transfer functions in signal processing — produce complex-valued intermediate or final results. provides the complete algebraic structure needed.
How it solves the problem
By defining , every polynomial of degree has exactly roots in (counting multiplicity). The complex plane (2D representation of ) provides geometric intuition: multiplication by is rotation by . The FFT decomposes any signal into complex exponentials , revealing frequency content.
Example in a business
Monthly sales seasonality detection: fft_output = np.fft.fft(monthly_sales). The output is a complex array where np.abs(fft_output) gives the amplitude spectrum (which frequencies are strong) and np.angle(fft_output) gives the phase. The dominant non-zero frequency reveals the seasonal period (e.g., a peak at frequency confirms annual seasonality in monthly data).
The formula, piece by piece
Breaking the notation:
- : blackboard-bold C, for "complex numbers."
- : the standard form. = real part, = imaginary part, .
- : both components are real numbers — complex numbers are pairs of reals.
- The set is uncountably infinite, like , but 2-dimensional.
Arithmetic in :
- Addition:
- Multiplication: (using )
- Division: (multiply by conjugate)
- Modulus: (distance from origin in complex plane)
- Conjugate: Python notation:
z = 3 + 4j(jinstead ofito avoid confusion with current variable name in engineering).
The formula in words
" is the set of all numbers of the form where and are real and is the square root of . Every real number is a complex number with . Complex numbers live on a 2D plane — real axis horizontally, imaginary axis vertically. Multiplication rotates and scales."
Critical Concepts
- Python uses
jfor :1jis the imaginary unit in Python.(2 + 3j) * (1 - 2j)computes complex multiplication. Do not name a variablejwhen working with complex numbers. - FFT output is complex:
np.fft.fft(x)always returns a complex array. You typically wantnp.abs(fft_output)(amplitude) andnp.angle(fft_output)(phase). The real part alone is not meaningful without the imaginary part. - Complex eigenvalues indicate oscillation: if a matrix has complex eigenvalues , the associated dynamics oscillate. If , the oscillation decays (stable). If , it grows (unstable). This is critical for AR model stability.
- numpy dtype:
np.complex128(twofloat64values) is the standard complex type.
Memory usage is double that of float64.
Step-by-step calculation (tiny example)
Task: multiply and , then find modulus.
Step 1: Multiply using FOIL
Step 2: Expand
Step 3: Substitute
Step 4: Modulus of result
Python check: (2 + 3j) * (1 - 2j) returns (8-1j). abs(8 - 1j) returns 8.06226....
Code examples
Beginner example (Python)
# Complex numbers ℂ = {a + bi | a, b ∈ ℝ}
# Python uses j for i
# Basic complex arithmetic
z1 = 2 + 3j
z2 = 1 - 2j
print("Complex number arithmetic:")
print(f" z1 = {z1}")
print(f" z2 = {z2}")
print(f" z1 + z2 = {z1 + z2}")
print(f" z1 × z2 = {z1 * z2}")
print(f" z1 / z2 = {z1 / z2:.4f}")
print(f" |z1| = {abs(z1):.4f} (modulus = √(2²+3²))")
print(f" Re(z1) = {z1.real}, Im(z1) = {z1.imag}")
print(f" conj(z1) = {z1.conjugate()}") # FFT: complex numbers in practice
import numpy as np
np.random.seed(42)
n_samples = 128
sample_rate = 128 # Hz
t = np.linspace(0, 1, n_samples, endpoint=False) # Signal: 5 Hz + 12 Hz sine waves + noise
signal = (np.sin(2 * np.pi * 5 * t) + 0.5 * np.sin(2 * np.pi * 12 * t) + 0.1 * np.random.randn(n_samples))
fft_output = np.fft.fft(signal) # complex array
frequencies = np.fft.fftfreq(n_samples, d=1/sample_rate)
amplitude = np.abs(fft_output) # magnitudes (real)
phase = np.angle(fft_output) # phases (real)
print(f"\nFFT output (complex array):")
print(f" dtype: {fft_output.dtype} ← complex128")
print(f" shape: {fft_output.shape}")
print(f" [0]: {fft_output[0]:.2f} ← DC component (real ≈ sum of signal)") # Find dominant frequencies
positive_freq_mask = frequencies > 0
peak_idxs = amplitude[positive_freq_mask].argsort()[-3:][::-1]
top_freqs = frequencies[positive_freq_mask][peak_idxs]
top_amps = amplitude[positive_freq_mask][peak_idxs]
print(f"\nTop-3 detected frequencies:")
for f, a in zip(top_freqs, top_amps):
print(f" {f:.1f} Hz amplitude = {a:.2f}")Beginner example (R)
# Complex numbers in R
z1 <- 2 + 3i
z2 <- 1 - 2i
cat("Complex arithmetic:\n")
cat(sprintf(" z1 = %s\n", as.character(z1)))
cat(sprintf(" z2 = %s\n", as.character(z2)))
cat(sprintf(" z1 + z2 = %s\n", as.character(z1 + z2)))
cat(sprintf(" z1 × z2 = %s\n", as.character(z1 * z2)))
cat(sprintf(" |z1| = %.4f\n", Mod(z1)))
cat(sprintf(" Re(z1) = %.1f, Im(z1) = %.1f\n", Re(z1), Im(z1)))
cat(sprintf(" Conj(z1)= %s\n", as.character(Conj(z1)))) # FFT in R
set.seed(42)
n_samples <- 128
t <- seq(0, 1, length.out = n_samples, by = FALSE)[1:n_samples]
signal <- sin(2 * pi * 5 * t) + 0.5 * sin(2 * pi * 12 * t) + 0.1 * rnorm(n_samples)
fft_output <- fft(signal) # complex vector
amplitude <- Mod(fft_output) # magnitudes
frequencies <- (0:(n_samples - 1)) * (n_samples / n_samples) # freq indices
cat("\nFFT output:\n")
cat(sprintf(" Length: %d\n", length(fft_output)))
cat(sprintf(" Class: %s\n", class(fft_output)))
cat(sprintf(" [1]: %.2f + %.2fi (DC component)\n", Re(fft_output[1]), Im(fft_output[1])))
top_3 <- order(amplitude[1:(n_samples/2)], decreasing = TRUE)[2:4] # skip DC
cat("\nTop-3 frequencies (by amplitude):\n")
for (idx in top_3) { cat(sprintf(" freq_idx=%3d amplitude=%.2f\n", idx - 1L, amplitude[idx]))
}Professional example (Python)
import numpy as np
import pandas as pd # Scenario: FFT-based seasonality detection in monthly sales data
# Complex numbers are the mathematical engine behind the FFT
np.random.seed(42)
N_MONTHS = 48 # 4 years of monthly data # Simulate sales with annual (12-month) seasonality + trend + noise
months = np.arange(N_MONTHS)
trend = 1000 + 5 * months
seasonal = 200 * np.sin(2 * np.pi * months / 12) + 100 * np.sin(2 * np.pi * months / 6)
noise = np.random.normal(0, 50, N_MONTHS)
sales = trend + seasonal + noise
df = pd.DataFrame({ "month": pd.date_range("2020-01-01", periods=N_MONTHS, freq="MS"), "sales": sales.round(2),
})
def detect_seasonality( signal: np.ndarray, sample_rate: float = 1.0, top_n: int = 5,
) -> pd.DataFrame:
""" Detect dominant seasonal periods via FFT. Args: signal: 1D time series array. sample_rate: Observations per unit time (default 1 = monthly). top_n: Number of top frequency components to return. Returns: DataFrame with frequency, period, and amplitude columns. """
n = len(signal)
detrended = signal - np.polyval(np.polyfit(np.arange(n), signal, deg=1), np.arange(n))
fft_vals = np.fft.rfft(detrended) # complex array
frequencies = np.fft.rfftfreq(n, d=1 / sample_rate)
amplitudes = np.abs(fft_vals) # magnitude of complex values # Exclude DC component (freq = 0)
nonzero_mask = frequencies > 0
freqs_nz = frequencies[nonzero_mask]
amps_nz = amplitudes[nonzero_mask]
top_idx = amps_nz.argsort()[-top_n:][::-1]
return pd.DataFrame({ "frequency": freqs_nz[top_idx].round(4), "period_months": (1 / freqs_nz[top_idx]).round(1), "amplitude": amps_nz[top_idx].round(2), })
seasonality_report = detect_seasonality(df["sales"].values, sample_rate=1.0)
print("FFT Seasonality Detection (ℂ-based FFT)")
print("=" * 44)
print(f" Signal length: {N_MONTHS} months")
print(f" FFT output type: np.complex128 (pairs of float64)")
print()
print("Top seasonal components:")
print(seasonality_report.to_string(index=False))
print("\nKey periods to investigate:")
for _, row in seasonality_report.iterrows():
print(f" Period = {row['period_months']:.1f} months (amplitude = {row['amplitude']:.1f})")Professional example (R)
# FFT returns complex (ℂ); extract period and amplitude for seasonality
set.seed(42)
N_MONTHS <- 60
t <- 0:(N_MONTHS - 1)
sales <- 10000 + 2000 * sin(2 * pi * t / 12) + 500 * sin(2 * pi * t / 3) + rnorm(N_MONTHS, 0, 100)
fft_out <- fft(sales)
amplitudes <- Mod(fft_out[1:(N_MONTHS/2)])
freqs <- (0:(N_MONTHS/2 - 1)) / N_MONTHS
periods <- 1 / freqs
seasonality_report <- data.frame( period_months = round(periods[order(-amplitudes)[1:5]], 1), amplitude = round(amplitudes[order(-amplitudes)[1:5]], 1)
)
cat("FFT output type: complex\n")
cat("Top seasonal components:\n")
print(seasonality_report)
cat("Key periods:", paste(round(seasonality_report$period_months, 1), "months"), "\n")Source: 1300 p. 15