Recurrence-threshold diagnostics¶
This example makes the radius-to-recurrence relationship visible before a recurrence threshold is interpreted.
Synthetic planar trajectory¶
import numpy as np
from eyetrajectoriespy import TrajectorySet
time = np.arange(240, dtype=float) * 0.01
x = np.sin(2 * np.pi * 1.2 * time)
y = 0.7 * np.cos(2 * np.pi * 0.8 * time + 0.3)
gaze = TrajectorySet(
time=time,
values=np.column_stack([x, y])[None, :, :],
curve_ids=("synthetic",),
dimension_names=("x", "y"),
time_unit="s",
coordinate_system="normalized",
)
1. Evaluate a declared radius grid¶
from eyetrajectoriespy import recurrence_radius_profile
profile = recurrence_radius_profile(
gaze,
curve="synthetic",
dimensions=("x", "y"),
radii=(0.02, 0.04, 0.06, 0.08, 0.12, 0.20),
metric="euclidean",
theiler_window=0.05,
theiler_window_units="seconds",
)
Inspect both cumulative recurrence and the pair-distance mass entering at each step:
print(
profile.table[
[
"previous_radius",
"radius",
"shell_pair_count",
"shell_pair_fraction",
"cumulative_recurrent_pairs",
"recurrence_rate",
]
]
)
The recurrence-rate column is the empirical pair-distance CDF at the declared radii.
2. Plot the curve¶
from eyetrajectoriespy import plot_recurrence_rate_curve
ax = plot_recurrence_rate_curve(profile)
A steep section means a small change in radius admits many additional state pairs. That is a sensitivity warning, not a prescription to avoid or prefer the region.
3. Confirm the base recurrence estimator¶
Suppose 0.08 was independently justified as the primary radius.
from eyetrajectoriespy import recurrence_matrix
recurrence = recurrence_matrix(
gaze,
curve="synthetic",
dimensions=("x", "y"),
radius=0.08,
metric="euclidean",
theiler_window=0.05,
theiler_window_units="seconds",
)
row = profile.table.loc[profile.table["radius"] == 0.08].iloc[0]
print(row["recurrence_rate"])
print(recurrence.achieved_recurrence_rate)
Those values use the same denominator and threshold convention and agree exactly.
4. Inspect profile coverage¶
print(profile.provenance["maximum_radius_coverage_fraction"])
print(profile.provenance["full_distance_distribution_captured"])
If the maximum radius covers only part of the empirical distance distribution, do not describe the shell table as a histogram of all pairwise distances.
5. Target-RR analysis remains explicit¶
If the scientific protocol instead controls recurrence density:
targeted = recurrence_matrix(
gaze,
curve="synthetic",
dimensions=("x", "y"),
target_recurrence_rate=0.05,
metric="euclidean",
theiler_window=0.05,
theiler_window_units="seconds",
)
print(targeted.target_recurrence_rate)
print(targeted.radius)
print(targeted.achieved_recurrence_rate)
Here the radius is solved to implement the target-RR policy. The resulting RR is controlled by design and should not be treated as an unconstrained outcome.
Reporting¶
from eyetrajectoriespy import recurrence_radius_profile_reporting_text
print(recurrence_radius_profile_reporting_text(profile))
The executable counterpart is examples/recurrence_threshold_diagnostics.py.