I'm trying to compute the phase angle between two time-series of real numbers. To check if my function is working without errors I have created two sine waves with a phase of 17 degrees. However, when I compute the phase angle between those two sine waves I do not get the 17 degrees. Here's my script:
import numpy as np
from scipy.signal import hilbert
import matplotlib.pyplot as plt
def coupling_angle_hilbert(x, y, datatype, center=True, pad=True):
"""
Compute the phase angle between two time series using the Hilbert transform.
Parameters:
- x: numpy array
Time series data for the first signal.
- y: numpy array
Time series data for the second signal.
- center: bool, optional
If True, center the amplitude of the data around zero. Default is True.
- pad: bool, optional
If True, perform data reflection to address issues arising with data distortion. Default is True.
- unwrap: bool, optional
If True, unwrap the phase angle to avoid phase wrapping. Default is True.
Returns:
- phase_angle: numpy array
Phase angle between the two signals.
"""
# Convert input data to radians if specified as degrees
if datatype.lower().strip() == 'degs':
x = np.radians(x)
y = np.radians(y)
# Center the signals if the 'center' option is enabled
if center:
# Adjust x to be centered around zero: subtract minimum, then offset by half the range
x = x - np.min(x) - ((np.max(x) - np.min(x))/2)
# Adjust y to be centered around zero: subtract minimum, then offset by half the range
y = y - np.min(y) - ((np.max(y) - np.min(y))/2)
# Reflect and pad the data if padding is enabled
if pad:
# Number of padding samples equal to signal length
# Ensure that the number of pads is even
npads = x.shape[0] // 2 * 2 # Ensure npads is even
# Reflect data at the beginning and end to create padding for 'x' and 'y'
x_padded = np.concatenate((x[:npads][::-1], x, x[-npads:][::-1]))
y_padded = np.concatenate((y[:npads][::-1], y, y[-npads:][::-1]))
else:
# If padding not enabled, use original signals without modification
x_padded = x
y_padded = y
# Apply the Hilbert transform to the time series data
hilbert_x = hilbert(x_padded)
hilbert_y = hilbert(y_padded)
# Calculate the phase of each signal by using arctan2 on imaginary and real parts
phase_angle_x = np.arctan2(hilbert_x.imag, x_padded)
phase_angle_y = np.arctan2(hilbert_y.imag, y_padded)
# Calculate the phase difference between y and x
phase_angle = phase_angle_y - phase_angle_x
# Trim the phase_angle to match the shape of x or y
if pad:
# Remove initial and ending padding to return only the original signal's phase angle difference
phase_angle = phase_angle[npads:npads + x.shape[0]]
return phase_angle
# input data
angles = np.radians(np.arange(0, 360, 1))
phase_offset = np.radians(17)
wav1 = np.sin(angles)
wav2 = np.sin(angles + phase_offset)
# Compute phase_angle usig Hilbert transform
ca_hilbert = coupling_angle_hilbert(wav1,
wav2,
'rads',
center=True,
pad=True)
plt.plot(np.degrees(ca_hilbert))
plt.show()
Thank you n advance for any help.
Instead of using arctan2(hilbert.imag, x)
, you can use np.angle()
, which returns the argument (angle) of a complex number (always in the range [-π, π]
). It's essentially doing arctan2(y, x)
for a complex number x + iy
.
Also, after phase_angle = phase_y - phase_x
, we need to ensure again that it lies in [-π, π], so we do phase_angle = np.angle(np.exp(1j * phase_angle))
, following the documentation.
Thus, your function becomes:
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import hilbert
def coupling_angle_hilbert(x, y, datatype="rads", center=True, pad=True):
"""
Compute the phase angle between two time series using the Hilbert transform.
Parameters:
- x: numpy array
Time series data for the first signal.
- y: numpy array
Time series data for the second signal.
- datatype: str, optional
Specify if input is in 'rads' or 'degs'. Default is 'rads'.
- center: bool, optional
If True, center the amplitude of the data around zero. Default is True.
- pad: bool, optional
If True, perform data reflection to address issues arising with data distortion. Default is True.
Returns:
- phase_angle: numpy array
Phase angle between the two signals in radians.
"""
if datatype.lower().strip() == "degs":
x = np.radians(x)
y = np.radians(y)
# Center the signals if the 'center' option is enabled
if center:
# Adjust x to be centered around zero: subtract minimum, then offset by half the range
x = x - np.min(x) - ((np.max(x) - np.min(x)) / 2)
# Adjust y to be centered around zero: subtract minimum, then offset by half the range
y = y - np.min(y) - ((np.max(y) - np.min(y)) / 2)
# Reflect and pad the data if padding is enabled
if pad:
# Number of padding samples equal to signal length
# Ensure that the number of pads is even
npads = x.shape[0] // 2 * 2 # Ensure npads is even
# Reflect data at the beginning and end to create padding for 'x' and 'y'
x_padded = np.concatenate((x[:npads][::-1], x, x[-npads:][::-1]))
y_padded = np.concatenate((y[:npads][::-1], y, y[-npads:][::-1]))
else:
# If padding not enabled, use original signals without modification
x_padded = x
y_padded = y
# Apply the Hilbert transform to the time series data
hilbert_x = hilbert(x_padded)
hilbert_y = hilbert(y_padded)
# Calculate the instantaneous phases
phase_x = np.angle(hilbert_x)
phase_y = np.angle(hilbert_y)
# Calculate the phase difference
phase_angle = phase_y - phase_x
# Ensure phase angle is in [-π, π]
phase_angle = np.angle(np.exp(1j * phase_angle))
# Trim the phase_angle to match the shape of x or y
if pad:
# Remove initial and ending padding to return only the original signal's phase angle difference
phase_angle = phase_angle[npads : npads + x.shape[0]]
return phase_angle
With that, I get 17.41
as phase difference.