--- title: "Irregular measurement and latent state tracking" author: "Alex Litovchenko" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Irregular measurement and latent state tracking} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4 ) has_kfas <- requireNamespace("KFAS", quietly = TRUE) ``` **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: - **`ild_prepare()`** computes per-row intervals (e.g. `.ild_dt`) and respects **`gap_threshold`** for episode boundaries. - **`ild_summary()`** and **`ild_spacing()`** / **`ild_spacing_class()`** summarize interval variability and classify spacing as **regular-ish** vs **irregular-ish** (see `vignette("ild-decomposition-and-spacing", package = "tidyILD")`). - **`ild_design_check()`** aggregates spacing and design summaries for reporting. 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: - **Irregular calendar timing** is still reflected in **ILD** metadata (intervals, spacing class, gaps). - The **state-space** fit itself is **discrete-time** along the **observation order** unless and until the backend gains explicit continuous-time support. 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 ```{r spacing_demo, eval = TRUE} 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 ild_spacing_class(x) ``` If **KFAS** is installed, you can fit with explicit acknowledgment of irregular spacing: ```{r kfas_irregular, eval = has_kfas} 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")] ``` If **KFAS** is not installed, install the **KFAS** package to run the chunk above. ## Where to go next - `vignette("kfas-state-space-modeling", package = "tidyILD")` — state-space concepts and **filtered vs smoothed** states. - `vignette("kfas-choosing-backend", package = "tidyILD")` — when to use **lme/nlme**, **brms**, or **KFAS**.