--- title: "Regularized Relational Event Models" subtitle: "Variable selection and shrinkage for REMs" author: "" date: "" output: rmarkdown::html_document: toc: true toc_depth: 3 theme: spacelab highlight: pygments code_folding: show header-includes: - \usepackage{amsmath,amssymb} vignette: > %\VignetteEngine{knitr::rmarkdown} %\VignetteIndexEntry{Regularized Relational Event Models} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(dpi = 72, collapse = TRUE, comment = "#>") # Penalized fits need glmnet (frequentist) / shrinkem (Bayesian), both declared # in Suggests. If a backend is unavailable - e.g. a CRAN check run with # _R_CHECK_FORCE_SUGGESTS_=false - the relevant chunks render code-only instead # of erroring the vignette build. has_glmnet <- requireNamespace("glmnet", quietly = TRUE) has_shrinkem <- requireNamespace("shrinkem", quietly = TRUE) ``` --- When building relational event models, researchers often face a choice between including many network statistics to capture complex dynamics and keeping the model parsimonious to avoid overfitting. **Regularized estimation** provides a principled middle ground: fit all candidate statistics simultaneously and let the data determine which effects matter through penalization. `remverse` supports two flavours of penalized estimation, both requested through the `penalty` argument of `remstimate()` or the convenience wrapper `rempenalty()`: - **Frequentist** (`approach = "frequentist"`, the default): an elastic-net penalty via `glmnet`, with cross-validated selection of the penalty strength. - **Bayesian** (`approach = "Bayesian"`): shrinkage priors (default horseshoe) via `shrinkem`. This tends to produce parsimonious set of non-zero effects (via the posterior mode), and has been validated in REMs (Karimova et al., 2023, 2025). ```{r load} library(remverse) ``` --- # 1 Data We use the `randomREH3` event history and `info3` actor attributes provided with `remverse`. Statistics use exponential memory decay (half-life 2000; see the pipeline vignette on why full memory is often unrealistic), and to keep cross-validation quick they are computed on the last stretch of the sequence via `first`. ```{r data} data(randomREH3) data(info3) reh <- remify(edgelist = randomREH3, model = "tie", directed = TRUE) ``` --- # 2 The kitchen-sink model We start by specifying a rich model with many endogenous and exogenous effects — the kind of model a researcher might consider when exploring which network mechanisms drive the event process. ```{r kitchen-sink} effects_rich <- ~ inertia(scaling = "std") + reciprocity(scaling = "std") + indegreeSender(scaling = "std") + indegreeReceiver(scaling = "std") + outdegreeSender(scaling = "std") + outdegreeReceiver(scaling = "std") + totaldegreeDyad(scaling = "std") + isp(scaling = "std") + osp(scaling = "std") + itp(scaling = "std") + otp(scaling = "std") + send("age", attr_actors = info3, scaling = "std") + receive("age", attr_actors = info3, scaling = "std") + difference("age", attr_actors = info3, scaling = "std") stats_rich <- remstats(reh = reh, tie_effects = effects_rich, first = 500, memory = "decay", memory_value = 2000) dimnames(stats_rich)[[3]] ``` That gives us `r dim(stats_rich)[3]` statistics (including the baseline) — several of them (the degree and shared-partner families) strongly correlated with one another. This is exactly the setting where regularization helps. ## 2.1 Unregularized MLE ```{r mle-rich} fit_mle <- remstimate(reh = reh, stats = stats_rich) summary(fit_mle) ``` With many correlated parameters, some coefficients have large standard errors and the model is prone to overfitting — good in-sample fit, poor prediction on held-out events. Note here that the p-values and posterior probabilities of zero effects, in columns `Pr(>|z|)` and `Pr(=0)`, respectively, already indicate that there is not a lot of evidence for including many effects. In this multiple testing problem, however, the question is what statistically sound thresholds should be for including/excluding effects. This is achieved via statistical regularization techniques. --- # 3 Bayesian regularization The Bayesian route applies a shrinkage prior (horseshoe by default) that pulls small effects strongly toward zero while leaving genuinely large effects essentially unpenalized. It is fast and typically yields a clean, parsimonious solution. ```{r shrinkem, message=FALSE, eval=has_shrinkem} fit_bayes <- rempenalty(reh = reh, stats = stats_rich, approach = "Bayesian") summary(fit_bayes) round(coef(fit_bayes), 3) ``` The effects that survive shrinkage identify the network mechanisms the data actually support; the rest are shrunk to (near) zero. The baseline is never penalized. We recommend to use the posterior mode as point estimate `shrunk.mode` of the effects, resulting in 4 nonzero effects (besides the intercept). (Because the posterior mode is estimated numerically from the posterior density, some effects may be reported as marginally nonzero—for example, `itp = -0.001`—but can be treated as zero.). --- # 4 Frequentist regularization (elastic net) The frequentist route uses `glmnet`. Cross-validation selects the penalty strength $\lambda$, and the elastic-net mixing parameter `alpha` interpolates between lasso (`alpha = 1`, the default) and ridge (`alpha = 0`). ```{r glmnet-default, message=FALSE, eval=has_glmnet} fit_glmnet <- rempenalty(reh = reh, stats = stats_rich, approach = "frequentist") summary(fit_glmnet) coef(fit_glmnet) ``` Equivalently, `remstimate(reh, stats_rich, penalty = list(alpha = 1))` fits the same model. ## 4.1 Comparing coefficients ```{r compare-coefs, eval=has_glmnet} coefs_mle <- coef(fit_mle) coefs_bayes <- coef(fit_bayes) coefs_glmnet <- coef(fit_glmnet) shared <- intersect(intersect(names(coefs_mle), names(coefs_glmnet)), names(coefs_bayes)) comparison <- data.frame( statistic = shared, MLE = round(coefs_mle[shared], 3), BAYES = round(coefs_bayes[shared], 3), GLMNET = round(coefs_glmnet[shared], 3), row.names = NULL ) comparison ``` The `selected` column shows which effects survived regularization. Effects shrunk to zero are considered non-informative given the other effects in the model. ## 4.2 Alpha: lasso vs. ridge ```{r alpha, message=FALSE, eval=has_glmnet} # Pure lasso (default): maximum sparsity fit_lasso <- rempenalty(reh, stats_rich, approach = "frequentist", alpha = 1) # Pure ridge: shrinkage without variable selection fit_ridge <- rempenalty(reh, stats_rich, approach = "frequentist", alpha = 0) cat("Lasso non-zero:", sum(coef(fit_lasso) != 0), "of", length(coef(fit_lasso)), "\n") cat("Ridge non-zero:", sum(coef(fit_ridge) != 0), "of", length(coef(fit_ridge)), "\n") ``` The lasso selects a small subset of effects; ridge retains all but shrinks them. Elastic net (`0 < alpha < 1`) sits in between. ## 4.3 Lambda selection By default `rempenalty()` uses `lambda.1se` (the most parsimonious model within one standard error of the cross-validation minimum). Use `lambda_select = "min"` for the deviance-minimizing $\lambda$: ```{r lambda, message=FALSE, eval=has_glmnet} fit_min <- rempenalty(reh, stats_rich, approach = "frequentist", lambda_select = "min") cat("lambda.1se non-zero:", sum(coef(fit_glmnet) != 0), "\n") cat("lambda.min non-zero:", sum(coef(fit_min) != 0), "\n") ``` --- # 5 Diagnostics Recall diagnostics for a penalized model use its point estimates to compute linear predictors. As elsewhere in `remverse`, `diagnostics()` takes the fit plus the `reh` and `stats` it was built from. ```{r diagnostics, out.width="50%", dev=c("jpeg"), dev.args = list(bg = "white"), eval=has_glmnet} diag_mle <- diagnostics(fit_mle, reh, stats_rich) diag_bayes <- diagnostics(fit_bayes, reh, stats_rich) diag_glmnet <- diagnostics(fit_glmnet, reh, stats_rich) cat("MLE recall: ", round(diag_mle$recall$summary$mean_rel_rank, 3), "\n") cat("BAYES recall: ", round(diag_bayes$recall$summary$mean_rel_rank, 3), "\n") cat("GLMNET recall: ", round(diag_glmnet$recall$summary$mean_rel_rank, 3), "\n") plot(diag_mle) plot(diag_bayes) plot(diag_glmnet) ``` In-sample recall may be slightly lower for the regularized model — it trades a little in-sample fit for better generalization. In this case, the recall is equal approximately across models while the regularized solutions `coefs_bayes` and `coefs_glmnet` and much more parsimonious than the unregularized solution `coefs_mle`: ```{r show-comparison, eval=has_glmnet} comparison ``` --- # 6 Summary | Aspect | MLE | Penalized (frequentist / Bayesian) | |--------|-----|------------------------------------| | Estimates | Unpenalized | Shrunk toward zero | | Variable selection | No | Yes | | Multicollinearity | Problematic | Handled | | Overfitting risk | Higher with many effects | Lower | | Uncertainty | Standard errors | glmnet: point estimates; shrinkem: posterior | Penalized estimation integrates seamlessly into the `remverse` pipeline: the same `remify()` and `remstats()` calls are used, and only the `remstimate()` step changes (via `penalty = ...` or `rempenalty()`). A natural workflow is to use penalization to screen a large set of candidate effects, then re-estimate the selected model with MLE or a GLMM for full inference. --- #### References Karimova, D., Leenders, R., Meijerink-Bosman, M., & Mulder, J. (2023). Separating the wheat from the chaff: Bayesian regularization in dynamic social networks. *Social Networks*, 74, 139-155. https://doi.org/10.1016/j.socnet.2023.02.006 Karimova, D., van Erp, S., Leenders, R., & Mulder, J. (2025). Honey, I shrunk the irrelevant effects! Simple and flexible approximate Bayesian regularization. *Journal of Mathematical Psychology*, 126, 102925. https://doi.org/10.1016/j.jmp.2025.102925 Tibshirani, R. (1996). Regression shrinkage and selection via the lasso. *Journal of the Royal Statistical Society: Series B (Methodological)*, 58(1), 267-288. https://doi.org/10.1111/j.2517-6161.1996.tb02080.x