# Accessing the contents of a stanfit object

#### 2020-07-27

This vignette demonstrates how to access most of data stored in a stanfit object. A stanfit object (an object of class "stanfit") contains the output derived from fitting a Stan model using Markov chain Monte Carlo or one of Stan’s variational approximations (meanfield or full-rank). Throughout the document we’ll use the stanfit object obtained from fitting the Eight Schools example model:

library(rstan)
fit <- stan_demo("eight_schools", refresh = 0)
class(fit)
[1] "stanfit"
attr(,"package")
[1] "rstan"

## Posterior draws

There are several functions that can be used to access the draws from the posterior distribution stored in a stanfit object. These are extract, as.matrix, as.data.frame, and as.array, each of which returns the draws in a different format.

#### extract()

The extract function (with its default arguments) returns a list with named components corresponding to the model parameters.

list_of_draws <- extract(fit)
print(names(list_of_draws))
[1] "mu"    "tau"   "eta"   "theta" "lp__" 

In this model the parameters mu and tau are scalars and theta is a vector with eight elements. This means that the draws for mu and tau will be vectors (with length equal to the number of post-warmup iterations times the number of chains) and the draws for theta will be a matrix, with each column corresponding to one of the eight components:

head(list_of_draws$mu) [1] 13.755611 5.088973 14.567504 10.966277 9.329064 9.746773 head(list_of_draws$tau)
[1]  5.584752  1.425946 15.275256  1.151014  5.980760  5.171067
head(list_of_draws$theta)  iterations [,1] [,2] [,3] [,4] [,5] [,6] [1,] 11.257421 12.968000 16.426172 13.004674 2.068346 7.825838 [2,] 6.939401 6.645859 4.544993 5.314296 3.955333 6.546783 [3,] 4.316781 15.154667 19.726759 14.355710 7.619603 11.677133 [4,] 10.070333 11.544674 9.669666 8.450212 10.693776 10.058468 [5,] 6.163263 11.234223 7.725507 9.205691 7.601304 8.187862 [6,] 12.823124 6.502476 16.686937 19.391841 14.496071 17.015973 iterations [,7] [,8] [1,] 14.361420 12.331327 [2,] 4.433757 5.960468 [3,] 17.586321 14.896491 [4,] 9.333538 11.061507 [5,] 6.424973 5.623696 [6,] 8.722546 12.142126 #### as.matrix(), as.data.frame(), as.array() The as.matrix, as.data.frame, and as.array functions can also be used to retrieve the posterior draws from a stanfit object: matrix_of_draws <- as.matrix(fit) print(colnames(matrix_of_draws))  [1] "mu" "tau" "eta[1]" "eta[2]" "eta[3]" "eta[4]" [7] "eta[5]" "eta[6]" "eta[7]" "eta[8]" "theta[1]" "theta[2]" [13] "theta[3]" "theta[4]" "theta[5]" "theta[6]" "theta[7]" "theta[8]" [19] "lp__"  df_of_draws <- as.data.frame(fit) print(colnames(df_of_draws))  [1] "mu" "tau" "eta[1]" "eta[2]" "eta[3]" "eta[4]" [7] "eta[5]" "eta[6]" "eta[7]" "eta[8]" "theta[1]" "theta[2]" [13] "theta[3]" "theta[4]" "theta[5]" "theta[6]" "theta[7]" "theta[8]" [19] "lp__"  array_of_draws <- as.array(fit) print(dimnames(array_of_draws)) $iterations
NULL

$chains [1] "chain:1" "chain:2" "chain:3" "chain:4"$parameters
[1] "mu"       "tau"      "eta[1]"   "eta[2]"   "eta[3]"   "eta[4]"
[7] "eta[5]"   "eta[6]"   "eta[7]"   "eta[8]"   "theta[1]" "theta[2]"
[13] "theta[3]" "theta[4]" "theta[5]" "theta[6]" "theta[7]" "theta[8]"
[19] "lp__"    

The as.matrix and as.data.frame methods essentially return the same thing except in matrix and data frame form, respectively. The as.array method returns the draws from each chain separately and so has an additional dimension:

print(dim(matrix_of_draws))
print(dim(df_of_draws))
print(dim(array_of_draws))
[1] 4000   19
[1] 4000   19
[1] 1000    4   19

By default all of the functions for retrieving the posterior draws return the draws for all parameters (and generated quantities). The optional argument pars (a character vector) can be used if only a subset of the parameters is desired, for example:

mu_and_theta1 <- as.matrix(fit, pars = c("mu", "theta[1]"))
head(mu_and_theta1)
          parameters
