Baseline, gaze/PFE, and luminance sensitivity

Prespecify consequential analysis choices

The sensitivity layer records alternative analysis states without selecting the scenario that produces the largest effect.

sim <- simulate_pupil_timecourse(
  n_participants = 5,
  trials_per_participant = 4,
  sampling_frequency = 20,
  time_window = c(-0.6, 1.4),
  include_gaze = TRUE,
  include_luminance = TRUE,
  seed = 73
)
contract <- create_pupil_contract(
  outcome_col = "pupil_mm",
  participant_col = "participant_id",
  trial_col = "trial_id",
  condition_col = "condition",
  time_col = "event_time",
  pupil_unit = "millimetres",
  sampling_frequency = 20,
  blink_col = "blink",
  interpolation_col = "interpolated",
  gaze_x_col = "gaze_x",
  gaze_y_col = "gaze_y",
  luminance_col = "luminance",
  baseline_window = c(-0.6, 0),
  pfe_corrected = FALSE
)
prepared <- prepare_pupil_timecourse(sim$data, contract)

spec_for_sensitivity <- specify_pupil_timecourse_model(
  prepared,
  autocorrelation = "none",
  smooth_basis_dimension = 5
)
suite <- create_pupil_sensitivity_suite(
  spec_for_sensitivity,
  baseline_windows = list(c(-0.6, -0.1), c(-0.4, -0.1)),
  baseline_window_operation = "subtract",
  baseline_operations = c("none", "subtract"),
  interpolation_policy = c("retain", "exclude_flagged"),
  gaze_adjustment = c("none", "declared_covariates"),
  luminance_adjustment = c("none", "declared_covariate"),
  smooth_basis_dimensions = c(5, 7),
  autocorrelation = c("none", "ar1"),
  analysis_windows = list(c(0.2, 1.0))
)
pupil_sensitivity_table(suite)
#>    scenario_id                   axis               value
#> 1         S001        baseline_window           -0.6,-0.1
#> 2         S002        baseline_window           -0.4,-0.1
#> 3         S003     baseline_operation                none
#> 4         S004     baseline_operation            subtract
#> 5         S005   interpolation_policy              retain
#> 6         S006   interpolation_policy     exclude_flagged
#> 7         S007        gaze_adjustment                none
#> 8         S008        gaze_adjustment declared_covariates
#> 9         S009   luminance_adjustment                none
#> 10        S010   luminance_adjustment  declared_covariate
#> 11        S011 smooth_basis_dimension                   5
#> 12        S012 smooth_basis_dimension                   7
#> 13        S013        autocorrelation                none
#> 14        S014        autocorrelation                 ar1
#> 15        S015        analysis_window               0.2,1

Materialize, do not rank

scenario_id <- pupil_sensitivity_table(suite)$scenario_id[1]
scenario <- materialize_pupil_sensitivity_scenario(suite, scenario_id)
scenario$scenario_id
#> [1] "S001"
scenario$specification
#> <gp3bayes_pupil_model_specification>
#>   Family: Gaussian pupil time-course
#>   Formula: .pupil_model ~ .condition + s(.event_time, by = .condition, k = 5) +      (1 | .participant)
#>   Temporal structure: smooth
#>   Condition trajectory: TRUE
#>   Autocorrelation: none
#>   Outcome unit: millimetres
#>   Baseline: subtract
#>   Unrestricted formula: FALSE
#>   Fit performed: FALSE

Each scenario can be fitted and reduced to the same declared estimand. compare_pupil_sensitivity_estimands() then places those estimands side by side. It does not identify a winner.

PFE and luminance are handled as measurement-context variables. The 0.4 foundation can audit them and compare explicitly declared adjusted/unadjusted specifications, but it does not invent a universal PFE correction or a Bayesian Open-DPSM replacement.