---
title: "9. Rolling networks"
output:
  rmarkdown::html_vignette:
    toc: true
bibliography: references.bib
link-citations: true
vignette: >
  %\VignetteIndexEntry{9. Rolling networks}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE,
  fig.width = 7, fig.height = 5.5
)
library(idiographic)
data(srl)
has_cograph <- requireNamespace("cograph", quietly = TRUE)
```

Rolling networks relax the one assumption every full-series model in this
package shares: that a single within-person process holds across the whole
observation window. A vector autoregression fitted to all of one person's
occasions returns one temporal and one contemporaneous network and asserts,
through its weak-stationarity assumption, that the same dynamics generated the
first week of the protocol and the last. Rolling estimation withdraws that
assertion. It slides a window of fixed length along the ordered series of a
single person and refits the estimator inside each window, so stationarity is
required only locally, within a window, and the sequence of window fits
describes how that person's own dynamics change over the protocol.

The estimand is correspondingly local and within-person. A temporal edge
`from -> to` in a given window states that this person's deviation on `from`
at occasion $t-1$ predicts their deviation on `to` at occasion $t$, for the
occasions inside that window, holding the other lagged variables constant. A
contemporaneous edge is a window-local partial correlation among the
within-occasion innovations — the associations that lag-one prediction inside
the window does not account for [@bringmann2013; @epskamp2018mlvar]. Nothing
in either estimand refers to other people: every window is estimated from one
individual's occasions, and differences between windows are within-person
variation in estimated dynamics, not differences between persons.

Two rolling estimators are provided. `fit_rolling_var()` refits the ordinary
least-squares VAR in each window and is the unregularized, time-varying
baseline: every window returns a dense temporal matrix and a dense
contemporaneous matrix. `fit_rolling_graphical_var()` refits the penalized
two-step estimator in each window — lasso-penalized lagged
regressions, a graphical lasso on their residuals, the penalty
selected per window by the extended Bayesian information criterion — so each
window carries its own sparsity decision. Both are
descriptive instruments: a sequence of window fits shows that local dynamics
differ, not why they differ. Rolling estimation is also not a remedy for short
series — each window contains fewer observations than the full series, so the
window size sets a bias-variance tradeoff in which smaller windows track
change more finely but estimate every network from less data. Because
adjacent windows share most of their occasions, their estimates are strongly
dependent, and window-size sensitivity should be reported whenever rolling
networks are offered as evidence of changing dynamics.

# Data and preprocessing

The rolling estimators expect the same long format as their full-series
counterparts: one row per person-occasion, an id column, and numeric
time-varying indicators ordered within person. The bundled `srl` data hold
self-regulated-learning indicators for 36 students measured over 156 occasions
each; this vignette fits a single student, Grace, on five indicators. Passing
`subject = "Grace"` selects her series inside the call, so the full `srl` data
remain intact. The stationarity screen precedes the fit.

```{r vars-audit}
vars <- c("efficacy", "value", "planning", "monitoring", "effort")
preprocess(srl, vars = vars, id = "name", subject = "Grace")
```

Grace's 156 ordered occasions yield 155 complete current/lagged pairs, and no
series trips a trend, high-autoregression, drift, unit-root, or zero-variance
flag. A clean global screen does not settle the question rolling estimation
asks: the whole-series diagnostics average over the protocol, and dynamics
that shift midway can leave every global flag silent. The window fits below
pose that local question directly.

# Fitting the model

`window_size` sets the number of occasions in each local fit, `step` sets how
far the window advances, and `keep_fits = TRUE` stores each fitted window
model so that `matrices()` and `plot()` can inspect a selected window
afterwards. With Grace's 156 occasions, a 50-occasion window advancing by 20
gives six windows, starting at occasions 1, 21, 41, 61, 81, and 101 and
ending at occasion 150; each window's networks are estimated from the 49
lagged pairs its 50 occasions provide.

```{r fit-rolling-var}
rolling_ols <- fit_rolling_var(
  srl, vars = vars, id = "name", subject = "Grace",
  window_size = 50, step = 20, scale = TRUE, keep_fits = TRUE
)
rolling_ols
```

The ordinary rolling fit returns six windows for Grace, each holding a dense
least-squares temporal matrix and a dense contemporaneous matrix — the
unregularized, time-varying baseline against which the sparse variant can be
read.

The graphical variant repeats the penalized estimation in every window.
Sparse selection contributes sampling variability of its own — the selected
edge set can change from window to window even when the underlying process
does not — so a larger window of 70 occasions advancing by 25 is used to
partially offset it, and a coarse penalty grid (`n_lambda = 8`) with
`gamma = 0`, the plain-BIC end of the criterion, keeps the per-window
selection inexpensive and less severe toward retained edges than the default
`gamma = 0.5`.

```{r fit-rolling-gvar}
rolling_gvar <- fit_rolling_graphical_var(
  srl, vars = vars, id = "name", subject = "Grace",
  window_size = 70, step = 25, n_lambda = 8, gamma = 0,
  keep_fits = TRUE
)
rolling_gvar
```

The graphical rolling fit returns four windows, starting at occasions 1, 26,
51, and 76, each estimated from the 69 lagged pairs its 70 occasions provide
and each carrying its own EBIC-selected sparsity decision.

# Reading the output

`as.data.frame()` flattens a rolling result to one row per window and edge,
with the window's start and end rows carried alongside each weight; the first
twelve rows cover the temporal edges of Grace's first window.

```{r rolling-table}
head(as.data.frame(rolling_ols), 12)
```

Within the first window, monitoring at occasion $t-1$ predicts lower efficacy
at $t$ (-0.272), planning predicts lower value (-0.265), and planning
predicts higher effort (0.277). These local coefficients exceed anything the
full-series least-squares fit reports for the same student — its largest
temporal coefficient is 0.160 in absolute value — which is the rolling
tradeoff stated numerically: a 50-occasion window can express transient local
dynamics that the full series averages away, and it estimates them with
correspondingly more noise.

```{r rolling-matrices}
matrices(rolling_ols, fit = 1)
```

`matrices()` with `fit = 1` returns the first window's coefficient matrices:
the lag-one temporal matrix, the residual covariance and precision of the
innovations, and the partial correlations derived from them. The window-one
contemporaneous layer is dominated by efficacy–monitoring (0.447) and
monitoring–effort (0.440), close to Grace's full-series contemporaneous
pattern: in this series the within-occasion structure is more stable than the
temporal coefficients.

```{r rolling-gvar-table}
edge_counts <- do.call(rbind, lapply(seq_along(rolling_gvar$fits), function(i) {
  data.frame(window = i, summary(rolling_gvar$fits[[i]]))
}))
edge_counts
edges(rolling_gvar$fits[[1]])
matrices(rolling_gvar, fit = 1)
```

All four sparse windows retain no temporal edges, matching the full-series
graphical fit for the same student. This is an executed selection result, not a
missing or disabled network; with these data, windows, and criterion, the
regularized temporal layer is empty. The first window's contemporaneous layer keeps
efficacy–monitoring (0.247), monitoring–effort (0.264), and a small
planning–effort edge (0.071) — the same three within-occasion partial
correlations the full-series graphical fit selects, here at window-local
magnitudes.

# Visualizing the network

Plotting a rolling fit with `fit = 1` draws the selected window's temporal
and contemporaneous panels side by side, with edge width scaled to absolute
weight and colour encoding sign.

```{r plot-rolling-var-all, eval=has_cograph}
plot(rolling_ols, fit = 1)
```

The first-window OLS plot is dense, as every unregularized window is: each of
the 25 possible temporal arrows and 10 possible contemporaneous edges carries
some estimate, and the eye is drawn by width rather than by presence.

```{r plot-rolling-var-temporal, eval=has_cograph}
plot(rolling_ols, fit = 1, layer = "temporal")
```

The temporal panel isolates the local lag-one structure — the
monitoring-to-efficacy and planning-to-effort effects read from the table
above.

```{r plot-rolling-var-contemporaneous, eval=has_cograph}
plot(rolling_ols, fit = 1, layer = "contemporaneous")
```

The contemporaneous panel carries the efficacy–monitoring–effort core that
also anchors Grace's full-series fits.

```{r plot-rolling-gvar, eval=has_cograph}
plot(rolling_gvar, fit = 1, layer = "contemporaneous")
```

The sparse window keeps the same core and sets the remaining within-occasion
associations to exact zeros. Redrawing this panel for each of the four
windows, by varying `fit`, is the graphical form of the question rolling
estimation answers: whether this person's local structure holds steady or
moves over the protocol.

# References
