pyriodic package#
circular#
- class pyriodic.circular.Circular(data: ndarray, labels: list | ndarray | None = None, unit: str = 'radians', full_range: tuple | None = None, metadata: DataFrame | None = None)#
Bases:
objectA class for representing and working with circular data (e.g., angles, time-of-day, phase).
This class supports circular statistics and visualisation of data that wraps around a fixed range, such as angles (in radians or degrees), phases, hours, or other cyclic measurements. It provides basic unit validation, conversion between degrees and radians, and visualisation tools.
- Parameters:
data (array-like) – A sequence of numerical values representing circular measurements. Values should be in the range appropriate to the specified unit.
labels (array-like of str or int, optional) – Optional condition labels or identifiers corresponding to each data point.
unit (str, optional) – The unit of the input data. Must be one of {“radians”, “degrees”}. Default is “radians”. Assumes radian range to be from 0 to 2pi.
full_range (tuple, optional)
- data#
The circular data as a NumPy array.
- Type:
ndarray
- labels#
Optional labels (e.g., condition tags) for each data point.
- Type:
ndarray or list
- unit#
Unit of measurement, either “radians” or “degrees”.
- Type:
str
- classmethod from_multiple(circular_objects, labels=None)#
- mean(group_by_label: bool = False)#
Compute the circular mean of the data.
- Parameters:
group_by_label (bool, default=False) – If True, computes the mean for each label group separately.
- Returns:
mean – The circular mean angle, either as a single value or a dictionary of means by label.
- Return type:
float or dict
- median(group_by_label: bool = False)#
Compute the circular median of the data.
- Parameters:
group_by_label (bool, default=False) – If True, computes the median for each label group separately.
- Returns:
median – The circular median angle, either as a single value or a dictionary of medians by label.
- Return type:
float or dict
- plot(ax=None, histogram=None, group_by_labels=False, density=False)#
- r(group_by_label: bool = False)#
Compute mean resultant length of the data, which is a measure of the data’s concentration.
- Parameters:
group_by_label (bool, default=False) – If True, computes the median for each label group separately.
- Returns:
r – The mean resultant length, either as a single value or a dictionary of values by label.
- Return type:
float or dict
datasets#
- pyriodic.datasets.fetch_dataset(destination: Path, url: str = None, fname: str = None, force_update: bool = False)#
Download and prepare a dataset using pooch.
- Parameters:
name (str) – Dataset identifier.
destination (Path) – Where the dataset should live (already resolved by caller).
force_update (bool) – If True, re-download even if cached.
density#
- pyriodic.density.vonmises_kde(data, kappa, min_x=0, max_x=6.283185307179586, n_bins=100)#
Vectorized von Mises kernel density estimate for circular data.
- Parameters:
data (np.ndarray) – Circular data in radians.
kappa (float) – Concentration parameter (higher = narrower peaks).
min_x (float) – Minimum value of the evaluation grid (default 0).
max_x (float) – Maximum value of the evaluation grid (default 2*pi).
n_bins (int) – Number of points in the evaluation grid.
- Returns:
x (np.ndarray) – Evaluation points.
kde (np.ndarray) – Estimated density at each point.
desc#
- pyriodic.desc.angular_deviation(rad: ndarray)#
- pyriodic.desc.circular_mean(rad: ndarray, wrap_to_2pi: bool = True)#
Compute the mean angle from a list of angles in radians.
Parameters:#
- radnp.ndarray
Array of angles in radians.
- wrap_to_2pibool, default=True
If True, wraps result to range [0, 2π]. If False, result is in [-π, π].
Returns:#
- meanfloat
The circular mean angle.
- pyriodic.desc.circular_median(rad: ndarray)#
Compute the median angle from a list of angles in radians.
Parameters:#
- radnp.ndarray
Array of angles in radians.
Returns:#
median : float
The circular median angle.
- pyriodic.desc.circular_r(rad: ndarray)#
Compute the length of the mean resultant vector $r$, a measure of circular concentration.
\[r = \sqrt{\bar{C}^2 + \bar{S}^2}\]
- pyriodic.desc.circular_standard_deviation(rad: ndarray)#
permutation#
- pyriodic.permutation.ecdf(data: ndarray, points: ndarray) ndarray#
Compute the Empirical Cumulative Distribution Function (ECDF) of the given data at specified points.
- Parameters:
data (np.ndarray) – The input sample data.
points (np.ndarray) – The points at which to evaluate the ECDF.
Returns
np.ndarray (-)
- pyriodic.permutation.paired_permutation_test(sample1: ndarray, sample2: ndarray, alternative: Literal['greater', 'less', 'two-sided'] = 'greater', n_permutations: int = 1000, rng: Generator | None = None, verbose: bool = True, return_null_distribution: bool = False) tuple#
Performs a permutation test on paired samples (e.g., group-level stat comparison).
- pyriodic.permutation.peak_to_peak_modulation(phases: ndarray, values: ndarray, n_bins: int = 8, min_bin_count: int = 10) float#
Computes the peak-to-peak modulation (max - min) of a continuous variable binned by circular phase.
- Parameters:
phases (np.ndarray) – 1D array of phase values (in radians, from 0 to 2π).
values (np.ndarray) – 1D array of continuous values (e.g., reaction times), same length as phases.
n_bins (int, default=8) – Number of phase bins to divide the cycle into.
min_bin_count (int, default=10) – Minimum number of samples required in each bin. If any bin has fewer, an error is raised.
- Returns:
modulation – Difference between the max and min average value across phase bins.
- Return type:
float
- pyriodic.permutation.permutation_test_against_surrogate(observed: ndarray, surrogate_samples: ndarray, n_permutations: int = 1000, stat_fun: Callable = <function watson_u2>, perm_stat_fun: Callable = <function mannwhitneyu>, alternative: Literal['greater', 'less', 'two-sided']='greater', rng: Generator | None = None, n_bins: int = 30, verbose: bool = True, return_obs_and_surr_stats=False, return_perm_stats=False) tuple#
- Parameters:
observed (np.ndarray) – The observed circular phase angles (in radians).
surrogate_samples (np.ndarray) – A 2D array of surrogate samples, where each row is a surrogate sample of circular phase angles (in radians).
n_permutations (int, default=1000) – The number of permutations to perform for the permutation test.
stat_fun (Callable, default=watson_u2) – A function that computes a test statistic between two samples of circular data.
perm_stat_fun (Callable, default=mannwhitneyu) – A function that computes a test statistic between two groups of test statistics (e.g., Mann-Whitney U test).
alternative ({"greater", "less", "two-sided"}, default="greater") – The alternative hypothesis to test.
rng (np.random.Generator, optional) – A NumPy random number generator for reproducibility. If None, a new generator is created.
n_bins (int, default=30) – The number of bins to use when computing the test statistic (if applicable).
verbose (bool, default=True) – If True, prints the observed statistic and p-value.
return_obs_and_surr_stats (bool, default=False) – If True, returns the observed-vs-surrogate and surrogate-vs-surrogate test statistics
return_perm_stats (bool, default=False) – If True, returns the permutation test statistics.
- pyriodic.permutation.permutation_test_between_samples(sample1: ndarray, sample2: ndarray, stat_fun: Callable | None = None, alternative: Literal['greater', 'less', 'two-sided'] = 'greater', n_permutations: int = 1000, rng: Generator | None = None, n_bins: int = 30, verbose: bool = True, return_null_distribution: bool = False) tuple#
NOTE: Documentation to be added
- pyriodic.permutation.permutation_test_phase_modulation(phases: ndarray, values: ndarray, stat_fun: Callable | None = None, n_null: int = 1000, n_bins: int = 8, min_bin_count: int = 10, alternative: Literal['greater', 'less', 'two-sided'] = 'greater', rng: Generator | None = None, verbose: bool = True, return_null_distribution: bool = False) tuple#
Perform a permutation test to assess whether a linear variable is modulated by circular phase.
- Parameters:
phases (np.ndarray) – 1D array of circular phase values (in radians).
values (np.ndarray) – 1D array of scalar values (e.g., RTs) to test for phase-dependent modulation.
stat_fun (Callable, optional) – Function that computes a modulation index given (phases, values). Defaults to peak-to-peak modulation across phase bins.
n_null (int, default=1000) – Number of permutations to build the null distribution.
n_bins (int, default=8) – Number of bins to use when binning phase (passed to stat_fun).
min_bin_count (int, default=10) – Minimum number of samples per bin (passed to stat_fun).
alternative ({"greater", "less", "two-sided"}, default="greater") – The alternative hypothesis to test.
rng (np.random.Generator, optional) – Random number generator for reproducibility.
verbose (bool, default=True) – If True, prints the observed statistic and p-value.
return_null_distribution (bool, default=False) – If True, also returns the null distribution of test statistics.
- Returns:
obs_stat (float) – The observed modulation index.
p_val (float) – P-value of the observed statistic under the null distribution.
null_dist (np.ndarray, optional) – The null distribution (if return_null_distribution is True).
- pyriodic.permutation.permutation_test_within_units(data1: list[~numpy.ndarray], data2: list[~numpy.ndarray], stat_fun: ~typing.Callable[[~numpy.ndarray, ~numpy.ndarray], float] = <function <lambda>>, n_permutations: int = 5000, rng: ~numpy.random._generator.Generator | None = None, alternative: ~typing.Literal['greater', 'less', 'two-sided'] = 'two-sided', verbose: bool = True, return_null_distribution: bool = False) tuple#
Performs a paired permutation test, where data at index 1 in data1 is assumed to be paired with data at index 1 in data2.
This is useful when each unit (e.g., participant, sensor, session) has paired data across two conditions, but the number of observations per condition may vary. Label permutations are performed within each unit to preserve intra-unit structure.
- Parameters:
data1 (list of np.ndarray) – List of arrays, where each array contains data for a unit (e.g., participant).
data2 (list of np.ndarray) – List of arrays, where each array contains data for a unit (e.g., participant). The indices for participants (or some other pairing) must match data1.
stat_fun (Callable, default = lambda x, y: np.mean(x - y)) – Function that computes a scalar test statistic for each pair of data arrays.
n_permutations (int, default = 1000) – Number of permutations to generate the null distribution.
rng (np.random.Generator, optional) – NumPy random number generator for reproducibility.
alternative ({"greater", "less", "two-sided"}, default = "two-sided") – The alternative hypothesis to test.
verbose (bool, default = True) – If True, print the observed statistic and p-value.
return_null_distribution (bool, default = False) – If True, return the full null distribution in the output.
- Returns:
observed_stat (float) – The observed group-level statistic (mean of unit-level stats).
p_value (float) – P-value under the null distribution.
null_distribution (np.ndarray, optional) – Returned if return_null_distribution is True.
- pyriodic.permutation.watson_u2(sample1: ndarray, sample2: ndarray, n_bins: int = 30) float#
Compute the Watson U² statistic between two circular samples.
This is a non-parametric test statistic used to compare two samples of circular data, based on the squared differences between their ECDFs.
Parameters: - sample1 (np.ndarray): First sample of circular data (values should be in [0, 2π]). - sample2 (np.ndarray): Second sample of circular data (values should be in [0, 2π]). - n_bins (int): Number of points to evaluate the ECDFs on the circle.
Returns: - float: The Watson U² statistic.
phase_events#
- pyriodic.phase_events.compute_segment_outliers(segments, threshold=3, sampling_rate=None)#
Identify segment durations that deviate > threshold * SD from the mean. Optionally normalize by sampling_rate.
- pyriodic.phase_events.create_phase_events(phase_ts: ndarray, events: ndarray, event_labels: ndarray | None = None, unit: str = 'radians', first_samp: int = 0, rejection_method: str | None = None, rejection_criterion: float | None = None, bad_segments: ndarray | None = None, metadata: DataFrame | None = None) Circular | tuple[Circular, list[int]]#
Create Circular object from a phase angle time series and event markers, optionally applying rejection criteria.
- Parameters:
phase_ts (np.ndarray) – 1D array of phase values (in radians), typically spanning multiple 0–2π cycles.
events (np.ndarray) – 1D array of sample indices at which to extract phase values.
event_labels (np.ndarray, optional) – Labels corresponding to each event, for grouping in the Circular object. Must be the same length as events.
unit (str) – Unit of the input phase time series and the desired unit for output Circular objects. Must be either ‘radians’ or ‘degrees’. Internally, all computations are performed in radians.
first_samp (int) – Offset to subtract from each event index to align with the phase time series (useful if phase_ts is a segment of a longer recording).
rejection_method (str, optional) – Method to reject events based on surrounding phase dynamics. Supported values: ‘segment_duration_sd’ — excludes events during rising/falling phase segments whose durations deviate more than rejection_criterion standard deviations from the mean.
rejection_criterion (float, optional) – Threshold (in standard deviation units) for identifying outlier segments. Only used if rejection_method=’segment_duration_sd’. Default is 3.
bad_segments (Optional[np.ndarray]) – For ignoring events that fall within specific segments of the phase time series. Shape should be (n_segments, 2), where each row is a (start, stop) index pair in samples.
return_rejected (bool) – If True, also return a list of rejected event indices.
- Returns:
Circular – A Circular object containing the phase values at the specified events.
(optional) list[int] – If return_rejected is True, returns a second value: the list of event indices that were rejected.
- pyriodic.phase_events.get_outlier_sample_indices(segments, outlier_indices)#
Convert outlier segments to a flat set of sample indices for fast lookup.
- pyriodic.phase_events.get_phase_segments(phase_ts: ndarray)#
Identify rising (0→π) and falling (π→2π) phase segments in a wrapped phase signal.
- Parameters:
phase_ts (np.ndarray) – 1D array of phase values (in radians), assumed to evolve continuously over time.
- Returns:
rising_segments (list of (start_idx, stop_idx)) – List of index tuples for rising phase segments (0 to π).
falling_segments (list of (start_idx, stop_idx)) – List of index tuples for falling phase segments (π to 2π).
preproc#
- class pyriodic.preproc.RawSignal(data, fs, info=None, bad_segments: None | ndarray = None)#
Bases:
object- annotate_bad_segments(segments: ndarray, unit: Literal['s', 'sample'])#
Annotates bad segments in the signal. :param segments: Array of segments to annotate. Dimensions should be (n, 2) where n is the number of segments. Each segment is defined by a start and end point. :type segments: np.ndarray :param unit: Unit of the segments, either “s” for seconds or “sample” for samples. :type unit: str
- convert_seconds_to_samples(array)#
- copy()#
- filter_bandpass(low=0.1, high=1.0)#
- property history#
- interpolate_missing()#
- phase_hilbert()#
Extract instantaneous phase angle using the Hilbert Transform.
- Parameters:
ts (np.ndarray) – 1D time series
- Returns:
phase angles in radians (0 to 2π)
- Return type:
np.ndarray
- phase_onepoint(peak_finder=None, distance=1, prominence=0.01)#
Extract phase by linearly interpolating from 0 to 2π between detected peaks.
- Parameters:
peak_finder (callable, optional) – Optional custom function of the form
func(ts, **kwargs) -> np.ndarrayreturning peak indices.distance (int) – Minimum distance between peaks, passed to the peak detection algorithm in seconds.
prominence (float) – Prominence threshold for peak detection.
- Returns:
phase (np.ndarray) – The interpolated phase values (ranging from 0 to 2π).
peaks (np.ndarray) – Indices of the detected peaks used for phase interpolation.
- phase_threepoint(peak_finder: Callable[[...], ndarray] | None = None, distance: int = 1, prominence: float | int = 0.01, percentile: float | int = 50, descent_window: int = 5)#
Extract phase using a three-point method: Peak → descending slope → flat region → ascending slope → next peak.
- Parameters:
peak_finder (callable, optional) – Optional custom function of the form
func(ts, **kwargs) -> np.ndarrayreturning peak indices.distance (int) – Minimum distance between peaks, used if no custom peak finder is provided.
prominence (float) – Prominence threshold for peak detection.
percentile (float) – Percentile of the absolute gradient below which a region is considered flat.
descent_window (int) – Number of samples used to confirm descending and ascending slopes before and after flat regions.
- Returns:
phase (np.ndarray) – Phase array in radians (0 to 2π).
peaks (np.ndarray) – Indices of detected peaks.
troughs (list of tuple) – Each tuple contains the (start_index, end_index) of a flat region between peaks.
- phase_twopoint(peak_finder=None, distance=1, prominence=0.01)#
Extract phase using linear interpolation between peaks and troughs.
- Parameters:
peak_finder (callable, optional) – Optional custom function of the form
func(ts, **kwargs) -> np.ndarrayreturning peak indices.distance (int) – Minimum distance in seconds between peaks, passed to the peak detection algorithm.
prominence (float) – Prominence threshold for peak detection.
- Returns:
phase (np.ndarray) – The extracted phase values (in radians).
peaks (np.ndarray) – Indices of detected peaks.
troughs (np.ndarray) – Indices of detected troughs.
- plot(ax=None, start=0, duration=20)#
Plots the time series data.
- Parameters:
ax – Matplotlib axis to plot on. If None, creates a new figure.
start – Start time in seconds.
duration – Duration in seconds to plot.
- remove_outliers(threshold=2.5, linear_interpolation=True)#
Removes outliers …. threshold + linear interpolation
- resample(sfreq=typing.Union[int, float])#
- Parameters:
sfreq – New sampling rate
- smoothing(window_size: float = 50.0)#
Smooth the timeseries using a moving average.
The smoothing window is centered on each sample. At the edges, the window is padded with NaNs so that only the available samples contribute to the average.
- Parameters:
window_size (float) – Size of the moving window in milliseconds.
- zscore()#
Normalises the signal in-place to mean 0 and unit variance.
regression#
utils#
- pyriodic.utils.calculate_p_value(observed_stat: float, null_distribution: ndarray, alternative: Literal['greater', 'less', 'two-sided'] = 'greater') float#
Compute the p-value for a given observed statistic against a null distribution.
- Parameters:
observed_stat (float) – The observed test statistic.
null_distribution (np.ndarray) – Array of test statistics computed under the null hypothesis.
alternative ({'greater', 'less', 'two-sided'}, default='greater') – Defines the alternative hypothesis: - ‘greater’: test if observed > null - ‘less’: test if observed < null - ‘two-sided’: test if observed is different from null (absolute deviation)
- Returns:
p_value – The computed p-value.
- Return type:
float
- pyriodic.utils.dat2rad(data: ndarray | float | int, full_range: tuple[float, float] = (0, 360))#
- pyriodic.utils.rad2dat(rad: ndarray | float | int, full_range: tuple[float, float] = (0, 360))#
- pyriodic.utils.rad2deg(rad: ndarray | float | int) ndarray | float | int#
Convert radians to degrees.
viz#
- class pyriodic.viz.CircPlot(circ: Circular, title: str | None = None, group_by_labels: bool = True, colours=None, fig_size=(6, 6), dpi=300, ax=None, radius_lim=None, angles: list | None = None, labels: list | None = None)#
Bases:
object- add_arrows(angles: ndarray, lengths: ndarray | None = None, labels: ndarray | None = None, **kwargs)#
Plot arrows at specified angles and lengths.
- Parameters:
angles (np.ndarray) – 1D array of angles (in radians) where arrows should be drawn.
lengths (np.ndarray, optional) – Length of each arrow. If None, all arrows will have unit length.
labels (np.ndarray, optional) – Optional label array (same length as angles) for grouping and coloring.
kwargs (dict) – Additional keyword arguments passed to ax.arrow. Common examples: width, color, alpha.
- add_circular_mean(grouped: bool | None = None, **kwargs)#
Plot mean resultant vector(s) as arrows.
- Parameters:
grouped (bool, optional) – If None, uses self.group_by_labels. If True, plots a vector per label. If False, plots a single mean vector for all data.
kwargs (dict) – Additional keyword arguments passed to ax.arrow.
- add_connected_points(y=None, label=None, connect_last_to_first=True, **kwargs)#
Plot connected data points on the polar axis.
- Parameters:
y (float, optional) – Y-coordinate for the points. If None, defaults to 0.5 for all points.
- add_density(kappa=20, n_bins=500, grouped: bool | None = None, **kwargs)#
Add a circular density estimate using Von Mises KDE.
- Parameters:
kappa (float) – Concentration parameter of the Von Mises distribution (higher = sharper).
n_bins (int) – Number of angular bins to evaluate the density on.
grouped (bool, optional) – If None, uses self.group_by_labels. If True, plots a density curve for each label separately. If False, plots a single joint density curve for all data.
kwargs (dict) – Additional keyword arguments passed to ax.plot (e.g., linewidth, linestyle, alpha).
- add_histogram(data: ndarray | None = None, label: str | None = None, bins=36, alpha=0.2, scale_to_max=True, color: str = 'grey')#
Plot histogram as radial bars on the polar axis. If data is None, uses circular data in self.circs.
- Parameters:
data (np.ndarray, optional) – 1D array of angles (in radians) to compute histogram. If None, uses self.circ.data.
label (str, optional) – Label for the histogram bars.
bins (int) – Number of bins for the histogram (default 36).
alpha (float) – Transparency of the bars (default 0.2).
scale_to_max (bool) – If True, scales the bar heights to fit within the current radial limits. This ensures that the histogram bars do not exceed the maximum radius of the plot and makes it difficult to see other plotted elements. If False, the bar heights are raw counts.
- add_hline(y=0, **kwargs)#
Add a horizontal line across the polar plot.
- Parameters:
y (float) – Y-coordinate for the horizontal line (default 0).
kwargs (dict) – Additional keyword arguments passed to ax.axhline (e.g., color, linestyle, linewidth).
- add_legend(**kwargs)#
Add a legend to the plot.
- Parameters:
kwargs (dict) – Additional arguments passed to ax.legend().
- add_points(grouped: bool | None = None, y=None, **kwargs)#
Plot circular data points on the polar axis.
- Parameters:
grouped (bool, optional) – If None, uses the self.group_by_labels setting. If True, plots each label separately. If False, plots all data points together, ignoring labels.
y (float, optional) – Y-coordinate for the points. If None, defaults to 0.5 for all points.
kwargs (dict) – Additional keyword arguments passed to ax.scatter, such as s, alpha, or marker.
- add_polar_line(angles: ndarray, values: ndarray, errors: ndarray | None = None, color: str | None = 'orange', label: str | None = 'Values', alpha: float = 0.2, **kwargs)#
Plot a line with optional shaded error band on the polar axis.
- Parameters:
angles (np.ndarray) – 1D array of angles (in radians) for the x-axis.
values (np.ndarray) – 1D array of values to plot on the radial axis.
errors (np.ndarray, optional) – 1D array of error values (e.g., std or SEM) for shading.
color (str, optional) – Color for line and fill.
label (str, optional) – Label for the line.
alpha (float, optional) – Transparency of the error band.
kwargs (dict) – Additional keyword arguments for ax.plot.
- prepare_ax(radius_lim=None, angles=None, labels=None, title=None)#
Prepare and customize a polar plot axis (self.ax) with standard aesthetic settings.
Parameters:#
- radius_limtuple or None
Optional tuple (min, max) for setting radial (r-axis) limits.
- angleslist of float or None
Angles (in degrees) where labels should be placed on the theta axis. Default is [0, 90, 180, 270, 360].
- labelslist of str or None
Labels corresponding to angles. Default is [“0/2π”, “”, “π”, “”, “”].
- save(filename, **kwargs)#
Save the figure to a file
- show()#
- ticks_to_degrees(n_ticks=8)#
Convert the theta ticks from radians to degrees.
- pyriodic.viz.plot_phase_diagnostics(phase_angles: dict[str, ndarray], fs: int | float, data: ndarray | None = None, events: list | ndarray | None = None, event_labels: list | ndarray | None = None, peaks=None, troughs=None, flat_start_stop=None, window_duration: float = 20.0, title=None, start=0)#
- Parameters:
phase_angles (dict[str, np.ndarray]) – Dictionary of named phase angle signals (e.g., {“Three-point”: …, “Hilbert”: …}). Each should be 1D array of same length as data.
fs (float) – Sampling frequency (Hz).
data (np.ndarray, optional) – Raw or preprocessed time series signal (same length as phase arrays).
events (list[int] or np.ndarray, optional) – Sample indices of events to mark (e.g., stimulus or response times).
event_labels (list[str], optional) – Label for each event (same length as events). Used for color grouping.
peaks (list[int] or np.ndarray, optional) – Sample indices of identified peaks.
troughs (list[int] or np.ndarray, optional) – Sample indices of identified troughs.
flat_start_stop (list[tuple[int, int]] or list[int], optional) – Flat segments as start–stop index tuples or flat indices directly.
window_duration (float) – Duration (in seconds) of each visible window in interactive mode.
start (float) – Start time (in seconds) for the initial view of the plot.
- Returns:
fig (matplotlib.figure.Figure) – The created figure.
axes (list[matplotlib.axes.Axes]) – The axes used in the plot (one per row: signal + phase tracks).