---
title: "Non-probability samples"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Non-probability samples}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(weightflow)
```

Opt-in panels, volunteer surveys and river samples have no design weights: the
inclusion probabilities are unknown and, worse, related to what we measure. A
weightflow recipe treats such a sample the same way it treats a probability
sample -- an explicit, auditable sequence of adjustments with recipe-aware
variances -- but the first steps change: instead of expanding design weights, we
*estimate* participation and adjust for it against a probability **reference**.

## A biased volunteer sample

We take a volunteer sample from the bundled `population` in which men over-
participate, so the sample mean of `income` is biased. A small probability survey
serves as the reference, carrying the design weights of a real reference survey.

```{r frame}
set.seed(1)
N   <- nrow(population)
p   <- plogis(-2 + 0.9 * (population$sex == "M") - 0.02 * (population$age - 45))
vol <- population[runif(N) < p, c("region", "sex", "age", "income")]  # volunteers
ref <- population[sample(N, 1000), c("region", "sex", "age")]         # reference
ref$d <- N / 1000                                                     # its design weights

truth <- mean(population$income)
c(truth = truth, naive_volunteer_mean = mean(vol$income))
```

## Declaring a non-probability sample

Start the recipe with `base_weights = NULL` and `nonprob = TRUE`: every unit
begins with a base weight of 1, and the object records that the sample is
non-probability (the report declares it and adds the methodological caveat).

```{r spec}
ref_sample <- reference_sample(ref, weights = "d")
```

## Pseudo-weighting

`step_pseudoweight()` fits a participation model that separates the volunteers
from the reference and assigns each volunteer the inverse-propensity
pseudo-weight `(1 - p) / p` (Elliott and Valliant 2017), so the weighted
volunteers reproduce the population. The two samples are stacked internally -- no
manual pooling.

```{r pseudoweight, warning = FALSE, message = FALSE}
spec <- weighting_spec(vol, base_weights = NULL, nonprob = TRUE) |>
  step_pseudoweight(reference = ref_sample, formula = ~ region + sex + age,
                    engine = "logit")
fit <- prep(spec)

w <- fit$final_weight
c(pseudoweighted_mean = weighted.mean(vol$income, w),
  sum_of_weights = sum(w), N = N)     # the (1-p)/p pseudo-weights sum to N
```

The pseudo-weighted mean is closer to the truth than the naive volunteer mean,
and the weights sum to the population size (a `1/p` mistake would overshoot).
Flexible learners (`engine = "tree" / "forest" / "boost"`, optionally
cross-fitted) are available for the participation model, exactly as for
response-propensity models.

## Doubly robust: add calibration

Pseudo-weighting is consistent if the participation model is right; calibration
is consistent if a linear outcome model is right. Doing **both** is doubly
robust -- correct if either holds. Calibrate the pseudo-weighted sample to the
reference:

```{r dr, eval = FALSE}
weighting_spec(vol, base_weights = NULL, nonprob = TRUE) |>
  step_pseudoweight(reference = ref_sample, formula = ~ region + sex + age,
                    engine = "logit") |>
  step_calibrate(method = "raking", formula = ~ region + sex,
                 population = ref_sample)
```

## How large is the sample, really?

A big opt-in sample can carry a small *effective* one. `data_defect()` brings the
data-defect view of Meng (2018): the effective size is governed by the
correlation between the outcome and participation, not by the raw count. Because
that correlation is not observable from the sample alone, the effective size is
reported across a grid of plausible values -- an **ignorance range**, not a
single number.

```{r datadefect}
dd <- data_defect(fit)
dd
```

## Variance

The recipe-aware bootstrap re-runs the whole non-probability recipe -- the
participation model included -- on each replicate, so the standard error carries
the variability of the pseudo-weighting, not just the spread of the final
weights.

```{r boot, warning = FALSE, message = FALSE}
boot <- bootstrap_weights(spec, replicates = 200, seed = 1, progress = FALSE)
boot_mean(boot, "income")
```

## Nonignorable selection

Everything above assumes participation is ignorable given the covariates. When it
may depend on the outcome itself, `step_nr_sensitivity()` reports how the estimate
moves across a single sensitivity parameter (the proxy pattern-mixture model of
Andridge and Little 2011), producing an *ignorance interval* to read next to the
sampling confidence interval. See `?step_nr_sensitivity`.

## References

Elliott and Valliant (2017), *Statistical Science*; Meng (2018), *Annals of
Applied Statistics*; Andridge and Little (2011), *JSSAM*.