iterations         mu  theta[1]
[1,]  8.9870595  7.620988
[2,]  0.7078515  7.170478
[3,] 13.6528703 13.054554
[4,]  6.2690719  6.867772
[5,]  7.3994697  6.221234
[6,]  2.8647763 12.793815

## Posterior summary statistics and convergence diagnostics

Summary statistics are obtained using the summary function. The object returned is a list with two components:

fit_summary <- summary(fit)
print(names(fit_summary))
[1] "summary"   "c_summary"

In fit_summary$summary all chains are merged whereas fit_summary$c_summary contains summaries for each chain individually. Typically we want the summary for all chains merged, which is what we’ll focus on here.

The summary is a matrix with rows corresponding to parameters and columns to the various summary quantities. These include the posterior mean, the posterior standard deviation, and various quantiles computed from the draws. The probs argument can be used to specify which quantiles to compute and pars can be used to specify a subset of parameters to include in the summary.

For models fit using MCMC, also included in the summary are the Monte Carlo standard error (se_mean), the effective sample size (n_eff), and the R-hat statistic (Rhat).

print(fit_summary$summary)  mean se_mean sd 2.5% 25% mu 7.949724852 0.11645748 5.0220423 -1.8312424 4.7576375 tau 6.767792859 0.26071994 6.3574731 0.2470565 2.5041545 eta[1] 0.408701730 0.01386516 0.9501978 -1.4874739 -0.2086203 eta[2] 0.007727134 0.01322405 0.8849320 -1.7512963 -0.5762528 eta[3] -0.192114553 0.01404407 0.9226422 -2.0030901 -0.7969951 eta[4] -0.033759894 0.01318734 0.8612730 -1.7409539 -0.5971957 eta[5] -0.346857560 0.01364171 0.8636999 -1.9529636 -0.9314834 eta[6] -0.230036510 0.01384811 0.8815712 -1.9260533 -0.7873415 eta[7] 0.343687116 0.01377346 0.8887923 -1.4161332 -0.2242292 eta[8] 0.035561913 0.01366197 0.9363172 -1.8079512 -0.5818113 theta[1] 11.462536053 0.15084188 8.3682751 -2.3660092 5.9851278 theta[2] 7.918125583 0.09708623 6.5484705 -5.1976169 3.8269726 theta[3] 6.180546976 0.12959986 7.8215647 -11.7517178 2.0698119 theta[4] 7.619393810 0.09174090 6.3401521 -4.7389570 3.7137076 theta[5] 5.105055688 0.10064363 6.2743073 -8.5951843 1.4489822 theta[6] 5.845512902 0.12290817 6.9074684 -9.5681116 2.1296288 theta[7] 10.599010949 0.11468754 6.8046805 -1.3860025 6.1034108 theta[8] 8.283245665 0.13464191 8.1685831 -8.0754557 3.6377522 lp__ -39.526738826 0.07853786 2.6176025 -45.3577084 -41.0664787 50% 75% 97.5% n_eff Rhat mu 7.835402041 11.1348996 18.358286 1859.6276 1.0005667 tau 5.335645186 9.3223878 20.538802 594.5941 1.0047787 eta[1] 0.421892594 1.0377818 2.231475 4696.5429 0.9997337 eta[2] 0.009115021 0.5805529 1.706422 4478.0686 0.9997160 eta[3] -0.207774375 0.4224644 1.641023 4315.9954 1.0009866 eta[4] -0.031837545 0.5329859 1.670423 4265.4744 0.9991571 eta[5] -0.371184839 0.1986612 1.444355 4008.5562 0.9995339 eta[6] -0.240246536 0.3500333 1.538437 4052.6007 0.9999744 eta[7] 0.355068745 0.9188961 2.098127 4164.0347 0.9994926 eta[8] 0.039034679 0.6629433 1.848352 4696.9896 1.0006744 theta[1] 10.274798188 15.7183875 30.977015 3077.7122 1.0003125 theta[2] 7.704921763 11.9399689 21.363006 4549.5085 1.0002197 theta[3] 6.720866758 10.8636365 21.017014 3642.3212 0.9997713 theta[4] 7.510177300 11.4861718 20.160995 4776.0998 0.9994167 theta[5] 5.670108610 9.2081225 16.259682 3886.5025 0.9991307 theta[6] 6.241934340 10.2265114 18.556742 3158.4669 1.0006471 theta[7] 9.874164480 14.5963734 25.669570 3520.3269 0.9999671 theta[8] 8.047692191 12.9014021 26.050090 3680.7215 1.0003636 lp__ -39.276377470 -37.7010733 -35.102266 1110.8341 1.0025522 If, for example, we wanted the only quantiles included to be 10% and 90%, and for only the parameters included to be mu and tau, we would specify that like this: mu_tau_summary <- summary(fit, pars = c("mu", "tau"), probs = c(0.1, 0.9))$summary
print(mu_tau_summary)
        mean   se_mean       sd      10%      90%     n_eff     Rhat
