---
title: "Association between mixed outcome pairs"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Association between mixed outcome pairs}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
if (!"package:drmTMB" %in% search()) {
  library(drmTMB)
}
```

<style>
h1.title {
  hyphens: none;
  overflow-wrap: normal;
  word-break: normal;
}
</style>

Use this page when you have two outcomes measured on the same individual, site,
or sampling occasion, but the outcomes need different models. For example, you
might record whether an individual bred (`0` or `1`) and how many offspring it
produced (a count). The question is: after accounting for predictors such as
habitat, do rows that do better than expected for one outcome also tend to do
better than expected for the other?

The one-call tool on this page returns that association and its numerical
diagnostics. For every admitted pair route it also provides two-stage Godambe
standard errors and Wald confidence intervals for the association-link
coefficients `alpha` when fit-specific diagnostics pass. This interval surface
is beta and deliberately scoped; it is not a general bivariate-family
interface. If
both outcomes are Gaussian and you need a jointly fitted model, use
`biv_gaussian()` and its residual correlation `rho12` instead.

Behind the scenes, the tool fits each outcome model first, holds those fitted
margins fixed, and estimates a latent-normal association called `eta`. You do
not need to understand that machinery before deciding whether the question and
data requirements below match your study.

## The question in an ecological example

Suppose each individual has a breeding outcome (`0` or `1`) and an offspring
count. Habitat may explain both outcomes. The question is not whether the raw
zeroes and counts have a large Pearson correlation. It is:

> After habitat has been accounted for in each outcome, do individuals that are
> more likely than expected to breed also tend to have more offspring than
> expected?

The answer is expressed on an unobserved common scale. A positive `eta` means
that the same rows tend to be unexpectedly high for both fitted outcomes on that
unobserved Gaussian scale. It does not mean that `eta` is an odds ratio, a logit
coefficient, an observed-scale correlation, or native residual `rho12`.

## One call, two stages

`biv_associate()` is a convenience wrapper. It makes the workflow one R call,
but it is still a two-stage estimator: it fits the two margins, freezes their
estimated parameters, and estimates only `eta` in stage 2. Changing the stage-2
association never refits the mean or scale parameters from either margin.

Start with the callable workflow; the diagram and equations below explain what
that call does. Create one data frame containing both responses and every
predictor, then remove incomplete rows once. A binary response must contain
exactly one literal `0` or `1` per row.

```{r one-call-example}
set.seed(20260801)
n <- 160L
habitat_score <- seq(-1.5, 1.5, length.out = n)
shared_tendency <- rnorm(n)

paired_data <- data.frame(
  habitat_score = habitat_score,
  bred = rbinom(
    n,
    size = 1,
    prob = plogis(-0.3 + 0.25 * habitat_score + 0.7 * shared_tendency)
  ),
  offspring = rnbinom(
    n,
    mu = exp(0.7 + 0.2 * habitat_score + 0.6 * shared_tendency),
    size = 2
  )
)

assoc <- biv_associate(
  bf(mu = bred ~ habitat_score),
  bf(mu = offspring ~ habitat_score, sigma = ~ 1),
  family = list(binomial(), nbinom2()),
  data = paired_data,
  association = ~ 1
)

