Measure pairwise coupling¶
Question¶
How far does one trace lead another, and is the lag stable?
See every package-generated example · Read the complete analysis pipeline
When to use¶
Use this for exactly two rhythmic traces on one shared time grid when stable phase lag and phase locking are the targets.
Example figure¶
This deterministic example is calculated by the coupling action and drawn by render_coupling_svg, the same renderer used for publication export. Empty or withheld elements are therefore visible exactly as they are in a real result.
import circadian_workbench as cw
cw.trace(hours, shell, name="SCN shell").coupling(core)
Required inputs and controls¶
The public function is the registered action below. settings= is accepted as a friendlier alias for config= by cw.call; the calculation stores the complete normalized config in provenance.
Function reference¶
cw.call("coupling", series_a, series_b, hours, config=None)
Arguments and parameters¶
| Name | Type | Required | Default | Units | Meaning |
|---|---|---|---|---|---|
series_a |
object | yes | — | recording units | The first trace: {'label': 'left lobe', 'values': [...]} on the shared hours grid. A [label, values] array works too. The label is reported back as the one that leads or lags, so make it the name you would use in a figure. |
series_b |
object | yes | — | recording units | The second trace, same shape as series_a and on the same hours grid. The reported lag is negative when series_a peaks first. |
hours |
array | yes | — | hours | The shared time grid the traces are sampled on, in hours from the start of the recording. |
config |
object | no | null |
- | Partial analysis config. Missing keys fall back to analysis.DEFAULT_CONFIG and out-of-range values are clamped silently — run describe_config for every key, its default and its allowed values, or normalize_config to see what a given config actually becomes. |
Every nested config key, default, allowed value, and purpose is listed in the complete configuration reference.
How it works¶
Band-limited Hilbert phases are subtracted pointwise. The circular mean gives lag, the resultant length gives phase-locking value, and sliding windows plus leave-one-cycle-out intervals test stability.
$$ \Delta\theta(t)=\theta_B(t)-\theta_A(t),\qquad PLV=\left|\frac{1}{N}\sum_t e^{i\Delta\theta(t)}\right| $$
Implementation: coupling.py::coupling.
Outputs and interpretation¶
The result reports lag and interval, leading trace, phase-locking value, drift and interval, windowed trajectories, overlap, cycles, locking verdict, and notes.
cw.call returns a Result: use .data for calculated values, .warnings for scientific qualifications, .provenance for version and input identity, .script for an equivalent replay script, and .files for saved outputs.
Limitations¶
Phase locking is association, not causal coupling. No p value is assigned to phase-locking value, and cross-correlation lag is deliberately not used because waveform and trend can mimic it.
Example¶
The figure above is a real package result from a seeded, redistributable synthetic dataset. Its audited project bundle retains figure_data.csv, a standalone plot.py, source hashes, an editable SVG, and a rendered preview.
Methods text¶
The two aligned traces were compared by their band-limited Hilbert phase difference; circular lag, phase-locking value, drift, sliding-window stability, and leave-one-cycle-out uncertainty were reported.
See also¶
Compare measurement channels · Measure population synchrony · Map phase across space · Analysis index · Gallery