stan-dev / math

The Stan Math Library is a C++ template library for automatic differentiation of any order using forward, reverse, and mixed modes. It includes a range of built-in functions for probabilistic modeling, linear algebra, and equation solving.
https://mc-stan.org
BSD 3-Clause "New" or "Revised" License
738 stars 186 forks source link

Bivariate normal distribution #962

Open rtrangucci opened 6 years ago

rtrangucci commented 6 years ago

Description

Add the bivariate normal distribution with scalar inputs. Specifically, add:

Reference: #2356

Example

Bivariate probit model

functions {
  int count_cases(int[] y1, int[] y2, int case_1, int case_2) {
    int N = size(y1);
    int N_cases = 0;
    for (n in 1:N) {
      if (y1[n] == case_1 && y2[n] == case_2)
        N_cases += 1;
    }
    return N_cases;
  }
}
data {
  int<lower=1> N;
  int<lower=1> K1;
  int<lower=1> K2;
  int<lower=0,upper=1> y1[N];
  int<lower=0,upper=1> y2[N];
  matrix[N, K1] X1;
  matrix[N, K2] X2;
}
transformed data {
  int N_00 = count_cases(y1, y2, 0, 0);
  int N_10 = count_cases(y1, y2, 1, 0);
  int N_01 = count_cases(y1, y2, 0, 1);
  int N_11 = count_cases(y1, y2, 1, 1);
  int n_00 = 1;
  int n_10 = 1;
  int n_01 = 1;
  int n_11 = 1;
  int idx_00[N_00];
  int idx_10[N_10];
  int idx_01[N_01];
  int idx_11[N_11];
  for (n in 1:N) {
    if(y1[n] == 0 && y2[n] == 0) {
      idx_00[n_00] = n;
      n_00 += 1;
    }
    if(y1[n] == 1 && y2[n] == 0) {
      idx_10[n_10] = n;
      n_10 += 1;
    }
    if(y1[n] == 0 && y2[n] == 1) {
      idx_01[n_01] = n;
      n_01 += 1;
    }
    if(y1[n] == 1 && y2[n] == 1) {
      idx_11[n_11] = n;
      n_11 += 1;
    }
  }
}
parameters {
  real<lower=0,upper=1> rho_raw;
  vector[K1] beta_1;
  vector[K2] beta_2;
  real alpha_1;
  real alpha_2;
}
transformed parameters {
  real rho = -1 + 2 * rho_raw;
}
model {
  vector[N] mu_1 = alpha_1 + X1 * beta_1;
  vector[N] mu_2 = alpha_2 + X2 * beta_2;
  rho_raw ~ beta(4,4);
  alpha_1 ~ normal(0, 2);
  alpha_2 ~ normal(0, 2);
  beta_1 ~ normal(0, 2);
  beta_2 ~ normal(0, 2);
  target += std_binormal_lcdf(-mu_1[idx_00], -mu_2[idx_00] | rho);
  target += std_binormal_lcdf(mu_1[idx_10], -mu_2[idx_10] | -rho);
  target += std_binormal_lcdf(-mu_1[idx_01], mu_2[idx_01] | -rho);
  target += std_binormal_lcdf(mu_1[idx_11], mu_2[idx_11] | rho);
}

Additional Information

binormal_cdf and binormal_lcdf can implement the following algorithms:

Current Math Version

v2.18.0

bgoodri commented 6 years ago

My guess is the Pan paper is the best approach for the _lcdf but we would have to have a heuristic for how many terms of the infinite sum to compute based on Phi(a), Phi(b), asin(rho), etc.

rtrangucci commented 6 years ago

Yeah, that sounds good to me. Here's a reference for the gradient of the CDF: https://blogs.sas.com/content/iml/2013/09/20/gradient-of-the-bivariate-normal-cumulative-distribution.html

rtrangucci commented 6 years ago

FYI, I'm waiting on #978 to work on the nonstandardized distributions.