Regresiones y matrices

Podemos formular los modelos de regresión usando álgebra matricial. Veamos un ejemplo donde tenemos tres variables predictoras.

set.seed(123)
n <- 30

x1 <-  runif(n, -3, 3)
x2 <-  runif(n, -3, 3)
x3 <-  runif(n, -3, 3)

Como la regresión es en definitiva una función lineal, podemos hacer las cuentas para obtener los valores esperados de la variable de respuesta \(\mathbf{\mu}\) con un producto entre una matriz donde ponemos las variables predictoras \(\mathbf{X}\) y un vector de coeficientes $: \[ \mathbf{\mu} = \mathbf{X} \mathbf{\beta} \] A esta matriz \(\mathbf{X}\) se la llama “matriz de diseño”. Para tener una “ordenada al orígen” agregamos a la matriz de variables predictoras una columna de unos. Veamos cómo hacer esto en R:

X <- cbind(numeric(n) + 1, x1, x2, x3) # esta sería la llamada "matriz de diseño"
betas <- c(-1, 0.2, -0.1, 0.8)
sigma <- 0.5

m <- X %*% betas

# ahora simulamos los datos
y <- rnorm(n, m, sd = sigma)

Formular las regresiones de esta manera nos permite resolver las estimaciones de parámetros usando mínimos cuadrados. Podemos estimar los coeficientes haciendo:

\[ \hat{\mathbf{\beta}} = \left( \mathbf{X}^t \mathbf{X} \right)^{-1} \mathbf{X}^t \mathbf{y} \]

Para hacer esas cuentas en R hacemos

solve( t(X) %*% X ) %*% t(X) %*% y
##          [,1]
##    -1.0591239
## x1  0.2868337
## x2 -0.1138111
## x3  0.7865845

Podemos comprobar que lm nos da esos resultados:

lm(y ~ x1 + x2 + x3)
## 
## Call:
## lm(formula = y ~ x1 + x2 + x3)
## 
## Coefficients:
## (Intercept)           x1           x2           x3  
##     -1.0591       0.2868      -0.1138       0.7866

En análisis Bayesianos, podemos aprovechar un poco la idea de una matriz de diseño para escribir los modelos de manera más compacta. Por ejemplo en lenguaje BUGS podemos escribir:

