Skip to content

Utilities

data2rad(data, k=360)

Convert data measured on a circular scale to corresponding angular directions.

\[ \alpha = \frac{2\pi \times \mathrm{data}}{k} \]

Parameters:

Name Type Description Default
data ndarray or float

Data measured on a circular scale.

required
k float or int

Number of intervals in the full cycle. Default is 360.

360

Returns:

Name Type Description
angle ndarray or float

Angular directions in radian.

Source code in pycircstat2/utils.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def data2rad(
    data: Union[np.ndarray, float, int],
    k: Union[float, int] = 360,  # number of intervals in the full cycle
) -> Union[np.ndarray, float]:  # eq(26.1), zar 2010
    r"""Convert data measured on a circular scale to
    corresponding angular directions.

    $$ \alpha = \frac{2\pi \times \mathrm{data}}{k} $$

    Parameters
    ----------
    data : np.ndarray or float
        Data measured on a circular scale.
    k : float or int
        Number of intervals in the full cycle. Default is 360.

    Returns
    -------
    angle: np.ndarray or float
        Angular directions in radian.
    """
    return 2 * np.pi * data / k

time2float(x, sep=':')

Convert an array of strings in time (hh:mm) to an array of floats.

Source code in pycircstat2/utils.py
40
41
42
43
44
45
46
47
48
49
def time2float(x: Union[np.ndarray, list, str], sep: str = ":") -> np.ndarray:
    """Convert an array of strings in time (hh:mm) to an array of floats."""

    def _t2f(x: str, sep: str):
        """Convert string of time to float. E.g. 12:45 -> 12.75"""
        hr, min = x.split(sep)
        return float(hr) + float(min) / 60

    t2f = np.vectorize(_t2f)
    return t2f(x, sep)

angmod(rad, bounds=[0, 2 * np.pi])

Normalize angles to a specified range.

Parameters:

rad : Union[np.ndarray, float, int] An angle or array of angles in radians. bounds : list, optional A list or tuple of two values [min, max] defining the target range. Default is [0, 2π).

Returns:

Union[np.ndarray, float] The normalized angle(s), constrained to the specified range.

Source code in pycircstat2/utils.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def angmod(
    rad: Union[np.ndarray, float, int], bounds: list = [0, 2 * np.pi]
) -> Union[np.ndarray, float]:
    """
    Normalize angles to a specified range.

    Parameters:
    -----------
    rad : Union[np.ndarray, float, int]
        An angle or array of angles in radians.
    bounds : list, optional
        A list or tuple of two values [min, max] defining the target range. Default is [0, 2π).

    Returns:
    --------
    Union[np.ndarray, float]
        The normalized angle(s), constrained to the specified range.
    """
    if len(bounds) != 2 or bounds[0] >= bounds[1]:
        raise ValueError(
            "bounds must be a list or tuple with two values [min, max] where min < max."
        )

    bound_min, bound_max = bounds
    bound_span = bound_max - bound_min
    result = ((rad - bound_min) % bound_span + bound_span) % bound_span + bound_min

    # Adjust values equal to bound_max to bound_min for consistency
    if isinstance(result, np.ndarray):
        result[result == bound_max] = bound_min
    elif result == bound_max:
        result = bound_min

    return result

angular_distance(a, b)

Angular distance between two angles.

Parameters:

Name Type Description Default
a Union[ndarray, list, float]

angle(s).

required
b float

target angle.

required

Returns:

Name Type Description
e ndarray

angular distance

Reference

P642, Section 27.2, Zar, 2010

Source code in pycircstat2/utils.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def angular_distance(a: Union[np.ndarray, list, float], b: float) -> np.ndarray:
    """Angular distance between two angles.

    Parameters
    ----------
    a: np.ndarray or float
        angle(s).

    b: float
        target angle.

    Returns
    -------
    e: np.ndarray
        angular distance

    Reference
    ---------
    P642, Section 27.2, Zar, 2010
    """

    a = np.array(a) if type(a) is list else a

    c = angmod(a - b)
    d = 2 * np.pi - c
    e = np.min([c, d], axis=0)

    return e

is_within_circular_range(value, lb, ub)

Check if a value lies within the circular range [lb, ub].

Parameters:

Name Type Description Default
value float

The value to check.

required
lb float

The lower bound of the range.

required
ub float

The upper bound of the range.

required

Returns:

Type Description
bool

True if the value is within the circular range, False otherwise.

Source code in pycircstat2/utils.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def is_within_circular_range(value: float, lb: float, ub: float) -> bool:
    """
    Check if a value lies within the circular range [lb, ub].

    Parameters
    ----------
    value : float
        The value to check.
    lb : float
        The lower bound of the range.
    ub : float
        The upper bound of the range.

    Returns
    -------
    bool
        True if the value is within the circular range, False otherwise.
    """
    value = np.mod(value, 2 * np.pi)
    lb = np.mod(lb, 2 * np.pi)
    ub = np.mod(ub, 2 * np.pi)

    if lb <= ub:
        # Standard range
        return lb <= value <= ub
    else:
        # Wrapping range
        return value >= lb or value <= ub

rotate_data(alpha, angle, unit='radian')

Rotate a circular dataset by a given angle, supporting degrees, radians, and hours.

Parameters:

Name Type Description Default
alpha ndarray

Angles in the specified unit.

required
angle float

Rotation angle in the specified unit.

required
unit str

Unit of measurement ("degree", "radian", or "hour"). Default is "radian".

'radian'

Returns:

Name Type Description
rotated_alpha ndarray

Rotated angles, normalized within the unit's full cycle.

Source code in pycircstat2/utils.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def rotate_data(alpha: np.ndarray, angle: float, unit: str = "radian") -> np.ndarray:
    """
    Rotate a circular dataset by a given angle, supporting degrees, radians, and hours.

    Parameters
    ----------
    alpha : np.ndarray
        Angles in the specified unit.
    angle : float
        Rotation angle in the specified unit.
    unit : str, optional
        Unit of measurement ("degree", "radian", or "hour"). Default is "radian".

    Returns
    -------
    rotated_alpha : np.ndarray
        Rotated angles, normalized within the unit's full cycle.
    """
    if unit == "degree":
        n_intervals = 360
    elif unit == "radian":
        n_intervals = 2 * np.pi
    elif unit == "hour":
        n_intervals = 24
    else:
        raise ValueError("Unit must be 'degree', 'radian', or 'hour'.")

    # Convert to radians for consistent computation
    alpha_rad = data2rad(alpha, k=n_intervals)
    angle_rad = data2rad(angle, k=n_intervals)

    # Perform rotation and normalize in radians
    rotated_alpha_rad = angmod(alpha_rad + angle_rad, bounds=[0, 2 * np.pi])

    # Convert back to the original unit
    rotated_alpha = rad2data(rotated_alpha_rad, k=n_intervals)

    return rotated_alpha