--- title: "Getting started with dScoreTest" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with dScoreTest} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(dScoreTest) ``` ## What the test does `dScoreTest` implements a **debiased (Neyman-orthogonalized) score test**. Given a fitted null model, it asks: is there a direction in which the model's score is systematically non-zero? If so, the model is misspecified. The test is computed by **sample splitting**: 1. On a held-out *hunt* sample, a flexible auxiliary fit searches for a promising direction `h(x)` of misspecification. 2. On an independent *test* sample, the score along that direction is evaluated and standardized. The orthogonalization step absorbs the plug-in bias from estimating `h` on a finite sample, so the test statistic is asymptotically standard normal under the null --- without assuming a parametric form for the alternative. The p-value is one-sided (power lives in the right tail). ## Two entry points - `gof_test()` --- is a fitted model well-specified, against a nonparametric alternative? - `compare_models()` --- does a nested alternative capture signal the null model misses? (An `anova()`-style comparison.) Both dispatch on the fitted object, with methods for `lm`, `glm`, and `mgcv::gam`. Both return a `dScoreTest` object that supports `print()`, `summary()`, and `plot()`. ## A first example We use a small linear model so this vignette builds quickly. Take a truth that is linear in `x1` and `x2`: ```{r gof-wellspec} set.seed(3) n <- 300 dat <- data.frame(x1 = rnorm(n), x2 = rnorm(n)) dat$y <- 1 + dat$x1 + dat$x2 + rnorm(n) fit <- lm(y ~ x1 + x2, data = dat) gof_test(fit) ``` The p-value is large: the linear model is correctly specified, and the test does not reject. Now introduce a quadratic effect the linear model cannot capture: ```{r gof-misspec} dat$y2 <- 1 + dat$x1 + dat$x1^2 + dat$x2 + rnorm(n) fit.mis <- lm(y2 ~ x1 + x2, data = dat) gof_test(fit.mis) ``` The p-value is small: the test detects that `E[y2 | x1, x2]` is not linear in `x1`. ## Comparing nested models `compare_models()` tests a null model against a richer alternative that contains it. We add the quadratic term as an explicit column: ```{r compare} dat$x1sq <- dat$x1^2 fit.0 <- lm(y2 ~ x1 + x2, data = dat) # null (linear) fit.1 <- lm(y2 ~ x1 + x1sq + x2, data = dat) # alternative (superset) compare_models(fit.0, fit.1) ``` The alternative's quadratic term captures the signal, so the comparison rejects. (`compare_models()` refits the models from their model frames, so supply extra terms as plain columns --- e.g. `x1sq` above --- rather than as in-formula transformations like `poly(x1, 2)` or `I(x1^2)`.) ## Choosing the hunt The search for a direction of misspecification (the "hunt") has three styles, passed via `hunt.style`: - `"optimal"` (default) --- the asymptotically optimal direction. - `"wls"` --- a simpler weighted-least-squares hunt; can be less powerful. - `"vanilla"` --- a basic hunt; a fallback. ## Where to go next The default hunt for `gof_test()` uses a regression forest (`grf`), which makes it a genuinely nonparametric goodness-of-fit test. See the *Goodness of fit and model comparison for GAMs* article for worked examples with `mgcv::gam`, including diagnostics via `plot()`.