Irregular measurement and latent state tracking

Alex Litovchenko

2026-04-17

Important: This vignette does not claim that KFAS in tidyILD “fixes” or removes the challenges of irregular measurement. tidyILD helps you measure and report timing structure (intervals, gaps, spacing class). ild_kfas() fits a discrete-time state-space model along observation order; it is not a drop-in substitute for continuous-time latent models (e.g. ctsem-like workflows). High irregularity can trigger a guardrail; continuous-time solutions are a later tier.

Motivation

Intensive longitudinal data (EMA, diaries, wearable prompts) often have irregular observation times: gaps, bursts, and varying intervals between measurements. tidyILD makes that structure visible and actionable before you choose a model:

Those tools support mixed models (e.g. choosing AR1 vs CAR1 for residual correlation in ild_lme()) and transparent reporting of how timing was handled. KFAS in tidyILD adds another branch: latent state tracking for a single series—but the same spacing diagnostics still describe whether the timing pattern matches the assumptions of the state-space fit you use.

What ild_kfas() assumes today

The KFAS backend in tidyILD fits models on a discrete time index: observations are ordered after ild_prepare(), and the state evolves one step per row for that series. It does not implement a continuous-time Kalman filter with unequal physical intervals built into the transition matrix (that design space points elsewhere, e.g. specialized continuous-time software—see inst/dev/KFAS_V1_BACKEND.md).

So:

Use spacing outputs to document irregularity and to interpret results cautiously when intervals vary wildly: a local-level model on the observation index is not the same as a model that correctly weights by elapsed time in continuous time.

irregular_time and guardrails

ild_kfas(..., irregular_time = TRUE) suppresses the default warning when spacing looks highly irregular relative to a discrete-time local-level assumption. It does not turn the model into a continuous-time model; it acknowledges that you have read the spacing diagnostics and accept the discrete-time framing (or a deliberate approximation).

After fitting, ild_diagnose() may attach KFAS-specific guardrails, including GR_KFAS_HIGH_IRREGULARITY_FOR_DISCRETE_TIME when the design is irregular-ish but the fit remains discrete-time. That rule is a design-space bridge: it connects spacing classification to state-space workflow without claiming to fix misspecification.

Browse rule definitions with guardrail_registry() and inspect triggered rows in ild_diagnose(fit)$guardrails.

Workflow sketch

library(tidyILD)
set.seed(3)
d <- ild_simulate(n_id = 1, n_obs_per = 50, irregular = TRUE, seed = 11)
x <- ild_prepare(d, id = "id", time = "time", gap_threshold = 7200)
ild_summary(x)$summary
#> # A tibble: 1 × 7
#>    n_id n_obs time_min time_max prop_gap median_dt_sec iqr_dt_sec
#>   <int> <int>    <dbl>    <dbl>    <dbl>         <dbl>      <dbl>
#> 1     1    50        0  176801.        0         3561.       579.
ild_spacing_class(x)
#> [1] "regular-ish"

If KFAS is installed, you can fit with explicit acknowledgment of irregular spacing:

fit <- suppressWarnings(
  ild_kfas(
    x,
    outcome = "y",
    state_spec = "local_level",
    time_units = "seconds",
    irregular_time = TRUE
  )
)
diag <- ild_diagnose(fit)
# When spacing is irregular-ish and IQR/median interval ratio > 0.75, expect
# GR_KFAS_HIGH_IRREGULARITY_FOR_DISCRETE_TIME among triggered guardrails:
diag$guardrails[, c("rule_id", "message")]
#> # A tibble: 0 × 2
#> # ℹ 2 variables: rule_id <chr>, message <chr>

If KFAS is not installed, install the KFAS package to run the chunk above.

Where to go next