Multimodal Process IRT: Responses, Time, Gaze, and Missingness

knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(eyeprocess)

Measurement channels, not feature dumping

The 0.7 architecture treats response-process observations as explicit measurement channels. A process variable is not automatically useful merely because it predicts an outcome. It should have a declared role, latent target, family, provenance, and validation programme.

spec <- irt_model_spec(
  id = "accuracy_time_gaze",
  latent = c("ability", "speed", "engagement"),
  channels = list(
    response = irt_response_channel("2pl"),
    rt       = irt_rt_channel("lognormal"),
    gaze     = irt_count_channel("negative_binomial")
  ),
  status = "experimental"
)
spec

Other channels include nominal choices, survival/event time, compositional AOI measurements, process sequences, functional trajectories, and bounded continuous process measures.

irt_continuous_channel("censored_normal", value = "evidence_dwell_proportion")
irt_sequence_channel("scanpath", family = "hmm")

Registry

list_irt_models()

Models can be registered and later promoted only after their validation evidence passes an explicit gate.

register_irt_model(spec)
validate_irt_model("accuracy_time_gaze", validation_data)
promote_irt_model("accuracy_time_gaze", evidence = evidence_object)

Response + response time + gaze

fit_joint_gaze_rt_irt() supports two roles:

fit <- fit_joint_gaze_rt_irt(
  data = trials,
  response = "correct",
  rt = "rt_ms",
  gaze = "fixation_count",
  person = "person_id",
  item = "item_id",
  gaze_family = "negative_binomial",
  engine = "brms"
)
plot(fit)

The function does not claim that a convenient reference engine is identical to the published three-way Bayesian model. That distinction is kept in the fit metadata.

Graded responses

The same idea extends to ordinal/graded outcomes:

fit_joint_graded_rt_process_irt(
  data = trials,
  response = "rating",
  rt = "rt_ms",
  process = "fixation_count",
  person = "person_id",
  item = "item_id",
  engine = "brms"
)

This is experimental until parameter recovery and external validation are completed.

Nominal distractors + option gaze

Binary correct/incorrect scoring discards which alternative was selected. A nominal process model can retain both the selected option and visual consideration of each option.

fit <- fit_nominal_gaze_irt(
  data = option_trials,
  response_option = "choice",
  option_gaze = c("dwell_A", "dwell_B", "dwell_C", "dwell_D"),
  item = "item_id",
  person = "person_id"
)

option_process_information(fit)
distractor_process_map(fit)
audit_distractor_attention(fit)
plot(fit)

Interpretation should stay process-based: an option attracted or retained more visual processing. This does not establish why.

Missingness as a process

missing <- classify_item_missingness(
  trials,
  response = "response",
  reached = "reached",
  inspected = "inspected_response_region",
  started = "started_response"
)

audit <- fit_omission_survival_irt(
  data = missing,
  response = "correct",
  response_time = "rt",
  omission_time = "elapsed",
  reached = "reached",
  person = "person_id",
  item = "item_id"
)
plot(audit)

The classification separates not reached, reached but not inspected, inspected omission, and started-but-unanswered cases instead of converting them all to NA.

Device and algorithm facets

facet_fit <- fit_manyfacet_process_irt(
  data = trials,
  response = "correct",
  process = "fixation_count",
  person = "person_id",
  item = "item_id",
  device = "device",
  session = "session",
  algorithm = "fixation_algorithm"
)

facet_effects(facet_fit)
audit_process_measurement_invariance(facet_fit)
plot(facet_fit)

A complementary generalizability_process_study() decomposes variance before a full measurement model is attempted.

Bounded gaze measures

AOI proportions and similar process quantities often have real mass at 0 and 1. The conditional censored-normal calibration helper respects those bounds rather than silently applying ordinary Gaussian regression.

cn <- fit_censored_normal_process_irt(
  response_matrix = aoi_proportion_matrix,
  theta = calibration_theta,
  lower = 0,
  upper = 1
)
predict(cn, theta = seq(-2, 2, length.out = 9))

This is conditional calibration given supplied theta; it is not labelled as the full marginal EM estimator from the 2026 CNRM paper.



Try the eyeprocess package in your browser

Any scripts or data that you put into this service are public.

eyeprocess documentation built on Sept. 28, 2026, 5:08 p.m.