cat(file = "reg.bug",
"
model{
  for(i in 1 : n){
    y[i] ~ dnorm(m[i], tau)
    m[i] <- b0 + inprod(b, X[i, ])
  }
  
  b0 ~ dnorm(0, 0.001)
  for (j in 1:k) {
    b[j] ~ dnorm(0, 1)
  }
  sigma ~ dnorm(0, 1)T(0, )
  tau <- pow(sigma, -2)
}
")

En este caso, la matriz de diseño no incluye una columna de unos porque quiero poner una previa diferente para la ordenada al origen. Para hacer el ajuste en JAGS:

library(jagsUI)
## Loading required package: lattice
## 
## Attaching package: 'jagsUI'
## The following object is masked from 'package:utils':
## 
##     View
X <- cbind(x1, x2, x3)
k <- ncol(X)
y <- as.vector(y)
data   <- list("y", "X", "n", "k")
params <- c("b0", "b", "sigma")

reg.sim <- jags(data, 
               inits = NULL, 
               params, 
               model.file = "reg.bug",
               n.chains = 3, 
               n.iter = 1000, 
               n.burnin = 500)
## 
## Processing function input....... 
## 
## Done. 
##  
## Compiling model graph
##    Resolving undeclared variables
##    Allocating nodes
## Graph information:
##    Observed stochastic nodes: 30
##    Unobserved stochastic nodes: 5
##    Total graph size: 233
## 
## Initializing model
## 
## Adaptive phase..... 
## Adaptive phase complete 
##  
## 
##  Burn-in phase, 500 iterations x 3 chains 
##  
## 
## Sampling from joint posterior, 500 iterations x 3 chains 
##  
## 
## Calculating statistics....... 
## 
## Done.
print(reg.sim)
## JAGS output for model 'reg.bug', generated by jagsUI.
## Estimates based on 3 chains of 1000 iterations,
## adaptation = 100 iterations (sufficient),
## burn-in = 500 iterations and thin rate = 1,
## yielding 1500 total samples from the joint posterior. 
## MCMC ran for 0.002 minutes at time 2019-05-14 22:26:39.
## 
##            mean    sd   2.5%    50%  97.5% overlap0     f  Rhat n.eff
## b0       -1.063 0.091 -1.245 -1.059 -0.889    FALSE 1.000 1.001  1500
## b[1]      0.289 0.052  0.189  0.290  0.391    FALSE 1.000 1.000  1500
## b[2]     -0.116 0.052 -0.218 -0.115 -0.017    FALSE 0.989 1.001  1469
## b[3]      0.783 0.055  0.669  0.784  0.890    FALSE 1.000 1.001  1500
## sigma     0.483 0.072  0.369  0.475  0.644    FALSE 1.000 1.004  1500
## deviance 39.857 3.550 35.213 39.079 48.717    FALSE 1.000 1.004   854
## 
## Successful convergence based on Rhat values (all < 1.1). 
## Rhat is the potential scale reduction factor (at convergence, Rhat=1). 
## For each parameter, n.eff is a crude measure of effective sample size. 
## 
## overlap0 checks if 0 falls in the parameter's 95% credible interval.
## f is the proportion of the posterior with the same sign as the mean;
## i.e., our confidence that the parameter is positive or negative.
## 
## DIC info: (pD = var(deviance)/2) 
## pD = 6.3 and DIC = 46.15 
## DIC is an estimate of expected predictive error (lower is better).

Si hacemos el análisis con Stan, podemos escribir el modelo como:

cat(file = "reg.stan", 
"
data { 
  int<lower=1> n;
  int<lower=1> k;
  vector[n] y;
  matrix[n,k] X;
}

parameters {
  real b0;
  vector[k] b;
  real<lower=0> s;
}

model{
  b0 ~ normal(0,5);
  b ~ normal(0,1);
  s ~ normal(0,1);
  y ~ normal(b0 + X*b, s);
}

"
)

veamos qué da…

library(rstan)
## Loading required package: ggplot2
## Loading required package: StanHeaders
## rstan (Version 2.18.2, GitRev: 2e1f913d3ca3)
## For execution on a local, multicore CPU with excess RAM we recommend calling
## options(mc.cores = parallel::detectCores()).
## To avoid recompilation of unchanged Stan programs, we recommend calling
## rstan_options(auto_write = TRUE)
## 
## Attaching package: 'rstan'
## The following object is masked from 'package:jagsUI':
## 
##     traceplot
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

reg_dat <- list(n = n,
               y = y,
               X = X,
               k = k)

stan.sim <- stan(file = 'reg.stan', 
            data = reg_dat,
            iter = 1000,
            chains = 3)

print(stan.sim)
## Inference for Stan model: reg.
## 3 chains, each with iter=1000; warmup=500; thin=1; 
## post-warmup draws per chain=500, total post-warmup draws=1500.
## 
##       mean se_mean   sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
## b0   -1.06    0.00 0.09 -1.24 -1.12 -1.06 -0.99 -0.88  1429 1.00
## b[1]  0.29    0.00 0.05  0.18  0.25  0.28  0.32  0.39  1486 1.00
## b[2] -0.11    0.00 0.05 -0.22 -0.15 -0.11 -0.08 -0.01  1689 1.00
## b[3]  0.79    0.00 0.06  0.68  0.75  0.78  0.82  0.90  1901 1.00
## s     0.48    0.00 0.07  0.37  0.43  0.47  0.52  0.64  1154 1.00
## lp__  6.40    0.07 1.76  1.90  5.43  6.83  7.70  8.70   578 1.02
## 
## Samples were drawn using NUTS(diag_e) at Tue May 14 22:26:42 2019.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).

Otra forma de considerar a los modelos de regresión de este tipo es pensar que la variable \(y\) proviene de una multivariada normal donde la matriz de varianza-covarianza tiene un solo valor en la diagonal (correspodiente a sigma la cuadrado) y es cero en el resto. Veamos un ejemplo con pocos datos como para ver de qué se trata:

library(mvtnorm)
n <- 6
sigma <- 0.5
S <- diag(numeric(n) + sigma^2)
S
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.25 0.00 0.00 0.00 0.00 0.00
## [2,] 0.00 0.25 0.00 0.00 0.00 0.00
## [3,] 0.00 0.00 0.25 0.00 0.00 0.00
## [4,] 0.00 0.00 0.00 0.25 0.00 0.00
## [5,] 0.00 0.00 0.00 0.00 0.25 0.00
## [6,] 0.00 0.00 0.00 0.00 0.00 0.25

Lo que vemos en la matriz de varianza-covarianza S es que la varianza es la misma para todas las observaciones (homogéneidad de varianza) y que los datos son independientes porque las covarianzas son siempre ceros.

Ahora calculamos la media y generamos los datos

X <- cbind(numeric(n) + 1, x1, x2, x3)
m <- X %*% betas
y <- rmvnorm(1, m[1:n], S)

Podemos modelar falta de independencia en los datos si consideramos valores distintos de cero en las covarianzas. Veamos por ejemplo un caso de autocorrelation espacial:

library(mvtnorm)

# función para calcular matriz de distancia en un cuadrado de lado = side
dist.matrix <- function(side)
{
  cords <- expand.grid(1:side, 1:side)
  D <- dist(cords, method="euclidean", diag=TRUE, upper=TRUE)
  D <- as.matrix(D)
  return(D)
}

# función para simular datos auto-correlacionados en 2D,
# con decaimiento exponencial de la autocorrelación 
# (la media es constante e igual a global.mu)
cor.surface <- function(side, global.mu, s, rho)
{
  D <- dist.matrix(side)
  S <- s^2 / (1 - rho) * rho^D
  mu <- rep(global.mu, times=side*side)
  M <- matrix(nrow=side, ncol=side)
  M[] <- rmvnorm(1, mu, S)
  return(M)
}


set.seed (123)
side = 10
s = 2
global.mu = 0
rho = 0.7  

M <- cor.surface(side = side, rho = rho, global.mu = global.mu, s = s)
image(M)

Ahora escribimos el modelo en BUGS

cat(file="mvnormal.bugs",
"
model
{
  y[1:N] ~ dmnorm(mu[], D.tau[,])

  for(i in 1:N){
    mu[i] <- global.mu
  }

  rho ~ dbeta(2,2)
  s ~ dnorm(0,1)T(0,)
  global.mu ~ dnorm(0, 0.1)

  for(i in 1:N){
    for(j in 1:N){
      # turning the distance matrix to covariance matrix
      D.covar[i,j] <- s*s / (1-rho) * pow(rho, D[i,j])
    }
  }
  # turning covariances into precisions:
  D.tau[1:N,1:N] <- inverse(D.covar[1:N,1:N])
}
")

y lo ajustamos con JAGS

y <- as.vector(as.matrix(M))
datos <- list(N = side * side, D = dist.matrix(side), y = y)

fit <-  jags(data=datos, 
              parameters.to.save=c("rho","s", "global.mu"),
              model.file="mvnormal.bugs",
              n.iter = 1000,
              n.chains = 3)
## 
## Processing function input....... 
## 
## Done. 
##  
## Compiling model graph
##    Resolving undeclared variables
##    Allocating nodes
## Graph information:
##    Observed stochastic nodes: 1
##    Unobserved stochastic nodes: 3
##    Total graph size: 30120
## 
## Initializing model
## 
## Adaptive phase..... 
## Adaptive phase complete 
##  
## No burn-in specified 
##  
## Sampling from joint posterior, 1000 iterations x 3 chains 
##  
## 
## Calculating statistics....... 
## 
## Done.
print(fit)
## JAGS output for model 'mvnormal.bugs', generated by jagsUI.
## Estimates based on 3 chains of 1000 iterations,
## adaptation = 100 iterations (sufficient),
## burn-in = 0 iterations and thin rate = 1,
## yielding 3000 total samples from the joint posterior. 
## MCMC ran for 1.15 minutes at time 2019-05-14 22:26:42.
## 
##              mean    sd    2.5%     50%   97.5% overlap0     f  Rhat n.eff
## rho         0.596 0.109   0.396   0.592   0.826    FALSE 1.000 1.002  2100
## s           1.814 0.129   1.583   1.808   2.081    FALSE 1.000 1.004   840
## global.mu   1.404 1.052  -0.839   1.462   3.326     TRUE 0.917 1.000  3000
## deviance  430.478 2.294 427.806 429.897 436.415    FALSE 1.000 1.008   479
## 
## Successful convergence based on Rhat values (all < 1.1). 
## Rhat is the potential scale reduction factor (at convergence, Rhat=1). 
## For each parameter, n.eff is a crude measure of effective sample size. 
## 
## overlap0 checks if 0 falls in the parameter's 95% credible interval.
## f is the proportion of the posterior with the same sign as the mean;
## i.e., our confidence that the parameter is positive or negative.
## 
## DIC info: (pD = var(deviance)/2) 
## pD = 2.6 and DIC = 433.101 
## DIC is an estimate of expected predictive error (lower is better).
#pairs(as.matrix(as.mcmc(fit)))