--- title: "Introduction to causalreg" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to causalreg} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Overview The `causalreg` package implements causal discovery via **Pearson risk invariance** for generalized linear models (GLMs) and generalized additive models (GAMs). Given a response and a set of candidate covariates, it identifies which covariates are causal parents of the response within a structural causal model. The key idea: if a GLM or GAM is correctly specified with respect to the true causal parents, the **Pearson risk** (expected squared Pearson residuals) is equal to 1. The package performs this test from observational data across subsets of covariates to find the causal model. ## Causal Poisson regression Consider a simple setting where `X1` causes `Y` (Poisson), and `X2` is a downstream effect of `Y`: ```{r poisson-glm} library(causalreg) n <- 1000 set.seed(123) X1 <- rnorm(n) Y <- rpois(n, exp(X1)) X2 <- log(Y + 1) + rnorm(n, 0, 0.3) data <- data.frame(X1, X2, Y) ``` ### Exhaustive search with chi-square test For Poisson models, the chi-square test is fast and appropriate: ```{r poisson-all} result <- cglm(Y ~ X1 + X2, "poisson", data, pval = "chi-square", search = "all") result$model.opt ``` The method correctly identifies `Y ~ X1` as the causal model. We can inspect the full results: ```{r poisson-all-details} # All models considered unlist(result$models) # Their p-values (acceptance means no evidence to reject Pearson risk = 1) result$pv # Their BIC values result$bic ``` Only the model `Y ~ X1` has a p-value above the significance threshold (alpha = 0.05 by default), and it is selected. ### Stepwise search For larger numbers of covariates, exhaustive search over all 2^p - 1 subsets becomes impractical. The stepwise search provides a faster alternative: ```{r poisson-step} result_step <- cglm(Y ~ X1 + X2, "poisson", data, pval = "chi-square", search = "stepwise") result_step$model.opt # Models visited during the search unlist(result_step$models) ``` The stepwise search starts with an intercept-only model, then greedily adds the variable that maximizes the Pearson risk p-value. After the forward stepwise phase, it performs backward elimination based on BIC as the selected models may contain non-predictive variables, i.e., variables with zero causal effect. ## Causal logistic regression For binomial models, the chi-square approximation does not hold, so we use the bootstrap test: ```{r binomial-glm} n <- 2000 set.seed(123) X1 <- rnorm(n) Y <- rbinom(n, 1, exp(X1) / (1 + exp(X1))) flip <- rbinom(n, 1, 0.1) X2 <- (1 - flip) * Y + rnorm(n, 0, 0.3) data <- data.frame(X1, X2, Y) set.seed(1) result <- cglm(Y ~ X1 + X2, "binomial", data, pval = "bootstrap", search = "all") result$model.opt ``` The bootstrap test correctly identifies the causal model. ## Causal GAMs for nonlinear relationships When the relationship between covariates and response is nonlinear, use `cgam()` with smooth terms: ```{r poisson-gam} n <- 1000 set.seed(123) X1 <- rnorm(n) Y <- rpois(n, exp(sin(X1))) X2 <- log(Y + 1) + rnorm(n, 0, 0.5) data <- data.frame(X1, X2, Y) result <- cgam(Y ~ s(X1) + s(X2), "poisson", data, pval = "chi-square", search = "all") result$model.opt ``` The `cgam()` function uses `mgcv::gam()` internally and handles smooth terms (`s()`) in the formula. The Pearson risk invariance principle applies equally to GAMs. ## Larger example: 5 covariates In a more realistic setting with 5 candidate covariates and a binomial response: ```{r five-cov, eval = FALSE} set.seed(12) n <- 3000 X1 <- rnorm(n) X2 <- rnorm(n, X1, 0.5) X3 <- rnorm(n, 0, 1) X4 <- rnorm(n, X2, 0.5) Y <- rbinom(n, 1, exp(0.8 * X2 - 0.9 * X3) / (1 + exp(0.8 * X2 - 0.9 * X3))) flip <- rbinom(n, 1, 0.1) X5 <- (1 - flip) * Y + flip * (1 - Y) + rnorm(n, 0, 0.3) dat <- data.frame(X1, X2, X3, X4, X5, Y) # Exhaustive search (evaluates all 2^5 - 1 = 31 subsets) set.seed(1) mod_all <- cglm(Y ~ X1 + X2 + X3 + X4 + X5, "binomial", dat, pval = "bootstrap", search = "all") mod_all$model.opt #> [1] "Y ~ X2 + X3" # Stepwise search (much faster) set.seed(1) mod_step <- cglm(Y ~ X1 + X2 + X3 + X4 + X5, "binomial", dat, pval = "bootstrap", search = "stepwise") mod_step$model.opt #> [1] "Y ~ X2 + X3" ``` Both search strategies correctly identify `X2` and `X3` as the causal parents of `Y`. ## Summary of recommendations | Scenario | `family` | `pval` | `search` | |----------|----------|--------|----------| | Count data, few covariates (p < 15) | `"poisson"` | `"chi-square"` | `"all"` | | Count data, many covariates | `"poisson"` | `"chi-square"` | `"stepwise"` | | Binary data, few covariates | `"binomial"` | `"bootstrap"` | `"all"` | | Binary data, many covariates | `"binomial"` | `"bootstrap"` | `"stepwise"` | | Nonlinear effects | Use `cgam()` with `s()` terms | Same as above | Same as above | ## Reference Polinelli, A., V. Vinciotti and E.C. Wit. (2026). "Causal generalized linear models via Pearson risk invariance." *Journal of Causal Inference*.