Worked example: component selection and FPC uncertainty¶
This example keeps two questions separate:
- How many FPCs reconstruct unseen participant trajectories well?
- How stable is the shape of the retained FPCs under participant resampling?
The data are synthetic and reproducible.
Generate repeated-trial trajectories¶
from eyetrajectoriespy import (
bootstrap_fpca_component_envelopes,
cross_validate_fpca_reconstruction,
fpca_component_envelope_reporting_text,
fpca_cross_validation_reporting_text,
plot_fpca_component_envelope,
plot_fpca_cross_validation,
select_fpca_components_cv,
simulate_planar_trajectories,
summarise_fpca_cross_validation,
)
gaze = simulate_planar_trajectories(
n_participants=12,
trials_per_participant=3,
n_time=61,
random_state=2026,
)
Each participant contributes three trials, so ordinary curve-level CV would allow participant leakage.
Participant-grouped reconstruction CV¶
cv = cross_validate_fpca_reconstruction(
gaze,
candidate_components=(1, 2, 3, 4, 5),
n_splits=4,
scaling="dimension_sd",
cv_unit="group",
group_column="participant_id",
)
summary = summarise_fpca_cross_validation(cv)
print(summary)
Check that each participant appears in one test fold only:
assert (
cv.assignments.groupby("group")["fold"].nunique() == 1
).all()
Plot the reconstruction curve:
plot_fpca_cross_validation(cv)
Apply a pre-specified rule¶
minimum_k = select_fpca_components_cv(cv, rule="minimum")
one_se_k = select_fpca_components_cv(cv, rule="one_se")
print(minimum_k, one_se_k)
Do not switch between rules after inspecting which one gives the preferred substantive story. Pre-specify the rule or report both as sensitivity information.
Bootstrap FPC shapes at the participant level¶
For illustration the executable example uses 20 replicates. A real analysis should use enough replicates for the intended descriptive resolution.
envelopes = bootstrap_fpca_component_envelopes(
gaze,
n_bootstrap=500,
n_components=one_se_k,
scaling="dimension_sd",
resample_unit="participant",
participant_column="participant_id",
level=0.95,
random_state=2026,
)
Inspect the matched-component similarities:
import numpy as np
print(np.median(envelopes.similarities, axis=0))
Then inspect the shape envelope for each dimension:
plot_fpca_component_envelope(
envelopes,
component=0,
dimension="x",
)
Repeat for y(t) before interpreting a multivariate spatial component.
Interpretation¶
A useful pattern is:
- held-out reconstruction error reaches a plateau;
- the chosen FPCs show high matched-bootstrap similarity;
- their pointwise envelopes preserve the main qualitative trajectory pattern.
A warning pattern is:
- adding components continues to improve reconstruction only trivially;
- the extra component has low matching similarity;
- its envelope changes shape or sign locally across resamples.
That does not automatically mean the component is invalid. It means its interpretation should be cautious and sensitivity analyses should be reported.
Reporting text¶
print(fpca_cross_validation_reporting_text(cv, rule="one_se"))
print(fpca_component_envelope_reporting_text(envelopes))
These helpers are starting points. A manuscript should still state the candidate range, fold unit, scaling, bootstrap unit, replicate count, random seed, and whether substantive conclusions changed under nearby component counts.
Failure case: too many candidate components¶
cross_validate_fpca_reconstruction(
gaze,
candidate_components=(1, 2, 40),
n_splits=4,
)
This fails rather than estimating a rank-degenerate training-fold direction.
Next checks¶
After selecting the retained dimension, continue with: