The silence test: does the state after a skipped prompt differ?
Source:R/tests_informative.R
silence_test.RdRegresses the state at prompt \(t+1\) on the state at \(t-1\) and the
response indicator at \(t\), within person, using all triples in which
prompts \(t-1\) and \(t+1\) were answered (prompt \(t\) may or may
not have been). Under every combination of the recoverable motifs
(M0, M1, M3, M4) that does not contain both M1 and M3, \(R_t\) is
conditionally independent of \(X_{t+1}\) given \(X_{t-1}\) and the
person (Yu, 2026), so the population coefficient of \(R_t\) is zero
when the conditional mean of \(X_{t+1}\) given \(X_{t-1}\) is linear
(exactly under M0, M3 and M4; under M1 the selection on \(X_t\) through
\(R_{t+1}\) makes it slightly nonlinear, and poly = 2 adds squared
lagged states to absorb the curvature). Under self-censoring (M2), latent
context (M5) or reactivity (M6) the coefficient is not zero. When burden
(M3) is combined with a state-dependent motif (M1 + M3, M2 + M3), the
conditioning on \(R_{t+1} = 1\) opens a collider at \(R_{t+1}\), whose
parents are \(R_t\) (burden) and either \(X_t\) (under M1, and
\(X_t\) drives \(X_{t+1}\)) or \(X_{t+1}\) itself (under M2): under
M1 + M3 the test rejects in large samples although the kernel is
recoverable (with a coefficient of the opposite sign to the self-censoring
signature), and under M2 + M3 the collider works against the self-censoring
signal and the test loses power. The sign of the
coefficient on the self-censoring variable is the sign of the tilt: a
negative value means that the states hidden by skips were higher than the
states that were reported.
Usage
silence_test(
data,
vars,
id = "id",
time = "time",
day = NULL,
R = "R",
se = NULL,
B = 500,
poly = 1L,
seed = NULL
)Arguments
- data
Long data frame with one row per scheduled prompt.
- vars
Names of the state columns.
- id, time
Names of the person and prompt-index columns.
- day
Optional name of a day column; pairs are formed only within a day (the overnight gap is not a lag-1 transition).
- R
Name of the response-indicator column (0/1, no
NA). If the default name is not among the columns ofdata, a prompt counts as answered when allvarsare non-missing; any other name must exist.- se
"cluster"(cluster-robust by person with \(t(G-1)\) and \(F(q, G-1)\) reference distributions; the default when there are at least 10 persons) or"dayblock"(block bootstrap that resamples person-day blocks with replacement; the default with fewer than 10 persons; requiresday).- B
Number of bootstrap resamples for
se = "dayblock".- poly
Degree of the polynomial in the lagged states (1 = linear).
- seed
Optional seed for the bootstrap (the caller's random-number state is restored).
Value
An object of class c("silence_test", "data.frame"): a data
frame with one row per state variable and the columns variable,
coef_R (coefficient of \(R_t\)), se, z (the ratio
of the coefficient to its standard error, referred to \(t(G - 1)\) under
se = "cluster") and p, and the attributes joint (a named vector with the Wald
statistic over all variables, its degrees of freedom and its p value),
n_triples, n_skipped (triples with a skipped middle
prompt) and se (the method used). It has a print method.
References
Cameron, A. C., & Miller, D. L. (2015). A practitioner's guide to cluster-robust inference. Journal of Human Resources, 50, 317-372. doi:10.3368/jhr.50.2.317
Yu, H.-T. (2026). What skipped prompts hide: Detecting, diagnosing, and correcting informative nonresponse in ecological momentary assessment. Manuscript under review.
See also
sensor_gap_test, fatigue_check,
recoverability (which reports whether the null is expected
under a declared graph)
Examples
sim <- simulate_ema(N = 40, n_prompts = 30, motifs = "M2", delta = -1, seed = 1)
st <- silence_test(sim$data, sim$vars)
st
#> Silence test: X_{t+1} ~ X_{t-1} + R_t (within person), 660 triples, 97 with a skipped middle prompt
#> variable coef_R se z p
#> NegA -0.546 0.106 -5.16 8.1e-06
#> PosA 0.089 0.109 0.82 4.2e-01
#> Stress -0.391 0.101 -3.86 4.3e-04
#> Fatigue -0.133 0.130 -1.02 3.1e-01
#> Joint Wald test: statistic = 34.77 on 4 df, p = 4.39e-05 [SE: cluster; t / F with G - 1 df reference]
#> Interpretation: coefficients near zero are expected under the recoverable motifs; a negative coefficient on a variable means the states hidden by skips were higher.
attr(st, "joint")
#> chisq df p
#> 3.476809e+01 4.000000e+00 4.389214e-05
sim0 <- simulate_ema(N = 40, n_prompts = 30, motifs = "M1", seed = 1)
silence_test(sim0$data, sim0$vars)
#> Silence test: X_{t+1} ~ X_{t-1} + R_t (within person), 637 triples, 129 with a skipped middle prompt
#> variable coef_R se z p
#> NegA -0.296 0.127 -2.34 0.025
#> PosA -0.054 0.101 -0.53 0.600
#> Stress -0.147 0.102 -1.44 0.160
#> Fatigue -0.197 0.105 -1.88 0.068
#> Joint Wald test: statistic = 8.87 on 4 df, p = 0.0847 [SE: cluster; t / F with G - 1 df reference]
#> Interpretation: coefficients near zero are expected under the recoverable motifs; a negative coefficient on a variable means the states hidden by skips were higher.
# quadratic terms in the lagged states, and a day structure
simd <- simulate_ema(N = 40, n_prompts = 30, motifs = "M2", delta = -1, days = 5, seed = 1)
silence_test(simd$data, simd$vars, day = "day", poly = 2)
#> Silence test: X_{t+1} ~ X_{t-1} + R_t (within person), 417 triples, 61 with a skipped middle prompt
#> variable coef_R se z p
#> NegA -0.482 0.120 -4.01 0.00028
#> PosA 0.007 0.158 0.04 0.96000
#> Stress -0.438 0.114 -3.84 0.00045
#> Fatigue -0.085 0.157 -0.54 0.59000
#> Joint Wald test: statistic = 26.01 on 4 df, p = 0.000435 [SE: cluster; t / F with G - 1 df reference]
#> Interpretation: coefficients near zero are expected under the recoverable motifs; a negative coefficient on a variable means the states hidden by skips were higher.