Source code for pyrenew.regression

"""
Helper classes for regression problems
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from typing import NamedTuple

import numpyro
import numpyro.distributions as dist
from jax.typing import ArrayLike

import pyrenew.transformation as t


[docs] class AbstractRegressionPrediction(metaclass=ABCMeta): # numpydoc ignore=GL08
[docs] @abstractmethod def predict(self): """ Make a regression prediction """ pass
[docs] @abstractmethod def sample(self, obs=None): """ Observe or sample from the regression problem according to the specified distributions """ pass
[docs] class GLMPredictionSample(NamedTuple): """ A container for holding the output from `GLMPrediction.sample()`. Attributes ---------- prediction : ArrayLike | None, optional Transformed predictions. Defaults to None. intercept : ArrayLike | None, optional Sampled intercept from intercept priors. Defaults to None. coefficients : ArrayLike | None, optional Prediction coefficients generated from coefficients priors. Defaults to None. """ prediction: ArrayLike | None = None intercept: ArrayLike | None = None coefficients: ArrayLike | None = None def __repr__(self): return ( f"GLMPredictionSample(" f"prediction={self.prediction}, " f"intercept={self.intercept}, " f"coefficients={self.coefficients})" )
[docs] class GLMPrediction(AbstractRegressionPrediction): """ Generalized linear model regression predictions """ def __init__( self, name: str, intercept_prior: dist.Distribution, coefficient_priors: dist.Distribution, transform: t.Transform = None, intercept_suffix="_intercept", coefficient_suffix="_coefficients", ) -> None: """ Default class constructor for GLMPrediction Parameters ---------- name : str The name of the prediction process, which will be used to name the constituent sampled parameters in calls to `numpyro.sample` intercept_prior : numypro.distributions.Distribution Prior distribution for the regression intercept value coefficient_priors : numpyro.distributions.Distribution Vectorized prior distribution for the regression coefficient values transform : numpyro.distributions.transforms.Transform, optional Transform linking the scale of the regression to the scale of the observation. If `None`, use an identity transform. Default `None`. intercept_suffix : str, optional Suffix for naming the intercept random variable in class to numpyro.sample(). Default `"_intercept"`. coefficient_suffix : str, optional Suffix for naming the regression coefficient random variables in calls to numpyro.sample(). Default `"_coefficients"`. """ if transform is None: transform = t.IdentityTransform() self.name = name self.transform = transform self.intercept_prior = intercept_prior self.coefficient_priors = coefficient_priors self.intercept_suffix = intercept_suffix self.coefficient_suffix = coefficient_suffix
[docs] def predict( self, intercept: ArrayLike, coefficients: ArrayLike, predictor_values: ArrayLike, ) -> ArrayLike: """ Generates a transformed prediction w/ intercept, coefficients, and predictor values Parameters ---------- intercept : ArrayLike Sampled numpyro distribution generated from intercept priors. coefficients : ArrayLike Sampled prediction coefficients distribution generated from coefficients priors. predictor_values : ArrayLike(n_predictors, n_observations) Matrix of predictor variables (covariates) for the regression problem. Each row should represent the predictor values corresponding to an observation; each column should represent a predictor variable. You do not include values of 1 for the intercept; these will be added automatically. Returns ------- ArrayLike Array of transformed predictions. """ transformed_prediction = intercept + predictor_values @ coefficients return self.transform.inv(transformed_prediction)
[docs] def sample(self, predictor_values: ArrayLike) -> GLMPredictionSample: """ Sample generalized linear model Parameters ----------- predictor_values : ArrayLike(n_predictors, n_observations) Matrix of predictor variables (covariates) for the regression problem. Each row should represent the predictor values corresponding to an observation; each column should represent a predictor variable. Do not include values of 1 for the intercept; these will be added automatically. Passed as the `predictor_values` argument to :meth:`GLMPrediction.predict()` Returns ------- GLMPredictionSample """ intercept = numpyro.sample( self.name + self.intercept_suffix, self.intercept_prior ) coefficients = numpyro.sample( self.name + self.coefficient_suffix, self.coefficient_priors ) prediction = self.predict(intercept, coefficients, predictor_values) return GLMPredictionSample( prediction=prediction, intercept=intercept, coefficients=coefficients, )
def __call__(self, *args, **kwargs): """ Alias for `sample()`. """ return self.sample(*args, **kwargs) def __repr__(self): return "GLMPrediction " + str(self.name)