Note
Go to the end to download the full example code
Complex to real#
Complex-valued linear problems can be reformulated as real-valued problems by splitting out the real and imaginary parts of the equations.
Remember that we are solving for \(C_1 = (c_{1r} + c_{1i})\), which is also complex-valued, therefore,
This can be expanded out
which gives,
For the complex-valued problem, the aim is to solve for \(C_1\). Making this real-valued means we are solving for \(c_{1r}\) and \(c_{1i}\).
Moving from complex-valued to real-valued results in the following
Doubling the number of observations as the real and imaginary parts of the observations are split up
Doubling the number of regressors as we are now solving for the real and imaginary component of each regressor explicitly
Usually, it is better to solve the problem with complex-values as outlined in this thread:
https://stats.stackexchange.com/questions/66088/analysis-with-complex-data-anything-different
import numpy as np
import matplotlib.pyplot as plt
from regressioninc.linear.models import add_intercept, OLS, MEstimator
from regressioninc.testing.complex import ComplexGrid, generate_linear_grid
from regressioninc.testing.complex import add_gaussian_noise
from regressioninc.testing.complex import add_outliers, plot_complex
from regressioninc.linear.models import complex_to_real, real_params_to_complex
np.random.seed(42)
Let’s setup another linear regression problem with complex values
params = np.array([0.5 + 2j, -3 - 1j])
grid_r1 = ComplexGrid(r1=0, r2=10, nr=11, i1=-5, i2=5, ni=11)
grid_r2 = ComplexGrid(r1=-25, r2=-5, nr=11, i1=-5, i2=5, ni=11)
X, y = generate_linear_grid(params, [grid_r1, grid_r2], intercept=20 + 20j)
fig = plot_complex(X, y, {})
fig.set_size_inches(7, 6)
plt.tight_layout()
plt.show()

Add high leverage points to our regressors
seeds = [22, 36]
for ireg in range(X.shape[1]):
np.random.seed(seeds[ireg])
X[:, ireg] = add_outliers(
X[:, ireg],
outlier_percent=20,
mult_min=7,
mult_max=10,
random_signs_real=True,
random_signs_imag=True,
)
np.random.seed(42)
intercept = 20 + 20j
y = np.matmul(X, params) + intercept
fig = plot_complex(X, y, {})
fig.set_size_inches(7, 6)
plt.tight_layout()
plt.show()

Solve
parameter 0: 0.500000+2.000000j
parameter 1: -3.000000-1.000000j
parameter 2: 20.000000+20.000000j
Add some outliers
y_noise = add_gaussian_noise(y, loc=(0, 0), scale=(21, 21))
model_ls = OLS()
model_ls.fit(X, y_noise)
for idx, params in enumerate(model_ls.estimate.params):
print(f"parameter {idx}: {params:.6f}")
fig = plot_complex(X, y_noise, {"least squares": model_ls}, y_orig=y)
fig.set_size_inches(7, 9)
plt.tight_layout()
plt.show()

parameter 0: 0.504175+1.992051j
parameter 1: -3.002991-1.001990j
parameter 2: 19.634988+20.233521j
Add some outliers
y_noise = add_gaussian_noise(y, loc=(0, 0), scale=(21, 21))
model_mest = MEstimator()
model_mest.fit(X, y_noise)
for idx, params in enumerate(model_mest.estimate.params):
print(f"paramsficient {idx}: {params:.6f}")
fig = plot_complex(
X, y_noise, {"least squares": model_ls, "M estimate": model_mest}, y_orig=y
)
fig.set_size_inches(7, 9)
plt.tight_layout()
plt.show()

paramsficient 0: 0.509339+1.998580j
paramsficient 1: -2.999273-0.998400j
paramsficient 2: 20.362645+19.934598j
Try running as a real-valued problem
parameter 0: 0.509492+1.998838j
parameter 1: -2.999218-0.998313j
parameter 2: 20.273556+19.907992j
Try running using real-valued M_estimates
parameter 0: 0.516664+1.999746j
parameter 1: -3.003042-1.001296j
parameter 2: 19.825662+19.956621j
Total running time of the script: (0 minutes 3.295 seconds)