rethinking R Package

repository·master·Indexed 25 days ago

https://github.com/rmcelreath/rethinking

An R package for Bayesian data analysis accompanying the book 'Statistical Rethinking' by Richard McElreath. It provides tools for explicit model specification using alist(), quadratic approximations via quap(), and Hamiltonian Monte Carlo sampling via ulam() (which compiles models into Stan). The package supports multilevel models, Gaussian processes, missing data imputation via merge_missing, and model comparison using WAIC and LOO.

Tokens
4.2K
Snippets
14
Records
19
Agent score
32%

What's inside rethinking

  1. Handle binary missing values with semi-automated marginalization

    master

    When working with binary (0/1) predictors that contain missing values (NA), Stan cannot sample the discrete parameters directly. map2stan handles this by performing semi-automated marginalization. It constructs a mixture model where the likelihood is the sum of the log-likelihoods conditional on all possible combinations of the missing 0/1 values.

    To use this, specify a distribution for the binary variable (e.g., bernoulli(phi)) and ensure you provide constraints for the hyperparameters (e.g., phi must be between 0 and 1).

    If you need to recover the unobserved values, set do_discrete_imputation=TRUE. This will compute the posterior probability of each missing value being 1.

    # Example: Binary predictor with missingness
    N <- 100
    N_miss <- 10
    x <- rbinom( N , size=1 , prob=0.5 )
    y <- rnorm( N , 2*x , 1 )
    x[ sample(1:N,size=N_miss) ] <- NA
    
    f6 <- alist(
        y ~ dnorm( mu , sigma ),
        mu <- a + b*x,
        x ~ bernoulli( phi ),
        a ~ dnorm( 0 , 100 ),
        b ~ dnorm( 0  , 10 ),
        phi ~ beta( 1 , 1 ),
        sigma ~ dcauchy(0,2)
    )
    
    # Use do_discrete_imputation=TRUE to get imputed probabilities
    m6 <- map2stan( f6 , data=list(y=y,x=x) , constraints=list(phi="lower=0,upper=1") ,
          do_discrete_imputation=TRUE )
    
    precis( m6 , depth=2 )
  2. Specify models using explicit distributional assumptions

    master

    Unlike typical formula-based R tools, rethinking requires models to be specified as a list of explicit distributional assumptions using alist(). This approach forces the user to define the likelihood and the priors for every parameter.

    Example of a simple Gaussian model:

    • The first formula is the likelihood (probability of the outcome).
    • Subsequent formulas are the priors for the parameters.
    f <- alist(
        y ~ dnorm( mu , sigma ),
        mu ~ dnorm( 0 , 10 ),
        sigma ~ dexp( 1 )
    )
    f <- alist(
        y ~ dnorm( mu , sigma ),
        mu ~ dnorm( 0 , 10 ),
        sigma ~ dexp( 1 )
    )
  3. Install the rethinking package

    master

    The rethinking package is not on CRAN and must be installed from GitHub. There are two versions available:

    Full Version (includes MCMC/Stan support)

    Requires a C++ toolchain and cmdstanr to be installed on your system first.

    1. Install C++ toolchain (follow mc-stan.org instructions).
    2. Install cmdstanr and compile libraries using cmdstanr::install_cmdstan().
    3. Install rethinking via R:
    install.packages(c("coda","mvtnorm","devtools","loo","dagitty","shape"))
    devtools::install_github("rmcelreath/rethinking")

    Slim Version (no MCMC)

    If you only need quadratic approximation (quap) and want to avoid Stan/MCMC setup, install the @slim branch:

    install.packages(c("coda","mvtnorm","devtools","loo","dagitty"))
    devtools::install_github("rmcelreath/rethinking@slim")
    install.packages(c("coda","mvtnorm","devtools","loo","dagitty","shape"))
    devtools::install_github("rmcelreath/rethinking")
  4. Implement Gaussian processes in `ulam`

    master

    Gaussian processes can be implemented in ulam using the cov_GPL2 macro.

    For a standard Gaussian process, you define a covariance matrix SIGMA using cov_GPL2( Dmat , etasq , rhosq , 0.01 ), where Dmat is a distance matrix.

    For more complex (e.g., non-centered) Gaussian processes, use the <<- operator for direct assignment to avoid loops during Stan compilation. This allows you to perform linear algebra operations, such as multiplying a Cholesky factor by a vector of random effects, directly within the model definition.

    # Simple Gaussian Process example
    m_GP1 <- ulam(
        alist(
            y ~ poisson( mu ),
            log(mu) <- a + aj[society] + b*log_pop,
            a ~ normal(0,10),
            b ~ normal(0,1),
            vector[10]: aj ~ multi_normal( 0 , SIGMA ),
            matrix[10,10]: SIGMA <- cov_GPL2( Dmat , etasq , rhosq , 0.01 ),
            etasq ~ exponential(1),
            rhosq ~ exponential(1)
        ),
        data=dat )
  5. Implement mixture models and conditional statements in `ulam`

    master

    The ulam function supports if-then logic and custom distribution assignments using the | (conditional) operator and the custom keyword. This is useful for coding mixture models like zero-inflated Poisson or models with discrete missing values.

    When using custom, you are defining custom target updates in Stan. You can use standard Stan functions like log_mix, log1m, and poisson_lpmf within these blocks.

    Example: A zero-inflated Poisson model where y|y==0 uses a mixture and y|y>0 uses a standard Poisson likelihood.

    # zero-inflated poisson model
    m_zip <- ulam(
        alist(
            y|y==0 ~ custom( log_mix( p , 0 , poisson_lpmf(0|lambda) ) ),
            y|y>0 ~ custom( log1m(p) + poisson_lpmf(y|lambda) ),
            logit(p) <- ap,
            log(lambda) <- al + bl*x,
            ap ~ dnorm(0,1),
            al ~ dnorm(0,10),
            bl ~ normal(0,1)
        ) ,
        data=list(y=y,x=x) )
  6. Compare models using Information Criteria (DIC, WAIC)

    master

    For ordinary GLMs and GLMMs, map and map2stan provide Deviance Information Criterion (DIC) and Watanabe-Akaike Information Criterion (WAIC).

    • Use the compare() function to summarize comparisons between models, which includes standard errors for WAIC.
    • ulam supports WAIC calculation by passing log_lik=TRUE, which returns the log-likelihood vector required by the loo package.
    • ensemble() computes link and sim outputs for a collection of models, weighting them by their Akaike weights (derived from WAIC).
  7. Specify multilevel models in ulam()

    master

    The ulam() function allows for complex multilevel models, including varying intercepts and varying slopes. You can use explicit vector/matrix declarations to handle grouped effects.

    Varying Intercepts Example:

    m_glmm1 <- ulam(
        alist(
            admit ~ binomial(applications,p),
            logit(p) <- a[dept] + b*male,
            a[dept] ~ normal( abar , sigma ),
            abar ~ normal( 0 , 4 ),
            sigma ~ half_normal(0,1),
            b ~ normal(0,1)
        ), data=UCBadmit )

    Varying Slopes with Vector Declaration: To declare a vector of length $N$ for each group, use the syntax vector[N]:name[group_index]. For example, vector[2]:v[dept] declares a vector of length 2 for each unique dept.

    m_glmm3 <- ulam(
        alist(
            admit ~ binomial(applications,p),
            logit(p) <- v[dept,1] + v[dept,2]*male,
            vector[2]:v[dept] ~ multi_normal( c(abar,bbar) , Rho , sigma ),
            abar ~ normal( 0 , 4 ),
            bbar ~ normal(0,1),
            sigma ~ half_normal(0,1),
            Rho ~ lkjcorr(2)
        ), data=UCBadmit )
    m_glmm1 <- ulam(
        alist(
            admit ~ binomial(applications,p),
            logit(p) <- a[dept] + b*male,
            a[dept] ~ normal( abar , sigma ),
            abar ~ normal( 0 , 4 ),
            sigma ~ half_normal(0,1),
            b ~ normal(0,1)
        ), data=UCBadmit )
  8. Impute continuous missing data using `merge_missing`

    master

    To handle missing real-valued data without manual bookkeeping, use the merge_missing macro in ulam.

    Syntax: x_merge <- merge_missing( x , x_impute )

    • x: The original vector containing NA values.
    • x_impute: A parameter vector (of the correct length for the missing values) that you assign a prior to.

    merge_missing automatically finds the NA indices in x, builds the x_impute vector, and creates a combined x_merge vector used in the model.

    # Example: Imputing missing values in x
    UCBadmit$x <- rnorm(12)
    UCBadmit$x[1:2] <- NA
    
    m_miss <- ulam(
        alist(
            admit ~ binomial(applications,p),
            logit(p) <- a + b*male + bx*x_merge,
            x_merge ~ normal( 0 , 1 ),
            x_merge <- merge_missing( x , x_impute ),
            a ~ normal(0,4),
            b ~ normal(0,1),
            bx ~ normal(0,1)
        ),
        data=UCBadmit )
  9. Enable within-chain multithreading with `cmdstanr`

    master

    To use within-chain multithreading in rethinking, you must use the cmdstanr backend.

    1. Install cmdstanr: devtools::install_github("stan-dev/cmdstanr").
    2. Install CmdStan: cmdstanr::install_cmdstan().
    3. In your ulam call, set cmdstan=TRUE and specify the number of threads using the threads argument.

    Note: Some complex models may not support automatic multithreading and might require manual Stan implementation using reduce_sum.

    # Example: ulam with cmdstan and 2 threads
    m1 <- ulam(
        alist(
            y ~ binomial_logit( m , logit_p ),
            logit_p <- a + b*x,
            a ~ normal(0,1.5),
            b ~ normal(0,0.5)
        ) , 
        data=dat ,
        cmdstan=TRUE ,
        threads=2 ,
        refresh=1000 )
  10. Correcting `map` usage for start values

    master
    In the 1st Edition (page 42), the text incorrectly states that map requires a list of start values. As long as priors are provided for each parameter, map does not require a list of start values. The example in code box 2.6 demonstrates this by omitting start values.
  11. Fixing 'dim(X) must have a positive length' errors in link() output

    master

    In recent versions of rethinking, the link() function returns a list containing all linear models (e.g., list(mu = ..., gamma = ...)), rather than a single matrix/vector.

    If you encounter the error Error in apply(..., 2, mean) : dim(X) must have a positive length when trying to process results from link(), you must explicitly access the desired component (usually mu) from the list before passing it to apply().