association(assoc)
alpha_se <- sqrt(diag(vcov(assoc)))
alpha_se
suppressWarnings(confint(assoc))
suppressWarnings(confint(assoc, type = "eta"))
```

The first uncertainty request prints the experimental interval warning once for
this lower-information example. The later calls suppress the duplicate warning
so the rendered page keeps the coefficient and `eta` intervals readable.

```{r staged-diagram, echo = FALSE, fig.width = 8, fig.height = 3.1, fig.alt = "Flow diagram showing one complete paired data set splitting into a binary margin and a count margin in stage 1, then joining in stage 2 where both margins are frozen and eta is estimated.", fig.cap = "The frozen-margin workflow. The two response models are fitted first on the same complete rows. Their fitted probabilities or distributions are held fixed while stage 2 estimates a latent-normal association, eta. For every admitted route, the public Godambe covariance propagates uncertainty from both fitted margins into alpha-scale Wald intervals when fit-specific diagnostics pass."}
old_par <- par(mar = c(0, 0, 1, 0))
plot.new()
plot.window(xlim = c(0, 12), ylim = c(0, 4))
boxes <- list(
  c(0.3, 1.3, 2.7, 2.7), c(3.5, 2.2, 6.2, 3.5),
  c(3.5, 0.5, 6.2, 1.8), c(8.2, 1.3, 11.6, 2.7)
)
cols <- c("#E7F3F5", "#F2E8C9", "#F2E8C9", "#DCEAD7")
for (i in seq_along(boxes)) {
  rect(boxes[[i]][1], boxes[[i]][2], boxes[[i]][3], boxes[[i]][4],
       col = cols[i], border = "#112638", lwd = 1.2)
}
text(1.5, 2.35, "One complete\npaired data set", cex = 0.9)
text(4.85, 3.05, "Stage 1\nBinary margin\nP(breeding = 1)", cex = 0.84)
text(4.85, 1.15, "Stage 1\nCount margin\nE(offspring)", cex = 0.84)
text(9.9, 2.35, "Stage 2\nFreeze both margins\nEstimate eta (or eta_i)", cex = 0.88)
arrows(2.7, 2.35, 3.45, 2.85, length = 0.08, lwd = 1.2)
arrows(2.7, 1.65, 3.45, 1.15, length = 0.08, lwd = 1.2)
arrows(6.2, 2.85, 8.15, 2.45, length = 0.08, lwd = 1.2)
arrows(6.2, 1.15, 8.15, 2.05, length = 0.08, lwd = 1.2)
par(old_par)
```

In symbols, stage 1 produces two fitted marginal parameter sets,
\(\widehat\theta_1\) and \(\widehat\theta_2\). Stage 2 estimates

\[
\widehat\beta_A = \arg\max_{\beta_A} \sum_i
\log f_{\eta_i}(y_{1i}, y_{2i} \mid \widehat\theta_1, \widehat\theta_2),
\qquad a_i=X_{A,i}\beta_A,\quad \eta_i = \tanh(a_i).
\]

For the usual constant association, \(X_A=1\), so every row has the same
`eta`. The beta Bernoulli x ordinary-NB2 slope route uses
\(X_{A,i}=(1,x_i)\). In computation, `drmTMB` keeps `eta` infinitesimally
inside -1 and 1, because exact endpoints make the latent-normal probability
calculation unstable. This is a plug-in association criterion, not a joint
maximum-likelihood fit. The stage-2 Hessian alone therefore cannot give an
ordinary valid standard error because it treats stage-1 uncertainty as fixed.
Every admitted pair route instead uses the stacked stage-1 and stage-2
estimating equations to form a Godambe covariance for its `alpha`
coefficients. This establishes interval feasibility. The Bernoulli x
ordinary-NB2 intercept route additionally has retained coverage calibration.

Writing the full staged parameter as
\(q=(\theta_B^\top,\theta_N^\top,\alpha)^\top\) and its per-row stacked score as
\(U_i(q)\), drmTMB computes

\[
H=-\frac{1}{n}\sum_i\frac{\partial U_i}{\partial q^\top},\qquad
J=\frac{1}{n}\sum_i U_iU_i^\top,\qquad
\widehat{\operatorname{Var}}(\widehat q)=\frac{1}{n}H^{-1}JH^{-\top}.
\]

`vcov(assoc)` returns the `alpha` block of this matrix and `confint(assoc)`
forms Wald intervals from that block. Thus the equations, R method, and
reported target all use the same unbounded association-link scale.

For a design row \(x_i^\top\), the bounded association is
\(\eta_i=0.999999\tanh(x_i^\top\alpha)\). `predict()` obtains its standard error
from the delta method and obtains its confidence limits by transforming the
link-scale Wald endpoints. The transformed interval therefore remains inside
the valid eta range.

## What a binary outcome contributes

A binary observation has no ordinary numeric residual. If the breeding margin
predicts a probability \(p_i = 0.80\), we observe only whether breeding happened,
not how far above or below its predicted tendency the individual lay.

The association calculation uses an auxiliary latent variable \(U_{Bi}\):

\[
U_{Bi} \sim N(0, 1), \qquad
B_i = 1\{U_{Bi} > \Phi^{-1}(1 - p_i)\}.
\]

This threshold gives \(P(B_i = 1) = p_i\). The latent variable is not an
individual measurement and should not be reported as an individual binary
residual. For a Gaussian outcome, the corresponding latent quantity is its
standardized residual \((Y_i - \mu_i)/\sigma_i\). `eta` describes the
correlation between these latent quantities after the margins are frozen.

## Reviewed pair classes

A binary response in a reviewed pair cannot be a proportion, a
`cbind(successes, failures)` response, or a binomial count with multiple trials.

The current reviewed classes are Gaussian x literal-Bernoulli, Gaussian x
ordinary-NB2, literal-Bernoulli x literal-Bernoulli, literal-Bernoulli x
ordinary-NB2, and ordinary-NB2 x ordinary-NB2. The last two show why
"cross-family" is only a historical label for this page: the same
frozen-margin machinery also has two reviewed same-family discrete classes.

`association(assoc)` returns `eta` unless the numerical diagnostic is
`boundary_unresolved`; a `near_boundary` status remains flagged. For the
example, a positive value says that a greater latent tendency to breed is
associated with a greater latent tendency for offspring after habitat and season
have been accounted for. It is not a raw-data correlation.

## Standard error and confidence interval

For every admitted pair class, `vcov(assoc)` returns a named covariance matrix
for the unbounded association-link coefficients `alpha` and `confint(assoc)`
returns the corresponding alpha-scale Wald intervals when fit-specific
diagnostics pass. These methods propagate fitted-margin uncertainty through a
two-stage Godambe sandwich; they do not use the conditional stage-2 Hessian.

Keep the reported scale visible. The coefficient interval and the derived eta
interval answer related but different questions:

```{r association-interval-example, eval = FALSE}
association(assoc)       # bounded latent-normal eta point estimate
sqrt(diag(vcov(assoc)))  # Godambe SE for alpha
confint(assoc)           # Wald interval for alpha
confint(assoc, type = "eta") # transformed interval for constant eta
```

All admitted routes are interval-feasible. Those without route-specific
coverage calibration warn that their intervals are experimental. The
intercept-only Bernoulli x ordinary-NB2 route is inference-ready with caveats.
In its retained high-information
campaign (`n = 480` or `960`), all 16 cells passed the predeclared bias,
availability, SE-calibration, and 95% coverage gates; cell coverage ranged from
0.935 to 0.957. The earlier lower-information campaign (`n = 120` or `240`)
failed five primary coverage cells because some intervals were unavailable.
Accordingly, lower-information fits are not blocked: a numerically valid
covariance is returned with a warning, while an unstable or boundary result
returns an informative error rather than a placeholder interval.

That coverage evidence does not transfer automatically to association slopes,
other family pairs, random effects, incomplete pairs, weights, offsets, or
REML. The eta transformation inherits the underlying alpha method's tier; it
does not create a new coverage claim. Association slopes and other family
pairs remain interval-feasible, while the remaining features stay outside the
current interval surface.

## When the estimate or interval is withheld

For pairs with two discrete outcomes, the calculation evaluates a bivariate
normal probability region that corresponds to the observed outcome pair. Some
extreme tail regions can be numerically unresolved. In that case `drmTMB`
withholds `eta` and its interval rather than clipping a probability or reporting
a repaired estimate.

```{r unresolved-example, eval = FALSE}
if (identical(assoc$status, "boundary_unresolved")) {
  assoc$diagnostics
  # Report that no association estimate was returned. Do not clip or repair it.
}
```

Inspect the diagnostics and report that no association estimate was available.
Use [Errors, warnings, and convergence](convergence.html) for the next checks;
do not convert an unresolved tail calculation into a scientific conclusion.

## Can the association itself be modelled?

Usually start with `association = ~ 1`: it estimates one common `eta` after the
predictors in the two margins have been accounted for. In the beta
literal-Bernoulli x ordinary-NB2 route only, you can instead use an
intercept-bearing fixed-effect formula with multiple predictors, factors,
interactions, or explicit transformations. For example, suppose
`habitat_score` is a numeric environmental gradient:

```{r association-slope-example, eval = FALSE}
assoc_by_habitat <- biv_associate(
  bf(mu = bred ~ habitat_score),
  bf(mu = offspring ~ habitat_score, sigma = ~ season),
  family = list(binomial(), nbinom2()),
  data = paired_data,
  association = ~ habitat_score
)

