---
title: "Getting started: the silentema workflow"
author: "Hsiu-Ting Yu"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{Getting started: the silentema workflow}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.5)
```

Experience-sampling participants skip prompts, and the reasons are rarely unrelated to the states being measured. `silentema` provides a graphical language for saying which mechanism is assumed (a dynamic missingness graph), a checker that says which quantities of a two-level VAR(1) remain estimable under that mechanism, two tests that ask the data whether skipped prompts were informative, and a sensitivity analysis for the one mechanism that cannot be repaired by estimation alone: self-censoring, where the current state itself causes the skip. This vignette walks through one complete analysis on simulated data. The other vignettes go deeper into each step: `vignette("dm-graphs")`, `vignette("testing-informativeness")`, `vignette("sensitivity-analysis")` and `vignette("simulation-and-design")`.

## 0. The data format

Every estimator takes a long data frame with **one row per scheduled prompt, answered or not**:

| Column | Content |
|---|---|
| `id` | person identifier |
| `time` | integer prompt index, increasing by one from one scheduled prompt to the next within a person |
| `R` | response indicator, 1 = answered (if absent, a prompt counts as answered when all state columns are non-missing) |
| state columns | the momentary states, `NA` at skipped prompts |
| `day` (optional) | day index; adjacent pairs are then formed within days only, so the overnight gap is not treated as a lag-1 transition |
| sensor, probe, context (optional) | an always-observed channel, a forced-response indicator, recorded contexts |

Rows for skipped prompts must be present (with `R = 0` and `NA` states): the tests and the response models need to know *that* a prompt was scheduled and skipped. Two rows whose `time` values differ by more than one are not adjacent. Real data usually need a reshaping step to reach this format; `simulate_ema()` produces it directly.

## 1. Simulate a study with self-censoring

```{r sim}
library(silentema)
sim <- simulate_ema(N = 60, n_prompts = 40, motifs = "M2", compliance = 0.70, delta = -1,
                    sensor_cor = 0.6, p_probe = 0.05, days = 5, seed = 1)
sim
head(sim$data)
```

Four momentary states (negative affect, positive affect, stress, fatigue) follow a two-level VAR(1) with person-specific means. Prompts are answered with a probit probability that decreases in the current level of negative affect (`delta = -1`), so the high-negative-affect moments are the ones that get skipped. A passive sensor correlated .6 with the within-person fluctuations of negative affect is observed at every prompt, and 5% of prompts are randomized probes that are always answered. Eight days of five prompts each.

## 2. Declare a dynamic missingness graph and check recoverability

```{r graph}
g <- dm_graph(motifs = "M2", sensor = TRUE, probe = TRUE)
g
plot(g)
recoverability(g)
```

The report says that the transition kernel (and therefore the temporal and contemporaneous networks) is not structurally recoverable under self-censoring, that the silence test and the sensor-gap test are expected to reject, and that the kernel can be recovered from probe prompts. Compare with a recoverable mechanism and with reactivity, under which the dynamics are recovered but the person mean must be read from the answered prompts rather than from the dynamics:

```{r graph2}
recoverability(dm_graph(c("M1", "M4")))
recoverability(dm_graph("M6"))
```

## 3. Ask the data whether silence was informative

```{r tests}
st <- silence_test(sim$data, sim$vars, day = "day")
st
sg <- sensor_gap_test(sim$data, sim$vars, sensor = "S", day = "day")
sg
fatigue_check(sim$data, day = "day")
```

The coefficient of `R_t` on negative affect in the silence test and the sensor gap are both negative: the states hidden by skipped prompts were higher than the reported ones. The positive coefficient on positive affect is the same tilt seen through the negative contemporaneous association between the two. The fatigue check reports the response persistence that burden (M3) would produce: here the pooled contrast is inflated by between-person differences in response rate (self-censoring acts on the raw state, so persons with a high typical level respond less), while the person-mean contrast is modest and arises from the autocorrelated states, as expected without burden.

## 4. Estimate, profile, calibrate

```{r fit}
fit0 <- fit_pairs(sim$data, sim$vars, day = "day")
fit0
confint(fit0)[1:4, ]
prof <- tilt_profile(sim$data, sim$vars, delta_grid = seq(-2, 0.5, by = 0.5), day = "day", probe = "Z")
prof
plot(prof, plausible = c(-1.5, -0.5))
```

The autoregressive coefficient of negative affect rises as the analysis allows for stronger self-censoring; at the generating value (-1) it is close to the population value of .40. Three designs calibrate the sensitivity value:

```{r calibrate}
cs <- calibrate_delta(prof, sim$data, method = "sensor", day = "day")
cs
plot(cs)
calibrate_delta(prof, sim$data, method = "probe", day = "day")
```

The sensor calibration is the most precise of the three designs, but it is not immune to sampling variability: in this sample its interval stops short of the generating value of -1, whereas the probe calibration (with only 5% probes) is much less precise and its interval is open on one side. Yu (2026) reports the coverage of these intervals over many replications; a single study should report all available calibrations and let the plausible interval cover their union.

The post-skip contrast needs no extra channel but relies fully on the functional form (it simulates from each fitted model; `n_sim` controls the number of simulated data sets per grid value, and `burden = "fit"` adds a burden term calibrated to the observed response persistence when M3 is declared together with M2):

```{r postskip}
calibrate_delta(prof, sim$data, method = "postskip", day = "day", n_sim = 5, seed = 2)
```

## 5. Report

```{r report}
break_even(prof, delta_max = 1.5)[1:4, ]
cat(missingness_declaration(g, silence = st, sensor_gap = sg, profile = prof, calibration = cs,
                            plausible = c(-1.5, -0.5)), sep = "\n")
```

`break_even()` returns, for every lagged coefficient, the sensitivity value at which its sign or its significance would change, and the range of estimates over the plausible interval (the identified set). `missingness_declaration()` writes the declaration that the accompanying article recommends for preregistrations and reports, filling the sections for which objects are supplied.

## 6. Full-information maximum likelihood under missing at random

For comparison, the likelihood that dynamic structural equation models maximize:

```{r fiml}
fit_fiml(sim$data, sim$vars)
```

Under self-censoring this estimator is biased in the same direction as the answered-pairs estimator, because both treat the mechanism as ignorable.

## Where to go next

* Which mechanism to declare, and what follows from it: `vignette("dm-graphs")`.
* How the tests work, their power and their failure modes: `vignette("testing-informativeness")`.
* Tilting, break-even values, calibration and reporting in depth: `vignette("sensitivity-analysis")`.
* Planning a study with the simulator: `vignette("simulation-and-design")`.
* The accompanying article (Yu, 2026) and its archived materials: https://osf.io/x6d2t/.