mu  7.949725 0.1164575 5.022042 1.728870 14.23924 1859.6276 1.000567
tau 6.767793 0.2607199 6.357473 1.045481 13.80119  594.5941 1.004779

Since mu_tau_summary is a matrix we can pull out columns using their names:

mu_tau_80pct <- mu_tau_summary[, c("10%", "90%")]
print(mu_tau_80pct)
         10%      90%
mu  1.728870 14.23924
tau 1.045481 13.80119

## Sampler diagnostics

For models fit using MCMC the stanfit object will also contain the values of parameters used for the sampler. The get_sampler_params function can be used to access this information.

The object returned by get_sampler_params is a list with one component (a matrix) per chain. Each of the matrices has number of columns corresponding to the number of sampler parameters and the column names provide the parameter names. The optional argument inc_warmup (defaulting to TRUE) indicates whether to include the warmup period.

sampler_params <- get_sampler_params(fit, inc_warmup = FALSE)
sampler_params_chain1 <- sampler_params[[1]]
colnames(sampler_params_chain1)
[1] "accept_stat__" "stepsize__"    "treedepth__"   "n_leapfrog__"
[5] "divergent__"   "energy__"     

To do things like calculate the average value of accept_stat__ for each chain (or the maximum value of treedepth__ for each chain if using the NUTS algorithm, etc.) the sapply function is useful as it will apply the same function to each component of sampler_params:

mean_accept_stat_by_chain <- sapply(sampler_params, function(x) mean(x[, "accept_stat__"]))
print(mean_accept_stat_by_chain)
[1] 0.8601258 0.8803538 0.8626561 0.9327039
max_treedepth_by_chain <- sapply(sampler_params, function(x) max(x[, "treedepth__"]))
print(max_treedepth_by_chain)
[1] 4 4 5 5

## Model code

The Stan program itself is also stored in the stanfit object and can be accessed using get_stancode:

code <- get_stancode(fit)

The object code is a single string and is not very intelligible when printed:

print(code)
[1] "data {\n  int<lower=0> J;          // number of schools \n  real y[J];               // estimated treatment effects\n  real<lower=0> sigma[J];  // s.e. of effect estimates \n}\nparameters {\n  real mu; \n  real<lower=0> tau;\n  vector[J] eta;\n}\ntransformed parameters {\n  vector[J] theta;\n  theta = mu + tau * eta;\n}\nmodel {\n  target += normal_lpdf(eta | 0, 1);\n  target += normal_lpdf(y | theta, sigma);\n}"
attr(,"model_name2")
[1] "schools"

A readable version can be printed using cat:

cat(code)
data {
int<lower=0> J;          // number of schools
real y[J];               // estimated treatment effects
real<lower=0> sigma[J];  // s.e. of effect estimates
}
parameters {
real mu;
real<lower=0> tau;
vector[J] eta;
}
transformed parameters {
vector[J] theta;
theta = mu + tau * eta;
}
model {
target += normal_lpdf(eta | 0, 1);
target += normal_lpdf(y | theta, sigma);
}

## Initial values

The get_inits function returns initial values as a list with one component per chain. Each component is itself a (named) list containing the initial values for each parameter for the corresponding chain:

inits <- get_inits(fit)
inits_chain1 <- inits[[1]]
print(inits_chain1)
$mu [1] -1.001074$tau
[1] 0.3153299

$eta [1] -0.002760239 1.212609451 -0.538926504 -0.604310311 -1.852417385 [6] 0.064908690 -0.803602925 1.006946375$theta
[1] -1.0019444 -0.6187019 -1.1710136 -1.1916311 -1.5851966 -0.9806063 -1.2544740
[8] -0.6835537

## (P)RNG seed

The get_seed function returns the (P)RNG seed as an integer:

print(get_seed(fit))
[1] 1007283841

## Warmup and sampling times

The get_elapsed_time function returns a matrix with the warmup and sampling times for each chain:

print(get_elapsed_time(fit))
          warmup   sample
chain:1 0.031855 0.031802
chain:2 0.034966 0.031189
chain:3 0.033241 0.032088
chain:4 0.031447 0.036188