association(assoc_by_habitat)
association(assoc_by_habitat, type = "fitted")

new_habitats <- data.frame(habitat_score = c(-1, 0, 1))
predict(
  assoc_by_habitat,
  newdata = new_habitats,
  type = "eta",
  se.fit = TRUE,
  interval = "confidence"
)
```

This simple formula fits \(a_i=\beta_0+\beta_1\,\texttt{habitat_score}_i\) and converts it
to a row-specific latent association \(\eta_i=\tanh(a_i)\).
`association()` first returns the two coefficients on the association-link
scale; `type = "fitted"` returns the corresponding `eta_i` values for the
rows used to fit the margins. `vcov()` and `confint()` also return the two-by-
two alpha covariance and coefficient intervals for this slope model. They are
interval-feasible and explicitly uncalibrated for coverage.

The same route accepts formulas such as `~ habitat_score + season`,
`~ habitat`, or `~ habitat_score * habitat`, and `predict()` evaluates the
fitted association link or bounded `eta` at compatible `newdata`. Its standard
errors are pointwise delta-method results, and its eta confidence limits are
transformed link-scale Wald intervals. They are not simultaneous bands.

This is similar in spirit to allowing a predictor for a residual `rho12`, but
it is not the same model or parameter. `rho12` belongs to a direct joint
likelihood, whereas this beta route freezes two separately fitted margins before
estimating its association link. Random effects, offsets, missing association
predictors, aliased design columns, dot expansion, and association regressions
for the other pair classes remain unsupported.

| Question | Current beta answer |
| --- | --- |
| Does habitat predict breeding? | Put `habitat` in the binary margin. |
| Does season predict offspring variation? | Put `season` in the count `sigma` margin. |
| Is there one remaining association after those effects? | Estimate `association = ~ 1`. |
| Does association change along one numeric habitat score? | For beta Bernoulli x ordinary-NB2 only, use `association = ~ habitat_score`. |
| Does association change among habitat categories or with several predictors? | For beta Bernoulli x ordinary-NB2 only, use an intercept-bearing fixed-effect formula and preserve its fitted factor levels for prediction. |

## How this differs from rho12

`rho12` belongs to a direct joint likelihood. In `biv_gaussian()` it is a
Gaussian residual correlation; in `biv_lognormal()` it is a log-response
residual correlation. `eta` is neither of those. It is a latent-normal
association obtained after separate margins have been fitted and frozen. The
new beta association slope changes the latent `eta_i`, not a direct `rho12`.

Use [Changing residual coupling with rho12](bivariate-coscale.html) for the
released Gaussian joint model. Use [Bivariate non-Gaussian models](bivariate-nongaussian.html)
to choose between an exact same-family joint model and this staged association
route.
