diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css index 049a254..4b7d98b 100644 --- a/assets/_mkdocstrings.css +++ b/assets/_mkdocstrings.css @@ -26,39 +26,84 @@ float: right; } -/* Keep headings consistent. */ -h1.doc-heading, -h2.doc-heading, -h3.doc-heading, -h4.doc-heading, -h5.doc-heading, -h6.doc-heading { - font-weight: 400; - line-height: 1.5; - color: inherit; - text-transform: none; +/* Symbols in Navigation and ToC. */ +:root, +[data-md-color-scheme="default"] { + --doc-symbol-attribute-fg-color: #953800; + --doc-symbol-function-fg-color: #8250df; + --doc-symbol-method-fg-color: #8250df; + --doc-symbol-class-fg-color: #0550ae; + --doc-symbol-module-fg-color: #5cad0f; + + --doc-symbol-attribute-bg-color: #9538001a; + --doc-symbol-function-bg-color: #8250df1a; + --doc-symbol-method-bg-color: #8250df1a; + --doc-symbol-class-bg-color: #0550ae1a; + --doc-symbol-module-bg-color: #5cad0f1a; +} + +[data-md-color-scheme="slate"] { + --doc-symbol-attribute-fg-color: #ffa657; + --doc-symbol-function-fg-color: #d2a8ff; + --doc-symbol-method-fg-color: #d2a8ff; + --doc-symbol-class-fg-color: #79c0ff; + --doc-symbol-module-fg-color: #baff79; + + --doc-symbol-attribute-bg-color: #ffa6571a; + --doc-symbol-function-bg-color: #d2a8ff1a; + --doc-symbol-method-bg-color: #d2a8ff1a; + --doc-symbol-class-bg-color: #79c0ff1a; + --doc-symbol-module-bg-color: #baff791a; +} + +code.doc-symbol { + border-radius: .1rem; + font-size: .85em; + padding: 0 .3em; + font-weight: bold; +} + +code.doc-symbol-attribute { + color: var(--doc-symbol-attribute-fg-color); + background-color: var(--doc-symbol-attribute-bg-color); +} + +code.doc-symbol-attribute::after { + content: "attr"; +} + +code.doc-symbol-function { + color: var(--doc-symbol-function-fg-color); + background-color: var(--doc-symbol-function-bg-color); +} + +code.doc-symbol-function::after { + content: "func"; } -h1.doc-heading { - font-size: 1.6rem; +code.doc-symbol-method { + color: var(--doc-symbol-method-fg-color); + background-color: var(--doc-symbol-method-bg-color); } -h2.doc-heading { - font-size: 1.2rem; +code.doc-symbol-method::after { + content: "meth"; } -h3.doc-heading { - font-size: 1.15rem; +code.doc-symbol-class { + color: var(--doc-symbol-class-fg-color); + background-color: var(--doc-symbol-class-bg-color); } -h4.doc-heading { - font-size: 1.10rem; +code.doc-symbol-class::after { + content: "class"; } -h5.doc-heading { - font-size: 1.05rem; +code.doc-symbol-module { + color: var(--doc-symbol-module-fg-color); + background-color: var(--doc-symbol-module-bg-color); } -h6.doc-heading { - font-size: 1rem; +code.doc-symbol-module::after { + content: "mod"; } \ No newline at end of file diff --git a/distributions/index.html b/distributions/index.html index ebd9fcc..19ddcc5 100644 --- a/distributions/index.html +++ b/distributions/index.html @@ -460,7 +460,7 @@
  • - from_mean() + from_mean @@ -469,7 +469,7 @@
  • - from_successes_and_failures() + from_successes_and_failures @@ -583,7 +583,7 @@
  • - sample_beta() + sample_beta @@ -592,7 +592,7 @@
  • - sample_variance() + sample_variance @@ -1017,7 +1017,7 @@
  • - from_mean() + from_mean @@ -1026,7 +1026,7 @@
  • - from_successes_and_failures() + from_successes_and_failures @@ -1140,7 +1140,7 @@
  • - sample_beta() + sample_beta @@ -1149,7 +1149,7 @@
  • - sample_variance() + sample_variance @@ -1257,7 +1257,6 @@

    Distributions

    -

    Beta @@ -1405,7 +1404,6 @@

    -

    from_mean(mean, alpha) @@ -1442,7 +1440,6 @@

    -

    from_successes_and_failures(successes, failures) @@ -1482,13 +1479,13 @@

    -

    BetaBinomial @@ -1635,13 +1632,13 @@

    +
    -

    BetaNegativeBinomial @@ -1752,13 +1749,13 @@

    +
    -

    Binomial @@ -1887,13 +1884,13 @@

    +
    -

    Dirichlet @@ -2004,13 +2001,13 @@

    +
    -

    Exponential @@ -2119,13 +2116,13 @@

    +
    -

    Gamma @@ -2258,13 +2255,13 @@

    +
    -

    Geometric @@ -2363,13 +2360,13 @@

    +
    -

    InverseGamma @@ -2486,13 +2483,13 @@

    +
    -

    NegativeBinomial @@ -2619,13 +2616,13 @@

    +
    -

    Normal @@ -2754,13 +2751,13 @@

    +
    -

    NormalInverseGamma @@ -3018,7 +3015,6 @@

    -

    sample_beta(size, return_variance=False, random_state=None) @@ -3180,7 +3176,6 @@

    sample_variance(size, random_state=None) @@ -3295,13 +3290,13 @@

    -

    Poisson @@ -3420,13 +3415,13 @@

    +
    -

    StudentT @@ -3561,13 +3556,13 @@

    +
    -

    Uniform @@ -3692,6 +3687,7 @@

    + diff --git a/index.html b/index.html index a84826c..a9dbb84 100644 --- a/index.html +++ b/index.html @@ -378,18 +378,27 @@

  • - + - Basic Usage + Features
  • - + - Features + Supported Models + + + +
  • + +
  • + + + Basic Usage @@ -860,18 +869,27 @@
  • - + - Basic Usage + Features
  • - + - Features + Supported Models + + + +
  • + +
  • + + + Basic Usage @@ -922,7 +940,30 @@

    Conjugate Models

    Installation

    pip install conjugate-models
     
    +

    Features

    + +

    Supported Models

    +

    Many likelihoods are supported including

    +

    Basic Usage

    +
      +
    1. Define prior distribution from distributions module
    2. +
    3. Pass data and prior into model from models modules
    4. +
    5. Analytics with posterior and posterior predictive distributions
    6. +
    from conjugate.distributions import Beta, BetaBinomial
     from conjugate.models import binomial_beta, binomial_beta_posterior_predictive
     
    @@ -958,15 +999,6 @@ 

    Basic Usage

    plt.show()

    -

    Features

    -

    Too Simple?

    Simple model, sure. Useful model, potentially.

    Constant probability of success, p, for n trials.

    diff --git a/mixins/index.html b/mixins/index.html index be2c1ed..6d56697 100644 --- a/mixins/index.html +++ b/mixins/index.html @@ -502,7 +502,7 @@
  • - plot_pdf() + plot_pdf @@ -526,7 +526,7 @@
  • - plot_pdf() + plot_pdf @@ -559,7 +559,7 @@
  • - set_bounds() + set_bounds @@ -568,7 +568,7 @@
  • - set_min_value() + set_min_value @@ -582,7 +582,7 @@
  • - resolve_label() + resolve_label @@ -951,7 +951,7 @@
  • - plot_pdf() + plot_pdf @@ -975,7 +975,7 @@
  • - plot_pdf() + plot_pdf @@ -1008,7 +1008,7 @@
  • - set_bounds() + set_bounds @@ -1017,7 +1017,7 @@
  • - set_min_value() + set_min_value @@ -1031,7 +1031,7 @@
  • - resolve_label() + resolve_label @@ -1099,7 +1099,6 @@

    Mixins

    -

    ContinuousPlotDistMixin @@ -1215,7 +1214,6 @@

    -

    plot_pdf(ax=None, **kwargs) @@ -1340,13 +1338,13 @@

    +
    -

    DirichletPlotDistMixin @@ -1424,7 +1422,6 @@

    -

    plot_pdf(ax=None, samples=1000, **kwargs) @@ -1484,13 +1481,13 @@

    +
    -

    DiscretePlotMixin @@ -1615,13 +1612,13 @@

    +
    -

    PlotDistMixin @@ -1763,7 +1760,6 @@

    -

    set_bounds(lower, upper) @@ -1792,7 +1788,6 @@

    -

    set_min_value(value) @@ -1826,6 +1821,7 @@

    + @@ -1834,7 +1830,6 @@

    -

    resolve_label(label, yy) @@ -1926,7 +1921,6 @@

    -

    SliceMixin @@ -1991,6 +1985,7 @@

    + diff --git a/models/index.html b/models/index.html index 40eb128..1efa659 100644 --- a/models/index.html +++ b/models/index.html @@ -471,7 +471,7 @@
  • - binomial_beta() + binomial_beta @@ -480,7 +480,7 @@
  • - binomial_beta_posterior_predictive() + binomial_beta_posterior_predictive @@ -489,7 +489,7 @@
  • - categorical_dirichlet() + categorical_dirichlet @@ -498,7 +498,7 @@
  • - exponetial_gamma() + exponetial_gamma @@ -507,7 +507,7 @@
  • - geometric_beta() + geometric_beta @@ -516,7 +516,7 @@
  • - linear_regression() + linear_regression @@ -525,7 +525,7 @@
  • - linear_regression_posterior_predictive() + linear_regression_posterior_predictive @@ -534,7 +534,7 @@
  • - multinomial_dirichlet() + multinomial_dirichlet @@ -543,7 +543,7 @@
  • - negative_binomial_beta() + negative_binomial_beta @@ -552,7 +552,7 @@
  • - negative_binomial_beta_posterior_predictive() + negative_binomial_beta_posterior_predictive @@ -561,7 +561,7 @@
  • - normal_known_mean() + normal_known_mean @@ -570,7 +570,7 @@
  • - normal_known_mean_posterior_predictive() + normal_known_mean_posterior_predictive @@ -579,7 +579,7 @@
  • - poisson_gamma() + poisson_gamma @@ -588,7 +588,7 @@
  • - poisson_gamma_posterior_predictive() + poisson_gamma_posterior_predictive @@ -950,7 +950,7 @@
  • - binomial_beta() + binomial_beta @@ -959,7 +959,7 @@
  • - binomial_beta_posterior_predictive() + binomial_beta_posterior_predictive @@ -968,7 +968,7 @@
  • - categorical_dirichlet() + categorical_dirichlet @@ -977,7 +977,7 @@
  • - exponetial_gamma() + exponetial_gamma @@ -986,7 +986,7 @@
  • - geometric_beta() + geometric_beta @@ -995,7 +995,7 @@
  • - linear_regression() + linear_regression @@ -1004,7 +1004,7 @@
  • - linear_regression_posterior_predictive() + linear_regression_posterior_predictive @@ -1013,7 +1013,7 @@
  • - multinomial_dirichlet() + multinomial_dirichlet @@ -1022,7 +1022,7 @@
  • - negative_binomial_beta() + negative_binomial_beta @@ -1031,7 +1031,7 @@
  • - negative_binomial_beta_posterior_predictive() + negative_binomial_beta_posterior_predictive @@ -1040,7 +1040,7 @@
  • - normal_known_mean() + normal_known_mean @@ -1049,7 +1049,7 @@
  • - normal_known_mean_posterior_predictive() + normal_known_mean_posterior_predictive @@ -1058,7 +1058,7 @@
  • - poisson_gamma() + poisson_gamma @@ -1067,7 +1067,7 @@
  • - poisson_gamma_posterior_predictive() + poisson_gamma_posterior_predictive @@ -1121,7 +1121,6 @@

    Models

    -

    binomial_beta(n, x, beta_prior) @@ -1260,7 +1259,6 @@

    -

    binomial_beta_posterior_predictive(n, beta) @@ -1375,7 +1373,6 @@

    categorical_dirichlet(x, dirichlet_prior) @@ -1408,7 +1405,6 @@

    -

    exponetial_gamma(x_total, n, gamma_prior) @@ -1445,7 +1441,6 @@

    -

    geometric_beta(x_total, n, beta_prior, one_start=True) @@ -1602,7 +1597,6 @@

    -

    linear_regression(X, y, normal_inverse_gamma_prior, inv=np.linalg.inv) @@ -1821,7 +1815,6 @@

    -

    linear_regression_posterior_predictive(normal_inverse_gamma, X, eye=np.eye) @@ -1874,7 +1867,6 @@

    multinomial_dirichlet(x, dirichlet_prior) @@ -1993,7 +1985,6 @@

    -

    negative_binomial_beta(r, n, x, beta_prior) @@ -2037,7 +2028,6 @@

    -

    negative_binomial_beta_posterior_predictive(r, beta) @@ -2066,7 +2056,6 @@

    normal_known_mean(x_total, x2_total, n, mu, inverse_gamma_prior) @@ -2251,7 +2240,6 @@

    -

    normal_known_mean_posterior_predictive(mu, inverse_gamma) @@ -2378,7 +2366,6 @@

    poisson_gamma(x_total, n, gamma_prior) @@ -2415,7 +2402,6 @@

    -

    poisson_gamma_posterior_predictive(gamma, n=1) diff --git a/search/search_index.json b/search/search_index.json index 0182309..0d1db0a 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Conjugate Models","text":"

    Bayesian conjugate models in Python

    "},{"location":"#installation","title":"Installation","text":"
    pip install conjugate-models\n
    "},{"location":"#basic-usage","title":"Basic Usage","text":"
    from conjugate.distributions import Beta, BetaBinomial\nfrom conjugate.models import binomial_beta, binomial_beta_posterior_predictive\n\n# Observed Data\nX = 4\nN = 10\n\n# Analytics\nprior = Beta(1, 1)\nprior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=prior)\n\nposterior: Beta = binomial_beta(n=N, x=X, beta_prior=prior)\nposterior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=posterior) \n\n# Figure\nimport matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(ncols=2)\n\nax = axes[0]\nax = posterior.plot_pdf(ax=ax, label=\"posterior\")\nprior.plot_pdf(ax=ax, label=\"prior\")\nax.axvline(x=X/N, color=\"black\", ymax=0.05, label=\"MLE\")\nax.set_title(\"Success Rate\")\nax.legend()\n\nax = axes[1]\nposterior_predictive.plot_pmf(ax=ax, label=\"posterior predictive\")\nprior_predictive.plot_pmf(ax=ax, label=\"prior predictive\")\nax.axvline(x=X, color=\"black\", ymax=0.05, label=\"Sample\")\nax.set_title(\"Number of Successes\")\nax.legend()\nplt.show()\n
    "},{"location":"#features","title":"Features","text":"
    • Connection to Scipy Distributions with dist attribute
    • Built in Plotting with plot_pdf and plot_pmf methods
    • Vectorized Operations for parameters and data
    • Indexing Parameters for subsetting and slicing
    • Generalized Numerical Inputs for inputs other than builtins and numpy arrays
    • Unsupported Distributions for sampling from unsupported distributions
    "},{"location":"#too-simple","title":"Too Simple?","text":"

    Simple model, sure. Useful model, potentially.

    Constant probability of success, p, for n trials.

    rng = np.random.default_rng(42)\n\n# Observed Data\nn_times = 75\np = np.repeat(0.5, n_times)\nsamples = rng.binomial(n=1, p=p, size=n_times)\n\n# Model\nn = np.arange(n_times) + 1\nprior = Beta(alpha=1, beta=1)\nposterior = binomial_beta(n=n, x=samples.cumsum(), beta_prior=prior)\n\n# Figure\nplt.plot(n, p, color=\"black\", label=\"true p\", linestyle=\"--\")\nplt.scatter(n, samples, color=\"black\", label=\"observed samples\")\nplt.plot(n, posterior.dist.mean(), color=\"red\", label=\"posterior mean\")\n# fill between the 95% credible interval\nplt.fill_between(\n    n, \n    posterior.dist.ppf(0.025),\n    posterior.dist.ppf(0.975),\n    color=\"red\",\n    alpha=0.2,\n    label=\"95% credible interval\",\n)\npadding = 0.025\nplt.ylim(0 - padding, 1 + padding)\nplt.xlim(1, n_times)\nplt.legend(loc=\"best\")\nplt.xlabel(\"Number of trials\")\nplt.ylabel(\"Probability\")\nplt.show()\n

    Even with a moving probability, this simple to implement model can be useful.

    ...\n\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x))\n\np_raw = rng.normal(loc=0, scale=0.2, size=n_times).cumsum()\np = sigmoid(p_raw)\n\n...\n

    "},{"location":"#resources","title":"Resources","text":"
    • Conjugate Priors
    "},{"location":"distributions/","title":"Distributions","text":"

    These are the supported distributions based on the conjugate models.

    Many have the dist attribute which is a scipy.stats distribution object. From there, you can use the methods from scipy.stats to get the pdf, cdf, etc.

    Distributions can be plotted using the plot_pmf or plot_pdf methods of the distribution.

    from conjugate.distribution import Beta \n\nbeta = Beta(1, 1)\nscipy_dist = beta.dist \n\nprint(scipy_dist.mean())\n# 0.5\nprint(scipy_dist.ppf([0.025, 0.975]))\n# [0.025 0.975]\n\nsamples = scipy_dist.rvs(100)\n\nbeta.plot_pmf(label=\"beta distribution\")\n

    Distributions like Poisson can be added with other Poissons or multiplied by numerical values in order to scale rate. For instance,

    daily_rate = 0.25\ndaily_pois = Poisson(lam=daily_rate)\n\ntwo_day_pois = daily_pois + daily_pois\nweekly_pois = 7 * daily_pois\n

    Below are the currently supported distributions

    "},{"location":"distributions/#conjugate.distributions.Beta","title":"Beta dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Beta distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required beta NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Beta(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Beta distribution.\n\n    Args:\n        alpha: shape parameter\n        beta: shape parameter\n\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    def __post_init__(self) -> None:\n        self.max_value = 1.0\n\n    @classmethod\n    def from_mean(cls, mean: float, alpha: float) -> \"Beta\":\n        \"\"\"Alternative constructor from mean and alpha.\"\"\"\n        beta = get_beta_param_from_mean_and_alpha(mean=mean, alpha=alpha)\n        return cls(alpha=alpha, beta=beta)\n\n    @classmethod\n    def from_successes_and_failures(cls, successes: int, failures: int) -> \"Beta\":\n        \"\"\"Alternative constructor based on hyperparameter interpretation.\"\"\"\n        alpha = successes + 1\n        beta = failures + 1\n        return cls(alpha=alpha, beta=beta)\n\n    @property\n    def dist(self):\n        return stats.beta(self.alpha, self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.Beta.from_mean","title":"from_mean(mean, alpha) classmethod","text":"

    Alternative constructor from mean and alpha.

    Source code in conjugate/distributions.py
    @classmethod\ndef from_mean(cls, mean: float, alpha: float) -> \"Beta\":\n    \"\"\"Alternative constructor from mean and alpha.\"\"\"\n    beta = get_beta_param_from_mean_and_alpha(mean=mean, alpha=alpha)\n    return cls(alpha=alpha, beta=beta)\n
    "},{"location":"distributions/#conjugate.distributions.Beta.from_successes_and_failures","title":"from_successes_and_failures(successes, failures) classmethod","text":"

    Alternative constructor based on hyperparameter interpretation.

    Source code in conjugate/distributions.py
    @classmethod\ndef from_successes_and_failures(cls, successes: int, failures: int) -> \"Beta\":\n    \"\"\"Alternative constructor based on hyperparameter interpretation.\"\"\"\n    alpha = successes + 1\n    beta = failures + 1\n    return cls(alpha=alpha, beta=beta)\n
    "},{"location":"distributions/#conjugate.distributions.BetaBinomial","title":"BetaBinomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Beta binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required alpha NUMERIC

    shape parameter

    required beta NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass BetaBinomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Beta binomial distribution.\n\n    Args:\n        n: number of trials\n        alpha: shape parameter\n        beta: shape parameter\n\n    \"\"\"\n\n    n: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    def __post_init__(self):\n        if isinstance(self.n, np.ndarray):\n            self.max_value = self.n.max()\n        else:\n            self.max_value = self.n\n\n    @property\n    def dist(self):\n        return stats.betabinom(self.n, self.alpha, self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.BetaNegativeBinomial","title":"BetaNegativeBinomial dataclass","text":"

    Bases: SliceMixin

    Beta negative binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of successes

    required alpha NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass BetaNegativeBinomial(SliceMixin):\n    \"\"\"Beta negative binomial distribution.\n\n    Args:\n        n: number of successes\n        alpha: shape parameter\n\n    \"\"\"\n\n    n: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n
    "},{"location":"distributions/#conjugate.distributions.Binomial","title":"Binomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass Binomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Binomial distribution.\n\n    Args:\n        n: number of trials\n        p: probability of success\n\n    \"\"\"\n\n    n: NUMERIC\n    p: NUMERIC\n\n    def __post_init__(self):\n        if isinstance(self.n, np.ndarray):\n            self.max_value = self.n.max()\n        else:\n            self.max_value = self.n\n\n    @property\n    def dist(self):\n        return stats.binom(n=self.n, p=self.p)\n
    "},{"location":"distributions/#conjugate.distributions.Dirichlet","title":"Dirichlet dataclass","text":"

    Bases: DirichletPlotDistMixin

    Dirichlet distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Dirichlet(DirichletPlotDistMixin):\n    \"\"\"Dirichlet distribution.\n\n    Args:\n        alpha: shape parameter\n\n    \"\"\"\n\n    alpha: NUMERIC\n\n    def __post_init__(self) -> None:\n        self.max_value = 1.0\n\n    @property\n    def dist(self):\n        if self.alpha.ndim == 1:\n            return stats.dirichlet(self.alpha)\n\n        return VectorizedDist(self.alpha, dist=stats.dirichlet)\n
    "},{"location":"distributions/#conjugate.distributions.Exponential","title":"Exponential dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Exponential distribution.

    Parameters:

    Name Type Description Default lam NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Exponential(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Exponential distribution.\n\n    Args:\n        lam: rate parameter\n\n    \"\"\"\n\n    lam: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.expon(scale=self.lam)\n\n    def __mul__(self, other):\n        return Gamma(alpha=other, beta=1 / self.lam)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Gamma","title":"Gamma dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Gamma distribution.

    Gamma Distribution Scipy Docmentation

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required beta NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Gamma(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Gamma distribution.\n\n    <a href=https://en.wikipedia.org/wiki/Gamma_distribution>Gamma Distribution</a>\n    <a href=https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gamma.html>Scipy Docmentation</a>\n\n    Args:\n        alpha: shape parameter\n        beta: rate parameter\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.gamma(a=self.alpha, scale=1 / self.beta)\n\n    def __mul__(self, other):\n        return Gamma(alpha=self.alpha * other, beta=self.beta)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Geometric","title":"Geometric dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Geometric distribution.

    Parameters:

    Name Type Description Default p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass Geometric(DiscretePlotMixin, SliceMixin):\n    \"\"\"Geometric distribution.\n\n    Args:\n        p: probability of success\n\n    \"\"\"\n\n    p: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.geom(self.p)\n
    "},{"location":"distributions/#conjugate.distributions.InverseGamma","title":"InverseGamma dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    InverseGamma distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape

    required beta NUMERIC

    scale

    required Source code in conjugate/distributions.py
    @dataclass\nclass InverseGamma(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"InverseGamma distribution.\n\n    Args:\n        alpha: shape\n        beta: scale\n\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.invgamma(a=self.alpha, scale=self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.NegativeBinomial","title":"NegativeBinomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Negative binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of successes

    required p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass NegativeBinomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Negative binomial distribution.\n\n    Args:\n        n: number of successes\n        p: probability of success\n\n    \"\"\"\n\n    n: NUMERIC\n    p: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.nbinom(n=self.n, p=self.p)\n\n    def __mul__(self, other):\n        return NegativeBinomial(n=self.n * other, p=self.p)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Normal","title":"Normal dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Normal distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required sigma NUMERIC

    standard deviation

    required Source code in conjugate/distributions.py
    @dataclass\nclass Normal(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Normal distribution.\n\n    Args:\n        mu: mean\n        sigma: standard deviation\n\n    \"\"\"\n\n    mu: NUMERIC\n    sigma: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.norm(self.mu, self.sigma)\n\n    def __mul__(self, other):\n        sigma = ((self.sigma**2) * other) ** 0.5\n        return Normal(mu=self.mu * other, sigma=sigma)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma","title":"NormalInverseGamma dataclass","text":"

    Normal inverse gamma distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required delta_inverse NUMERIC

    covariance matrix

    required alpha NUMERIC

    shape

    required beta NUMERIC

    scale

    required Source code in conjugate/distributions.py
    @dataclass\nclass NormalInverseGamma:\n    \"\"\"Normal inverse gamma distribution.\n\n    Args:\n        mu: mean\n        delta_inverse: covariance matrix\n        alpha: shape\n        beta: scale\n\n    \"\"\"\n\n    mu: NUMERIC\n    delta_inverse: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @classmethod\n    def from_inverse_gamma(\n        cls, mu: NUMERIC, delta_inverse: NUMERIC, inverse_gamma: InverseGamma\n    ) -> \"NormalInverseGamma\":\n        return cls(\n            mu=mu,\n            delta_inverse=delta_inverse,\n            alpha=inverse_gamma.alpha,\n            beta=inverse_gamma.beta,\n        )\n\n    @property\n    def inverse_gamma(self) -> InverseGamma:\n        return InverseGamma(alpha=self.alpha, beta=self.beta)\n\n    def sample_variance(self, size: int, random_state=None) -> NUMERIC:\n        \"\"\"Sample variance from the inverse gamma distribution.\n\n        Args:\n            size: number of samples\n            random_state: random state\n\n        Returns:\n            samples from the inverse gamma distribution\n\n        \"\"\"\n        return self.inverse_gamma.dist.rvs(size=size, random_state=random_state)\n\n    def sample_beta(\n        self, size: int, return_variance: bool = False, random_state=None\n    ) -> Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]:\n        \"\"\"Sample beta from the normal distribution.\n\n        Args:\n            size: number of samples\n            return_variance: whether to return variance as well\n            random_state: random state\n\n        Returns:\n            samples from the normal distribution and optionally variance\n\n        \"\"\"\n        variance = self.sample_variance(size=size, random_state=random_state)\n\n        beta = np.stack(\n            [\n                stats.multivariate_normal(self.mu, v * self.delta_inverse).rvs(\n                    size=1, random_state=random_state\n                )\n                for v in variance\n            ]\n        )\n\n        if return_variance:\n            return beta, variance\n\n        return beta\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma.sample_beta","title":"sample_beta(size, return_variance=False, random_state=None)","text":"

    Sample beta from the normal distribution.

    Parameters:

    Name Type Description Default size int

    number of samples

    required return_variance bool

    whether to return variance as well

    False random_state

    random state

    None

    Returns:

    Type Description Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]

    samples from the normal distribution and optionally variance

    Source code in conjugate/distributions.py
    def sample_beta(\n    self, size: int, return_variance: bool = False, random_state=None\n) -> Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]:\n    \"\"\"Sample beta from the normal distribution.\n\n    Args:\n        size: number of samples\n        return_variance: whether to return variance as well\n        random_state: random state\n\n    Returns:\n        samples from the normal distribution and optionally variance\n\n    \"\"\"\n    variance = self.sample_variance(size=size, random_state=random_state)\n\n    beta = np.stack(\n        [\n            stats.multivariate_normal(self.mu, v * self.delta_inverse).rvs(\n                size=1, random_state=random_state\n            )\n            for v in variance\n        ]\n    )\n\n    if return_variance:\n        return beta, variance\n\n    return beta\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma.sample_variance","title":"sample_variance(size, random_state=None)","text":"

    Sample variance from the inverse gamma distribution.

    Parameters:

    Name Type Description Default size int

    number of samples

    required random_state

    random state

    None

    Returns:

    Type Description NUMERIC

    samples from the inverse gamma distribution

    Source code in conjugate/distributions.py
    def sample_variance(self, size: int, random_state=None) -> NUMERIC:\n    \"\"\"Sample variance from the inverse gamma distribution.\n\n    Args:\n        size: number of samples\n        random_state: random state\n\n    Returns:\n        samples from the inverse gamma distribution\n\n    \"\"\"\n    return self.inverse_gamma.dist.rvs(size=size, random_state=random_state)\n
    "},{"location":"distributions/#conjugate.distributions.Poisson","title":"Poisson dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Poisson distribution.

    Parameters:

    Name Type Description Default lam NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Poisson(DiscretePlotMixin, SliceMixin):\n    \"\"\"Poisson distribution.\n\n    Args:\n        lam: rate parameter\n\n    \"\"\"\n\n    lam: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.poisson(self.lam)\n\n    def __mul__(self, other) -> \"Poisson\":\n        return Poisson(lam=self.lam * other)\n\n    __rmul__ = __mul__\n\n    def __add__(self, other) -> \"Poisson\":\n        return Poisson(self.lam + other.lam)\n\n    __radd__ = __add__\n
    "},{"location":"distributions/#conjugate.distributions.StudentT","title":"StudentT dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    StudentT distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required sigma NUMERIC

    standard deviation

    required nu NUMERIC

    degrees of freedom

    required Source code in conjugate/distributions.py
    @dataclass\nclass StudentT(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"StudentT distribution.\n\n    Args:\n        mu: mean\n        sigma: standard deviation\n        nu: degrees of freedom\n\n    \"\"\"\n\n    mu: NUMERIC\n    sigma: NUMERIC\n    nu: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.t(self.nu, self.mu, self.sigma)\n
    "},{"location":"distributions/#conjugate.distributions.Uniform","title":"Uniform dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Uniform distribution.

    Parameters:

    Name Type Description Default low NUMERIC

    lower bound

    required high NUMERIC

    upper bound

    required Source code in conjugate/distributions.py
    @dataclass\nclass Uniform(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Uniform distribution.\n\n    Args:\n        low: lower bound\n        high: upper bound\n\n    \"\"\"\n\n    low: NUMERIC\n    high: NUMERIC\n\n    def __post_init__(self):\n        self.min_value = self.low\n        self.max_value = self.high\n\n    @property\n    def dist(self):\n        return stats.uniform(self.low, self.high)\n
    "},{"location":"mixins/","title":"Mixins","text":"

    Two sets of mixins to support the plotting and slicing of the distribution parameters

    "},{"location":"mixins/#conjugate.plot.ContinuousPlotDistMixin","title":"ContinuousPlotDistMixin","text":"

    Bases: PlotDistMixin

    Functionality for plot_pdf method of continuous distributions.

    Source code in conjugate/plot.py
    class ContinuousPlotDistMixin(PlotDistMixin):\n    \"\"\"Functionality for plot_pdf method of continuous distributions.\"\"\"\n\n    def plot_pdf(self, ax: Optional[plt.Axes] = None, **kwargs) -> plt.Axes:\n        \"\"\"Plot the pdf of distribution\n\n        Args:\n            ax: matplotlib Axes, optional\n            **kwargs: Additonal kwargs to pass to matplotlib\n\n        Returns:\n            new or modified Axes\n\n        \"\"\"\n        ax = self._settle_axis(ax=ax)\n\n        x = self._create_x_values()\n        x = self._reshape_x_values(x)\n\n        return self._create_plot_on_axis(x, ax, **kwargs)\n\n    def _create_x_values(self) -> np.ndarray:\n        return np.linspace(self.min_value, self.max_value, 100)\n\n    def _setup_labels(self, ax) -> None:\n        ax.set_xlabel(\"Domain\")\n        ax.set_ylabel(\"Density $f(x)$\")\n\n    def _create_plot_on_axis(self, x, ax, **kwargs) -> plt.Axes:\n        yy = self.dist.pdf(x)\n        if \"label\" in kwargs:\n            label = kwargs.pop(\"label\")\n            label = resolve_label(label, yy)\n        else:\n            label = None\n\n        ax.plot(x, yy, label=label, **kwargs)\n        self._setup_labels(ax=ax)\n        ax.set_ylim(0, None)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.ContinuousPlotDistMixin.plot_pdf","title":"plot_pdf(ax=None, **kwargs)","text":"

    Plot the pdf of distribution

    Parameters:

    Name Type Description Default ax Optional[Axes]

    matplotlib Axes, optional

    None **kwargs

    Additonal kwargs to pass to matplotlib

    {}

    Returns:

    Type Description Axes

    new or modified Axes

    Source code in conjugate/plot.py
    def plot_pdf(self, ax: Optional[plt.Axes] = None, **kwargs) -> plt.Axes:\n    \"\"\"Plot the pdf of distribution\n\n    Args:\n        ax: matplotlib Axes, optional\n        **kwargs: Additonal kwargs to pass to matplotlib\n\n    Returns:\n        new or modified Axes\n\n    \"\"\"\n    ax = self._settle_axis(ax=ax)\n\n    x = self._create_x_values()\n    x = self._reshape_x_values(x)\n\n    return self._create_plot_on_axis(x, ax, **kwargs)\n
    "},{"location":"mixins/#conjugate.plot.DirichletPlotDistMixin","title":"DirichletPlotDistMixin","text":"

    Bases: ContinuousPlotDistMixin

    Plot the pdf using samples from the dirichlet distribution.

    Source code in conjugate/plot.py
    class DirichletPlotDistMixin(ContinuousPlotDistMixin):\n    \"\"\"Plot the pdf using samples from the dirichlet distribution.\"\"\"\n\n    def plot_pdf(\n        self, ax: Optional[plt.Axes] = None, samples: int = 1_000, **kwargs\n    ) -> plt.Axes:\n        \"\"\"Plots the pdf\"\"\"\n        distribution_samples = self.dist.rvs(size=samples)\n\n        ax = self._settle_axis(ax=ax)\n        xx = self._create_x_values()\n\n        for x in distribution_samples.T:\n            kde = gaussian_kde(x)\n\n            yy = kde(xx)\n\n            ax.plot(xx, yy, **kwargs)\n\n        self._setup_labels(ax=ax)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.DirichletPlotDistMixin.plot_pdf","title":"plot_pdf(ax=None, samples=1000, **kwargs)","text":"

    Plots the pdf

    Source code in conjugate/plot.py
    def plot_pdf(\n    self, ax: Optional[plt.Axes] = None, samples: int = 1_000, **kwargs\n) -> plt.Axes:\n    \"\"\"Plots the pdf\"\"\"\n    distribution_samples = self.dist.rvs(size=samples)\n\n    ax = self._settle_axis(ax=ax)\n    xx = self._create_x_values()\n\n    for x in distribution_samples.T:\n        kde = gaussian_kde(x)\n\n        yy = kde(xx)\n\n        ax.plot(xx, yy, **kwargs)\n\n    self._setup_labels(ax=ax)\n    return ax\n
    "},{"location":"mixins/#conjugate.plot.DiscretePlotMixin","title":"DiscretePlotMixin","text":"

    Bases: PlotDistMixin

    Adding the plot_pmf method to class.

    Source code in conjugate/plot.py
    class DiscretePlotMixin(PlotDistMixin):\n    \"\"\"Adding the plot_pmf method to class.\"\"\"\n\n    def plot_pmf(\n        self, ax: Optional[plt.Axes] = None, mark: str = \"o-\", **kwargs\n    ) -> plt.Axes:\n        ax = self._settle_axis(ax=ax)\n\n        x = self._create_x_values()\n        x = self._reshape_x_values(x)\n\n        return self._create_plot_on_axis(x, ax, mark, **kwargs)\n\n    def _create_x_values(self) -> np.ndarray:\n        return np.arange(self.min_value, self.max_value + 1, 1)\n\n    def _create_plot_on_axis(\n        self, x, ax, mark, conditional: bool = False, **kwargs\n    ) -> plt.Axes:\n        yy = self.dist.pmf(x)\n        if conditional:\n            yy = yy / np.sum(yy)\n            ylabel = f\"Conditional Probability $f(x|{self.min_value} \\\\leq x \\\\leq {self.max_value})$\"\n        else:\n            ylabel = \"Probability $f(x)$\"\n\n        if \"label\" in kwargs:\n            label = kwargs.pop(\"label\")\n            label = resolve_label(label, yy)\n        else:\n            label = None\n\n        ax.plot(x, yy, mark, label=label, **kwargs)\n\n        if self.max_value - self.min_value < 15:\n            ax.set_xticks(x.ravel())\n        else:\n            ax.set_xticks(x.ravel(), minor=True)\n            ax.set_xticks(x[::5].ravel())\n\n        ax.set_xlabel(\"Domain\")\n        ax.set_ylabel(ylabel)\n        ax.set_ylim(0, None)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin","title":"PlotDistMixin","text":"

    Base mixin in order to support plotting. Requires the dist attribute of the scipy distribution.

    Source code in conjugate/plot.py
    class PlotDistMixin:\n    \"\"\"Base mixin in order to support plotting. Requires the dist attribute of the scipy distribution.\"\"\"\n\n    @property\n    def dist(self) -> Distribution:\n        raise NotImplementedError(\"Implement this property in the subclass.\")\n\n    @property\n    def max_value(self) -> float:\n        if not hasattr(self, \"_max_value\"):\n            raise ValueError(\"Set the max value before plotting.\")\n\n        return self._max_value\n\n    @max_value.setter\n    def max_value(self, value: float) -> None:\n        self._max_value = value\n\n    def set_max_value(self, value: float) -> \"PlotDistMixin\":\n        self.max_value = value\n\n        return self\n\n    @property\n    def min_value(self) -> float:\n        if not hasattr(self, \"_min_value\"):\n            self._min_value = 0.0\n\n        return self._min_value\n\n    @min_value.setter\n    def min_value(self, value: float) -> None:\n        self._min_value = value\n\n    def set_min_value(self, value: float) -> \"PlotDistMixin\":\n        \"\"\"Set the minimum value for plotting.\"\"\"\n        self.min_value = value\n\n        return self\n\n    def set_bounds(self, lower: float, upper: float) -> \"PlotDistMixin\":\n        \"\"\"Set both the min and max values for plotting.\"\"\"\n        return self.set_min_value(lower).set_max_value(upper)\n\n    def _reshape_x_values(self, x: np.ndarray) -> np.ndarray:\n        \"\"\"Make sure that the values are ready for plotting.\"\"\"\n        for value in asdict(self).values():\n            if not isinstance(value, float):\n                return x[:, None]\n\n        return x\n\n    def _settle_axis(self, ax: Optional[plt.Axes] = None) -> plt.Axes:\n        return ax if ax is not None else plt.gca()\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin.set_bounds","title":"set_bounds(lower, upper)","text":"

    Set both the min and max values for plotting.

    Source code in conjugate/plot.py
    def set_bounds(self, lower: float, upper: float) -> \"PlotDistMixin\":\n    \"\"\"Set both the min and max values for plotting.\"\"\"\n    return self.set_min_value(lower).set_max_value(upper)\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin.set_min_value","title":"set_min_value(value)","text":"

    Set the minimum value for plotting.

    Source code in conjugate/plot.py
    def set_min_value(self, value: float) -> \"PlotDistMixin\":\n    \"\"\"Set the minimum value for plotting.\"\"\"\n    self.min_value = value\n\n    return self\n
    "},{"location":"mixins/#conjugate.plot.resolve_label","title":"resolve_label(label, yy)","text":"

    https://stackoverflow.com/questions/73662931/matplotlib-plot-a-numpy-array-as-many-lines-with-a-single-label

    Source code in conjugate/plot.py
    def resolve_label(label: LABEL_INPUT, yy: np.ndarray):\n    \"\"\"\n\n    https://stackoverflow.com/questions/73662931/matplotlib-plot-a-numpy-array-as-many-lines-with-a-single-label\n    \"\"\"\n    if yy.ndim == 1:\n        return label\n\n    ncols = yy.shape[1]\n    if ncols != 1:\n        if isinstance(label, str):\n            return [f\"{label} {i}\" for i in range(1, ncols + 1)]\n\n        if callable(label):\n            return [label(i) for i in range(ncols)]\n\n        if isinstance(label, Iterable):\n            return label\n\n        raise ValueError(\"Label must be a string, iterable, or callable.\")\n\n    return label\n
    "},{"location":"mixins/#conjugate.slice.SliceMixin","title":"SliceMixin","text":"

    Mixin in order to slice the parameters

    Source code in conjugate/slice.py
    class SliceMixin:\n    \"\"\"Mixin in order to slice the parameters\"\"\"\n\n    def __getitem__(self, key):\n        params = asdict(self)\n\n        def slice(value, key):\n            try:\n                return value[key]\n            except Exception:\n                return value\n\n        new_params = {k: slice(value=v, key=key) for k, v in params.items()}\n\n        return self.__class__(**new_params)\n
    "},{"location":"models/","title":"Models","text":"

    For more on these models, check out the Conjugate Prior Wikipedia Table

    Below are the supported models

    "},{"location":"models/#conjugate.models.binomial_beta","title":"binomial_beta(n, x, beta_prior)","text":"

    Posterior distribution for a binomial likelihood with a beta prior.

    Parameters:

    Name Type Description Default n NUMERIC

    total number of trials

    required x NUMERIC

    sucesses from that trials

    required beta_prior Beta

    Beta distribution prior

    required

    Returns:

    Type Description Beta

    Beta distribution posterior

    Source code in conjugate/models.py
    def binomial_beta(n: NUMERIC, x: NUMERIC, beta_prior: Beta) -> Beta:\n    \"\"\"Posterior distribution for a binomial likelihood with a beta prior.\n\n    Args:\n        n: total number of trials\n        x: sucesses from that trials\n        beta_prior: Beta distribution prior\n\n    Returns:\n        Beta distribution posterior\n\n    \"\"\"\n    alpha_post, beta_post = get_binomial_beta_posterior_params(\n        beta_prior.alpha, beta_prior.beta, n, x\n    )\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.binomial_beta_posterior_predictive","title":"binomial_beta_posterior_predictive(n, beta)","text":"

    Posterior predictive distribution for a binomial likelihood with a beta prior.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required beta Beta

    Beta distribution

    required

    Returns:

    Type Description BetaBinomial

    BetaBinomial posterior predictive distribution

    Source code in conjugate/models.py
    def binomial_beta_posterior_predictive(n: NUMERIC, beta: Beta) -> BetaBinomial:\n    \"\"\"Posterior predictive distribution for a binomial likelihood with a beta prior.\n\n    Args:\n        n: number of trials\n        beta: Beta distribution\n\n    Returns:\n        BetaBinomial posterior predictive distribution\n\n    \"\"\"\n    return BetaBinomial(n=n, alpha=beta.alpha, beta=beta.beta)\n
    "},{"location":"models/#conjugate.models.categorical_dirichlet","title":"categorical_dirichlet(x, dirichlet_prior)","text":"

    Posterior distribution of Categorical model with Dirichlet prior.

    Source code in conjugate/models.py
    def categorical_dirichlet(x: NUMERIC, dirichlet_prior: Dirichlet) -> Dirichlet:\n    \"\"\"Posterior distribution of Categorical model with Dirichlet prior.\"\"\"\n    alpha_post = get_dirichlet_posterior_params(dirichlet_prior.alpha, x)\n\n    return Dirichlet(alpha=alpha_post)\n
    "},{"location":"models/#conjugate.models.exponetial_gamma","title":"exponetial_gamma(x_total, n, gamma_prior)","text":"

    Posterior distribution for an exponential likelihood with a gamma prior

    Source code in conjugate/models.py
    def exponetial_gamma(x_total: NUMERIC, n: NUMERIC, gamma_prior: Gamma) -> Gamma:\n    \"\"\"Posterior distribution for an exponential likelihood with a gamma prior\"\"\"\n    alpha_post, beta_post = get_exponential_gamma_posterior_params(\n        alpha=gamma_prior.alpha, beta=gamma_prior.beta, x_total=x_total, n=n\n    )\n\n    return Gamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.geometric_beta","title":"geometric_beta(x_total, n, beta_prior, one_start=True)","text":"

    Posterior distribution for a geometric likelihood with a beta prior.

    Parameters:

    Name Type Description Default x_total

    sum of all trials outcomes

    required n

    total number of trials

    required beta_prior Beta

    Beta distribution prior

    required one_start bool

    whether to outcomes start at 1, defaults to True. False is 0 start.

    True

    Returns:

    Type Description Beta

    Beta distribution posterior

    Source code in conjugate/models.py
    def geometric_beta(x_total, n, beta_prior: Beta, one_start: bool = True) -> Beta:\n    \"\"\"Posterior distribution for a geometric likelihood with a beta prior.\n\n    Args:\n        x_total: sum of all trials outcomes\n        n: total number of trials\n        beta_prior: Beta distribution prior\n        one_start: whether to outcomes start at 1, defaults to True. False is 0 start.\n\n    Returns:\n        Beta distribution posterior\n\n    \"\"\"\n    alpha_post = beta_prior.alpha + n\n    beta_post = beta_prior.beta + x_total\n\n    if one_start:\n        beta_post = beta_post - n\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.linear_regression","title":"linear_regression(X, y, normal_inverse_gamma_prior, inv=np.linalg.inv)","text":"

    Posterior distribution for a linear regression model with a normal inverse gamma prior.

    Derivation taken from this blog here.

    Parameters:

    Name Type Description Default X NUMERIC

    design matrix

    required y NUMERIC

    response vector

    required normal_inverse_gamma_prior NormalInverseGamma

    NormalInverseGamma prior

    required inv

    function to invert matrix, defaults to np.linalg.inv

    inv

    Returns:

    Type Description NormalInverseGamma

    NormalInverseGamma posterior distribution

    Source code in conjugate/models.py
    def linear_regression(\n    X: NUMERIC,\n    y: NUMERIC,\n    normal_inverse_gamma_prior: NormalInverseGamma,\n    inv=np.linalg.inv,\n) -> NormalInverseGamma:\n    \"\"\"Posterior distribution for a linear regression model with a normal inverse gamma prior.\n\n    Derivation taken from this blog [here](https://gregorygundersen.com/blog/2020/02/04/bayesian-linear-regression/).\n\n    Args:\n        X: design matrix\n        y: response vector\n        normal_inverse_gamma_prior: NormalInverseGamma prior\n        inv: function to invert matrix, defaults to np.linalg.inv\n\n    Returns:\n        NormalInverseGamma posterior distribution\n\n    \"\"\"\n    N = X.shape[0]\n\n    delta = inv(normal_inverse_gamma_prior.delta_inverse)\n\n    delta_post = (X.T @ X) + delta\n    delta_post_inverse = inv(delta_post)\n\n    mu_post = (\n        # (B, B)\n        delta_post_inverse\n        # (B, 1)\n        # (B, B) * (B, 1) +  (B, N) * (N, 1)\n        @ (delta @ normal_inverse_gamma_prior.mu + X.T @ y)\n    )\n\n    alpha_post = normal_inverse_gamma_prior.alpha + (0.5 * N)\n    beta_post = normal_inverse_gamma_prior.beta + (\n        0.5\n        * (\n            (y.T @ y)\n            # (1, B) * (B, B) * (B, 1)\n            + (normal_inverse_gamma_prior.mu.T @ delta @ normal_inverse_gamma_prior.mu)\n            # (1, B) * (B, B) * (B, 1)\n            - (mu_post.T @ delta_post @ mu_post)\n        )\n    )\n\n    return NormalInverseGamma(\n        mu=mu_post, delta_inverse=delta_post_inverse, alpha=alpha_post, beta=beta_post\n    )\n
    "},{"location":"models/#conjugate.models.linear_regression_posterior_predictive","title":"linear_regression_posterior_predictive(normal_inverse_gamma, X, eye=np.eye)","text":"

    Posterior predictive distribution for a linear regression model with a normal inverse gamma prior.

    Source code in conjugate/models.py
    def linear_regression_posterior_predictive(\n    normal_inverse_gamma: NormalInverseGamma, X: NUMERIC, eye=np.eye\n) -> MultivariateStudentT:\n    \"\"\"Posterior predictive distribution for a linear regression model with a normal inverse gamma prior.\"\"\"\n    mu = X @ normal_inverse_gamma.mu\n    sigma = (normal_inverse_gamma.beta / normal_inverse_gamma.alpha) * (\n        eye(X.shape[0]) + (X @ normal_inverse_gamma.delta_inverse @ X.T)\n    )\n    nu = 2 * normal_inverse_gamma.alpha\n\n    return MultivariateStudentT(\n        mu=mu,\n        sigma=sigma,\n        nu=nu,\n    )\n
    "},{"location":"models/#conjugate.models.multinomial_dirichlet","title":"multinomial_dirichlet(x, dirichlet_prior)","text":"

    Posterior distribution of Multinomial model with Dirichlet prior.

    Parameters:

    Name Type Description Default x NUMERIC

    counts

    required dirichlet_prior Dirichlet

    Dirichlet prior on the counts

    required

    Returns:

    Type Description Dirichlet

    Dirichlet posterior distribution

    Source code in conjugate/models.py
    def multinomial_dirichlet(x: NUMERIC, dirichlet_prior: Dirichlet) -> Dirichlet:\n    \"\"\"Posterior distribution of Multinomial model with Dirichlet prior.\n\n    Args:\n        x: counts\n        dirichlet_prior: Dirichlet prior on the counts\n\n    Returns:\n        Dirichlet posterior distribution\n\n    \"\"\"\n    alpha_post = get_dirichlet_posterior_params(dirichlet_prior.alpha, x)\n\n    return Dirichlet(alpha=alpha_post)\n
    "},{"location":"models/#conjugate.models.negative_binomial_beta","title":"negative_binomial_beta(r, n, x, beta_prior)","text":"

    Posterior distribution for a negative binomial likelihood with a beta prior.

    Args:

    Source code in conjugate/models.py
    def negative_binomial_beta(r, n, x, beta_prior: Beta) -> Beta:\n    \"\"\"Posterior distribution for a negative binomial likelihood with a beta prior.\n\n    Args:\n\n    \"\"\"\n    alpha_post = beta_prior.alpha + (r * n)\n    beta_post = beta_prior.beta + x\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.negative_binomial_beta_posterior_predictive","title":"negative_binomial_beta_posterior_predictive(r, beta)","text":"

    Posterior predictive distribution for a negative binomial likelihood with a beta prior

    Source code in conjugate/models.py
    def negative_binomial_beta_posterior_predictive(r, beta: Beta) -> BetaNegativeBinomial:\n    \"\"\"Posterior predictive distribution for a negative binomial likelihood with a beta prior\"\"\"\n    return BetaNegativeBinomial(r=r, alpha=beta.alpha, beta=beta.beta)\n
    "},{"location":"models/#conjugate.models.normal_known_mean","title":"normal_known_mean(x_total, x2_total, n, mu, inverse_gamma_prior)","text":"

    Posterior distribution for a normal likelihood with a known mean and a variance prior.

    Parameters:

    Name Type Description Default x_total NUMERIC

    sum of all outcomes

    required x2_total NUMERIC

    sum of all outcomes squared

    required n NUMERIC

    total number of samples in x_total

    required mu NUMERIC

    known mean

    required inverse_gamma_prior InverseGamma

    InverseGamma prior for variance

    required

    Returns:

    Type Description InverseGamma

    InverseGamma posterior distribution for the variance

    Source code in conjugate/models.py
    def normal_known_mean(\n    x_total: NUMERIC,\n    x2_total: NUMERIC,\n    n: NUMERIC,\n    mu: NUMERIC,\n    inverse_gamma_prior: InverseGamma,\n) -> InverseGamma:\n    \"\"\"Posterior distribution for a normal likelihood with a known mean and a variance prior.\n\n    Args:\n        x_total: sum of all outcomes\n        x2_total: sum of all outcomes squared\n        n: total number of samples in x_total\n        mu: known mean\n        inverse_gamma_prior: InverseGamma prior for variance\n\n    Returns:\n        InverseGamma posterior distribution for the variance\n\n    \"\"\"\n    alpha_post = inverse_gamma_prior.alpha + (n / 2)\n    beta_post = inverse_gamma_prior.beta + (\n        0.5 * (x2_total - (2 * mu * x_total) + (n * (mu**2)))\n    )\n\n    return InverseGamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.normal_known_mean_posterior_predictive","title":"normal_known_mean_posterior_predictive(mu, inverse_gamma)","text":"

    Posterior predictive distribution for a normal likelihood with a known mean and a variance prior.

    Parameters:

    Name Type Description Default mu NUMERIC

    known mean

    required inverse_gamma InverseGamma

    InverseGamma prior

    required

    Returns:

    Type Description StudentT

    StudentT posterior predictive distribution

    Source code in conjugate/models.py
    def normal_known_mean_posterior_predictive(\n    mu: NUMERIC, inverse_gamma: InverseGamma\n) -> StudentT:\n    \"\"\"Posterior predictive distribution for a normal likelihood with a known mean and a variance prior.\n\n    Args:\n        mu: known mean\n        inverse_gamma: InverseGamma prior\n\n    Returns:\n        StudentT posterior predictive distribution\n\n    \"\"\"\n    return StudentT(\n        n=2 * inverse_gamma.alpha,\n        mu=mu,\n        sigma=(inverse_gamma.beta / inverse_gamma.alpha) ** 0.5,\n    )\n
    "},{"location":"models/#conjugate.models.poisson_gamma","title":"poisson_gamma(x_total, n, gamma_prior)","text":"

    Posterior distribution for a poisson likelihood with a gamma prior

    Source code in conjugate/models.py
    def poisson_gamma(x_total: NUMERIC, n: NUMERIC, gamma_prior: Gamma) -> Gamma:\n    \"\"\"Posterior distribution for a poisson likelihood with a gamma prior\"\"\"\n    alpha_post, beta_post = get_poisson_gamma_posterior_params(\n        alpha=gamma_prior.alpha, beta=gamma_prior.beta, x_total=x_total, n=n\n    )\n\n    return Gamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.poisson_gamma_posterior_predictive","title":"poisson_gamma_posterior_predictive(gamma, n=1)","text":"

    Posterior predictive distribution for a poisson likelihood with a gamma prior

    Parameters:

    Name Type Description Default gamma Gamma

    Gamma distribution

    required n NUMERIC

    Number of trials for each sample, defaults to 1. Can be used to scale the distributions to a different unit of time.

    1

    Returns:

    Type Description NegativeBinomial

    NegativeBinomial distribution related to posterior predictive

    Source code in conjugate/models.py
    def poisson_gamma_posterior_predictive(\n    gamma: Gamma, n: NUMERIC = 1\n) -> NegativeBinomial:\n    \"\"\"Posterior predictive distribution for a poisson likelihood with a gamma prior\n\n    Args:\n        gamma: Gamma distribution\n        n: Number of trials for each sample, defaults to 1.\n            Can be used to scale the distributions to a different unit of time.\n\n    Returns:\n        NegativeBinomial distribution related to posterior predictive\n\n    \"\"\"\n    n = n * gamma.alpha\n    p = gamma.beta / (1 + gamma.beta)\n\n    return NegativeBinomial(n=n, p=p)\n
    "},{"location":"examples/bayesian-update/","title":"Bayesian Update","text":"

    Easy to use Bayesian inference incrementally by making the posterior the prior for the next update.

    import numpy as np\nimport matplotlib.pyplot as plt\n\nfrom conjugate.distributions import NormalInverseGamma\nfrom conjugate.models import linear_regression\n\ndef create_sampler(mu, sigma, rng): \n    \"\"\"Generate a sampler from a normal distribution with mean `mu` and standard deviation `sigma`.\"\"\"\n    def sample(n: int): \n        return rng.normal(loc=mu, scale=sigma, size=n)\n\n    return sample\n\n\nmu = 5.0\nsigma = 2.5\nrng = np.random.default_rng(0)\nsample = create_sampler(mu=mu, sigma=sigma, rng=rng)\n\n\nprior = NormalInverseGamma(\n    mu=np.array([0]), \n    delta_inverse=np.array([[1]]), \n    alpha=1, beta=1, \n)\n\n\ncumsum = 0\nbatch_sizes = [5, 10, 25]\nax = plt.gca()\nfor batch_size in batch_sizes:\n    y = sample(n=batch_size)\n    X = np.ones_like(y)[:, None]\n\n    posterior = linear_regression(X, y, prior)\n    beta_samples, variance_samples = posterior.sample_beta(size=1000, return_variance=True, random_state=rng)\n\n    cumsum += batch_size\n    label = f\"n={cumsum}\"\n    ax.scatter(variance_samples ** 0.5, beta_samples, alpha=0.25, label=label)\n\n    prior = posterior \n\nax.scatter(sigma, mu, color=\"black\", label=\"true\")\nax.set(\n    xlabel=\"$\\sigma$\", \n    ylabel=\"$\\mu$\", \n    xlim=(0, None), \n    ylim=(0, None), \n    title=\"Updated posterior samples of $\\mu$ and $\\sigma$\"\n)\nax.legend()\n\nplt.show()\n

    "},{"location":"examples/binomial/","title":"Binomial Model","text":"
    from conjugate.distributions import Beta, Binomial, BetaBinomial\nfrom conjugate.models import binomial_beta, binomial_beta_posterior_predictive\n\nimport matplotlib.pyplot as plt\n\nN = 10\ntrue_dist = Binomial(n=N, p=0.5)\n\n# Observed Data\nX = true_dist.dist.rvs(size=1, random_state=42)\n\n# Conjugate prior\nprior = Beta(alpha=1, beta=1)\nposterior: Beta = binomial_beta(n=N, x=X, beta_prior=prior)\n\n# Comparison\nprior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=prior)\nposterior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=posterior)\n\n# Figure \nfig, axes = plt.subplots(ncols=2, nrows=1, figsize=(8, 4))\n\nax: plt.Axes = axes[0]\nposterior.plot_pdf(ax=ax, label=\"posterior\")\nprior.plot_pdf(ax=ax, label=\"prior\")\nax.axvline(x=X/N, color=\"black\", ymax=0.05, label=\"MLE\")\nax.axvline(x=true_dist.p, color=\"black\", ymax=0.05, linestyle=\"--\", label=\"True\")\nax.set_title(\"Success Rate\")\nax.legend()\n\nax: plt.Axes = axes[1]\ntrue_dist.plot_pmf(ax=ax, label=\"true distribution\", color=\"C2\")\nposterior_predictive.plot_pmf(ax=ax, label=\"posterior predictive\")\nprior_predictive.plot_pmf(ax=ax, label=\"prior predictive\")\nax.axvline(x=X, color=\"black\", ymax=0.05, label=\"Sample\")\nax.set_title(\"Number of Successes\")\nax.legend()\n\nplt.show()\n
    "},{"location":"examples/generalized-inputs/","title":"Generalized Numerical Inputs","text":"

    Though the plotting is meant for numpy and python numbers, the conjugate models work with anything that works like numbers.

    For instance, Bayesian models in SQL using the SQL Builder, PyPika

    from pypika import Field \n\n# Columns from table in database\nN = Field(\"total\")\nX = Field(\"successes\")\n\n# Conjugate prior\nprior = Beta(alpha=1, beta=1)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\nprint(\"Posterior alpha:\", posterior.alpha)\nprint(\"Posterior beta:\", posterior.beta)\n# Posterior alpha: 1+\"successes\"\n# Posterior beta: 1+\"total\"-\"successes\"\n\n# Priors can be fields too\nalpha = Field(\"previous_successes\") - 1\nbeta = Field(\"previous_failures\") - 1\n\nprior = Beta(alpha=alpha, beta=beta)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\nprint(\"Posterior alpha:\", posterior.alpha)\nprint(\"Posterior beta:\", posterior.beta)\n# Posterior alpha: \"previous_successes\"-1+\"successes\"\n# Posterior beta: \"previous_failures\"-1+\"total\"-\"successes\"\n

    Using PyMC distributions for sampling with additional uncertainty

    import pymc as pm \n\nalpha = pm.Gamma.dist(alpha=1, beta=20)\nbeta = pm.Gamma.dist(alpha=1, beta=20)\n\n# Observed Data\nN = 10\nX = 4\n\n# Conjugate prior \nprior = Beta(alpha=alpha, beta=beta)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\n# Reconstruct the posterior distribution with PyMC\nprior_dist = pm.Beta.dist(alpha=prior.alpha, beta=prior.beta)\nposterior_dist = pm.Beta.dist(alpha=posterior.alpha, beta=posterior.beta)\n\nsamples = pm.draw([alpha, beta, prior_dist, posterior_dist], draws=1000)\n
    "},{"location":"examples/indexing/","title":"Indexing Parameters","text":"

    The distributions can be indexed for subsets.

    beta = np.arange(1, 10)\nprior = Beta(alpha=1, beta=beta)\n\nidx = [0, 5, -1]\nprior_subset = prior[idx]\nprior_subset.plot_pdf(label = lambda i: f\"prior {i}\")\nplt.legend()\nplt.show()\n

    "},{"location":"examples/linear-regression/","title":"Linear Regression","text":"

    We can fit linear regression that includes a predictive distribution for new data using a conjugate prior. This example only has one covariate, but the same approach can be used for multiple covariates.

    "},{"location":"examples/linear-regression/#simulate-data","title":"Simulate Data","text":"

    We are going to simulate data from a linear regression model. The true intercept is 3.5, the true slope is -2.0, and the true variance is 2.5.

    import numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\n\nfrom conjugate.distributions import NormalInverseGamma, MultivariateStudentT\nfrom conjugate.models import linear_regression, linear_regression_posterior_predictive\n\nintercept = 3.5\nslope = -2.0\nsigma = 2.5\n\nrng = np.random.default_rng(0)\n\nx_lim = 3\nn_points = 100\nx = np.linspace(-x_lim, x_lim, n_points)\ny = intercept + slope * x + rng.normal(scale=sigma, size=n_points)\n
    "},{"location":"examples/linear-regression/#define-prior-and-find-posterior","title":"Define Prior and Find Posterior","text":"

    There needs to be a prior for the intercept, slope, and the variance.

    prior = NormalInverseGamma(\n    mu=np.array([0, 0]),\n    delta_inverse=np.array([[1, 0], [0, 1]]),\n    alpha=1,\n    beta=1,\n)\n\ndef create_X(x: np.ndarray) -> np.ndarray:\n    return np.stack([np.ones_like(x), x]).T\n\nX = create_X(x)\nposterior: NormalInverseGamma = linear_regression(\n    X=X,\n    y=y,\n    normal_inverse_gamma_prior=prior,\n)\n
    "},{"location":"examples/linear-regression/#posterior-predictive-for-new-data","title":"Posterior Predictive for New Data","text":"

    The multivariate student-t distribution is used for the posterior predictive distribution. We have to draw samples from it since the scipy implementation does not have a ppf method.

    # New Data\nx_lim_new = 1.5 * x_lim\nx_new = np.linspace(-x_lim_new, x_lim_new, 20)\nX_new = create_X(x_new)\npp: MultivariateStudentT = linear_regression_posterior_predictive(normal_inverse_gamma=posterior, X=X_new)\n\nsamples = pp.dist.rvs(5_000).T\ndf_samples = pd.DataFrame(samples, index=x_new)\n
    "},{"location":"examples/linear-regression/#plot-results","title":"Plot Results","text":"

    We can see that the posterior predictive distribution begins to widen as we move away from the data.

    Overall, the posterior predictive distribution is a good fit for the data. The true line is within the 95% posterior predictive interval.

    def plot_abline(intercept: float, slope: float, ax: plt.Axes = None, **kwargs):\n    \"\"\"Plot a line from slope and intercept\"\"\"\n    if ax is None:\n        ax = plt.gca()\n\n    x_vals = np.array(ax.get_xlim())\n    y_vals = intercept + slope * x_vals\n    ax.plot(x_vals, y_vals, **kwargs)\n\n\ndef plot_lines(ax: plt.Axes, samples: np.ndarray, label: str, color: str, alpha: float):\n    for i, betas in enumerate(samples):\n        label = label if i == 0 else None\n        plot_abline(betas[0], betas[1], ax=ax, color=color, alpha=alpha, label=label)\n\n\nfig, ax = plt.subplots()\nax.set_xlim(-x_lim, x_lim)\nax.set_ylim(y.min(), y.max())\n\nax.scatter(x, y, label=\"data\")\n\nplot_lines(\n    ax=ax,\n    samples=prior.sample_beta(size=100, random_state=rng),\n    label=\"prior\",\n    color=\"blue\",\n    alpha=0.05,\n)\nplot_lines(\n    ax=ax,\n    samples=posterior.sample_beta(size=100, random_state=rng),\n    label=\"posterior\",\n    color=\"black\",\n    alpha=0.2,\n)\n\nplot_abline(intercept, slope, ax=ax, label=\"true\", color=\"red\")\n\nax.set(xlabel=\"x\", ylabel=\"y\", title=\"Linear regression with conjugate prior\")\n\n# New Data\nax.plot(x_new, pp.mu, color=\"green\", label=\"posterior predictive mean\")\ndf_quantile = df_samples.T.quantile([0.025, 0.975]).T\nax.fill_between(\n    x_new,\n    df_quantile[0.025],\n    df_quantile[0.975],\n    alpha=0.2,\n    color=\"green\",\n    label=\"95% posterior predictive interval\",\n)\nax.legend()\nax.set(xlim=(-x_lim_new, x_lim_new))\nplt.show()\n

    "},{"location":"examples/plotting/","title":"Plotting Distributions","text":"

    All the distributions can be plotted using the plot_pdf and plot_pmf methods. The plot_pdf method is used for continuous distributions and the plot_pmf method is used for discrete distributions.

    There is limited support for some distributions like the Dirichlet or those without a dist scipy.

    from conjugate.distributions import Beta, Gamma, Normal\n\nimport matplotlib.pyplot as plt\n\nbeta = Beta(1, 1)\ngamma = Gamma(1, 1)\nnormal = Normal(0, 1)\n\nbound = 3\n\ndist = [beta, gamma, normal]\nlabels = [\"beta\", \"gamma\", \"normal\"]\nax = plt.gca()\nfor label, dist in zip(labels, dist):\n    dist.set_bounds(-bound, bound).plot_pdf(label=label)\n\nax.legend()\nplt.show()\n

    The plotting is also supported for vectorized inputs.

    "},{"location":"examples/pymc-sampling/","title":"Unsupported Posterior Predictive Distributions with PyMC Sampling","text":"

    The geometric beta model posterior predictive doesn't have a common dist, but what doesn't mean the posterior predictive can be used. For instance, PyMC can be used to fill in this gap.

    import pymc as pm\n\nfrom conjugate.distribution import Beta\nfrom conjugate.models import geometric_beta\n\nprior = Beta(1, 1)\nposterior: Beta = geometric_beta(x=1, n=10, beta_prior=prior)\n\nposterior_dist = pm.Beta.dist(alpha=posterior.alpha, beta=posterior.beta)\ngeometric_posterior_predictive = pm.Geometric.dist(posterior_dist)\n\nposterior_predictive_samples = pm.draw(geometric_posterior_predictive, draws=100)\n
    "},{"location":"examples/scaling-distributions/","title":"Scaling Distributions","text":"

    Some of the distributions can be scaled by a constant factor or added together. For instance, operations with Poisson distribution represent the number of events in a given time interval.

    from conjugate.distributions import Poisson\n\nimport matplotlib.pyplot as plt\n\ndaily_rate = 0.25\ndaily_pois = Poisson(lam=daily_rate)\n\ntwo_day_pois = daily_pois + daily_pois\nweekly_pois = 7 * daily_pois\n\nmax_value = 7\nax = plt.gca()\ndists = [daily_pois, two_day_pois, weekly_pois]\nbase_labels = [\"daily\", \"two day\", \"weekly\"]\nfor dist, base_label in zip(dists, base_labels):\n    label = f\"{base_label} rate={dist.lam}\"\n    dist.set_max_value(max_value).plot_pmf(ax=ax, label=label)\n\nax.legend()\nplt.show()\n

    The normal distribution also supports scaling making use of the fact that the variance of a scaled normal distribution is the square of the scaling factor.

    from conjugate.distributions import Normal\n\nimport matplotlib.pyplot as plt\n\nnorm = Normal(mu=0, sigma=1)\nnorm_times_2 = norm * 2\n\nbound = 6\nax = norm.set_bounds(-bound, bound).plot_pdf(label=f\"normal (std = {norm.sigma:.2f})\")\nnorm_times_2.set_bounds(-bound, bound).plot_pdf(ax=ax, label=f\"normal * 2 (std = {norm_times_2.sigma:.2f})\")\nax.legend()\nplt.show()\n

    "},{"location":"examples/scipy-connection/","title":"Connection to SciPy Distributions","text":"

    Many distributions have the dist attribute which is a scipy.stats distribution object. From there, the methods from scipy.stats to get the pdf, cdf, etc can be leveraged.

    from conjugate.distribution import Beta \n\nbeta = Beta(1, 1)\nscipy_dist = beta.dist \n\nprint(scipy_dist.mean())\n# 0.5\nprint(scipy_dist.ppf([0.025, 0.975]))\n# [0.025 0.975]\n\nsamples = scipy_dist.rvs(100)\n
    "},{"location":"examples/vectorized-inputs/","title":"Vectorized Inputs","text":"

    All data and priors will allow for vectorized assuming the shapes work for broadcasting.

    The plotting also supports arrays of results

    import numpy as np\n\nfrom conjugate.distributions import Beta\nfrom conjugate.models import binomial_beta\n\nimport matplotlib.pyplot as plt\n\n# Analytics \nprior = Beta(alpha=1, beta=np.array([1, 5]))\nposterior = binomial_beta(n=N, x=x, beta_prior=prior)\n\n# Figure\nax = prior.plot_pdf(label=lambda i: f\"prior {i}\")\nposterior.plot_pdf(ax=ax, label=lambda i: f\"posterior {i}\")\nax.axvline(x=x / N, ymax=0.05, color=\"black\", linestyle=\"--\", label=\"MLE\")\nax.legend()\nplt.show()\n

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Conjugate Models","text":"

    Bayesian conjugate models in Python

    "},{"location":"#installation","title":"Installation","text":"
    pip install conjugate-models\n
    "},{"location":"#features","title":"Features","text":"
    • Connection to Scipy Distributions with dist attribute
    • Built in Plotting with plot_pdf and plot_pmf methods
    • Vectorized Operations for parameters and data
    • Indexing Parameters for subsetting and slicing
    • Generalized Numerical Inputs for inputs other than builtins and numpy arrays
    • Unsupported Distributions for sampling from unsupported distributions
    "},{"location":"#supported-models","title":"Supported Models","text":"

    Many likelihoods are supported including

    • Bernoulli / Binomial
    • Categorical / Multinomial
    • Poisson
    • Normal (including linear regression)
    • and many more
    "},{"location":"#basic-usage","title":"Basic Usage","text":"
    1. Define prior distribution from distributions module
    2. Pass data and prior into model from models modules
    3. Analytics with posterior and posterior predictive distributions
    from conjugate.distributions import Beta, BetaBinomial\nfrom conjugate.models import binomial_beta, binomial_beta_posterior_predictive\n\n# Observed Data\nX = 4\nN = 10\n\n# Analytics\nprior = Beta(1, 1)\nprior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=prior)\n\nposterior: Beta = binomial_beta(n=N, x=X, beta_prior=prior)\nposterior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=posterior) \n\n# Figure\nimport matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(ncols=2)\n\nax = axes[0]\nax = posterior.plot_pdf(ax=ax, label=\"posterior\")\nprior.plot_pdf(ax=ax, label=\"prior\")\nax.axvline(x=X/N, color=\"black\", ymax=0.05, label=\"MLE\")\nax.set_title(\"Success Rate\")\nax.legend()\n\nax = axes[1]\nposterior_predictive.plot_pmf(ax=ax, label=\"posterior predictive\")\nprior_predictive.plot_pmf(ax=ax, label=\"prior predictive\")\nax.axvline(x=X, color=\"black\", ymax=0.05, label=\"Sample\")\nax.set_title(\"Number of Successes\")\nax.legend()\nplt.show()\n
    "},{"location":"#too-simple","title":"Too Simple?","text":"

    Simple model, sure. Useful model, potentially.

    Constant probability of success, p, for n trials.

    rng = np.random.default_rng(42)\n\n# Observed Data\nn_times = 75\np = np.repeat(0.5, n_times)\nsamples = rng.binomial(n=1, p=p, size=n_times)\n\n# Model\nn = np.arange(n_times) + 1\nprior = Beta(alpha=1, beta=1)\nposterior = binomial_beta(n=n, x=samples.cumsum(), beta_prior=prior)\n\n# Figure\nplt.plot(n, p, color=\"black\", label=\"true p\", linestyle=\"--\")\nplt.scatter(n, samples, color=\"black\", label=\"observed samples\")\nplt.plot(n, posterior.dist.mean(), color=\"red\", label=\"posterior mean\")\n# fill between the 95% credible interval\nplt.fill_between(\n    n, \n    posterior.dist.ppf(0.025),\n    posterior.dist.ppf(0.975),\n    color=\"red\",\n    alpha=0.2,\n    label=\"95% credible interval\",\n)\npadding = 0.025\nplt.ylim(0 - padding, 1 + padding)\nplt.xlim(1, n_times)\nplt.legend(loc=\"best\")\nplt.xlabel(\"Number of trials\")\nplt.ylabel(\"Probability\")\nplt.show()\n

    Even with a moving probability, this simple to implement model can be useful.

    ...\n\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x))\n\np_raw = rng.normal(loc=0, scale=0.2, size=n_times).cumsum()\np = sigmoid(p_raw)\n\n...\n

    "},{"location":"#resources","title":"Resources","text":"
    • Conjugate Priors
    "},{"location":"distributions/","title":"Distributions","text":"

    These are the supported distributions based on the conjugate models.

    Many have the dist attribute which is a scipy.stats distribution object. From there, you can use the methods from scipy.stats to get the pdf, cdf, etc.

    Distributions can be plotted using the plot_pmf or plot_pdf methods of the distribution.

    from conjugate.distribution import Beta \n\nbeta = Beta(1, 1)\nscipy_dist = beta.dist \n\nprint(scipy_dist.mean())\n# 0.5\nprint(scipy_dist.ppf([0.025, 0.975]))\n# [0.025 0.975]\n\nsamples = scipy_dist.rvs(100)\n\nbeta.plot_pmf(label=\"beta distribution\")\n

    Distributions like Poisson can be added with other Poissons or multiplied by numerical values in order to scale rate. For instance,

    daily_rate = 0.25\ndaily_pois = Poisson(lam=daily_rate)\n\ntwo_day_pois = daily_pois + daily_pois\nweekly_pois = 7 * daily_pois\n

    Below are the currently supported distributions

    "},{"location":"distributions/#conjugate.distributions.Beta","title":"Beta dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Beta distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required beta NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Beta(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Beta distribution.\n\n    Args:\n        alpha: shape parameter\n        beta: shape parameter\n\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    def __post_init__(self) -> None:\n        self.max_value = 1.0\n\n    @classmethod\n    def from_mean(cls, mean: float, alpha: float) -> \"Beta\":\n        \"\"\"Alternative constructor from mean and alpha.\"\"\"\n        beta = get_beta_param_from_mean_and_alpha(mean=mean, alpha=alpha)\n        return cls(alpha=alpha, beta=beta)\n\n    @classmethod\n    def from_successes_and_failures(cls, successes: int, failures: int) -> \"Beta\":\n        \"\"\"Alternative constructor based on hyperparameter interpretation.\"\"\"\n        alpha = successes + 1\n        beta = failures + 1\n        return cls(alpha=alpha, beta=beta)\n\n    @property\n    def dist(self):\n        return stats.beta(self.alpha, self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.Beta.from_mean","title":"from_mean(mean, alpha) classmethod","text":"

    Alternative constructor from mean and alpha.

    Source code in conjugate/distributions.py
    @classmethod\ndef from_mean(cls, mean: float, alpha: float) -> \"Beta\":\n    \"\"\"Alternative constructor from mean and alpha.\"\"\"\n    beta = get_beta_param_from_mean_and_alpha(mean=mean, alpha=alpha)\n    return cls(alpha=alpha, beta=beta)\n
    "},{"location":"distributions/#conjugate.distributions.Beta.from_successes_and_failures","title":"from_successes_and_failures(successes, failures) classmethod","text":"

    Alternative constructor based on hyperparameter interpretation.

    Source code in conjugate/distributions.py
    @classmethod\ndef from_successes_and_failures(cls, successes: int, failures: int) -> \"Beta\":\n    \"\"\"Alternative constructor based on hyperparameter interpretation.\"\"\"\n    alpha = successes + 1\n    beta = failures + 1\n    return cls(alpha=alpha, beta=beta)\n
    "},{"location":"distributions/#conjugate.distributions.BetaBinomial","title":"BetaBinomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Beta binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required alpha NUMERIC

    shape parameter

    required beta NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass BetaBinomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Beta binomial distribution.\n\n    Args:\n        n: number of trials\n        alpha: shape parameter\n        beta: shape parameter\n\n    \"\"\"\n\n    n: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    def __post_init__(self):\n        if isinstance(self.n, np.ndarray):\n            self.max_value = self.n.max()\n        else:\n            self.max_value = self.n\n\n    @property\n    def dist(self):\n        return stats.betabinom(self.n, self.alpha, self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.BetaNegativeBinomial","title":"BetaNegativeBinomial dataclass","text":"

    Bases: SliceMixin

    Beta negative binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of successes

    required alpha NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass BetaNegativeBinomial(SliceMixin):\n    \"\"\"Beta negative binomial distribution.\n\n    Args:\n        n: number of successes\n        alpha: shape parameter\n\n    \"\"\"\n\n    n: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n
    "},{"location":"distributions/#conjugate.distributions.Binomial","title":"Binomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass Binomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Binomial distribution.\n\n    Args:\n        n: number of trials\n        p: probability of success\n\n    \"\"\"\n\n    n: NUMERIC\n    p: NUMERIC\n\n    def __post_init__(self):\n        if isinstance(self.n, np.ndarray):\n            self.max_value = self.n.max()\n        else:\n            self.max_value = self.n\n\n    @property\n    def dist(self):\n        return stats.binom(n=self.n, p=self.p)\n
    "},{"location":"distributions/#conjugate.distributions.Dirichlet","title":"Dirichlet dataclass","text":"

    Bases: DirichletPlotDistMixin

    Dirichlet distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Dirichlet(DirichletPlotDistMixin):\n    \"\"\"Dirichlet distribution.\n\n    Args:\n        alpha: shape parameter\n\n    \"\"\"\n\n    alpha: NUMERIC\n\n    def __post_init__(self) -> None:\n        self.max_value = 1.0\n\n    @property\n    def dist(self):\n        if self.alpha.ndim == 1:\n            return stats.dirichlet(self.alpha)\n\n        return VectorizedDist(self.alpha, dist=stats.dirichlet)\n
    "},{"location":"distributions/#conjugate.distributions.Exponential","title":"Exponential dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Exponential distribution.

    Parameters:

    Name Type Description Default lam NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Exponential(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Exponential distribution.\n\n    Args:\n        lam: rate parameter\n\n    \"\"\"\n\n    lam: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.expon(scale=self.lam)\n\n    def __mul__(self, other):\n        return Gamma(alpha=other, beta=1 / self.lam)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Gamma","title":"Gamma dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Gamma distribution.

    Gamma Distribution Scipy Docmentation

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape parameter

    required beta NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Gamma(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Gamma distribution.\n\n    <a href=https://en.wikipedia.org/wiki/Gamma_distribution>Gamma Distribution</a>\n    <a href=https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gamma.html>Scipy Docmentation</a>\n\n    Args:\n        alpha: shape parameter\n        beta: rate parameter\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.gamma(a=self.alpha, scale=1 / self.beta)\n\n    def __mul__(self, other):\n        return Gamma(alpha=self.alpha * other, beta=self.beta)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Geometric","title":"Geometric dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Geometric distribution.

    Parameters:

    Name Type Description Default p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass Geometric(DiscretePlotMixin, SliceMixin):\n    \"\"\"Geometric distribution.\n\n    Args:\n        p: probability of success\n\n    \"\"\"\n\n    p: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.geom(self.p)\n
    "},{"location":"distributions/#conjugate.distributions.InverseGamma","title":"InverseGamma dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    InverseGamma distribution.

    Parameters:

    Name Type Description Default alpha NUMERIC

    shape

    required beta NUMERIC

    scale

    required Source code in conjugate/distributions.py
    @dataclass\nclass InverseGamma(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"InverseGamma distribution.\n\n    Args:\n        alpha: shape\n        beta: scale\n\n    \"\"\"\n\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.invgamma(a=self.alpha, scale=self.beta)\n
    "},{"location":"distributions/#conjugate.distributions.NegativeBinomial","title":"NegativeBinomial dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Negative binomial distribution.

    Parameters:

    Name Type Description Default n NUMERIC

    number of successes

    required p NUMERIC

    probability of success

    required Source code in conjugate/distributions.py
    @dataclass\nclass NegativeBinomial(DiscretePlotMixin, SliceMixin):\n    \"\"\"Negative binomial distribution.\n\n    Args:\n        n: number of successes\n        p: probability of success\n\n    \"\"\"\n\n    n: NUMERIC\n    p: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.nbinom(n=self.n, p=self.p)\n\n    def __mul__(self, other):\n        return NegativeBinomial(n=self.n * other, p=self.p)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.Normal","title":"Normal dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Normal distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required sigma NUMERIC

    standard deviation

    required Source code in conjugate/distributions.py
    @dataclass\nclass Normal(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Normal distribution.\n\n    Args:\n        mu: mean\n        sigma: standard deviation\n\n    \"\"\"\n\n    mu: NUMERIC\n    sigma: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.norm(self.mu, self.sigma)\n\n    def __mul__(self, other):\n        sigma = ((self.sigma**2) * other) ** 0.5\n        return Normal(mu=self.mu * other, sigma=sigma)\n\n    __rmul__ = __mul__\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma","title":"NormalInverseGamma dataclass","text":"

    Normal inverse gamma distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required delta_inverse NUMERIC

    covariance matrix

    required alpha NUMERIC

    shape

    required beta NUMERIC

    scale

    required Source code in conjugate/distributions.py
    @dataclass\nclass NormalInverseGamma:\n    \"\"\"Normal inverse gamma distribution.\n\n    Args:\n        mu: mean\n        delta_inverse: covariance matrix\n        alpha: shape\n        beta: scale\n\n    \"\"\"\n\n    mu: NUMERIC\n    delta_inverse: NUMERIC\n    alpha: NUMERIC\n    beta: NUMERIC\n\n    @classmethod\n    def from_inverse_gamma(\n        cls, mu: NUMERIC, delta_inverse: NUMERIC, inverse_gamma: InverseGamma\n    ) -> \"NormalInverseGamma\":\n        return cls(\n            mu=mu,\n            delta_inverse=delta_inverse,\n            alpha=inverse_gamma.alpha,\n            beta=inverse_gamma.beta,\n        )\n\n    @property\n    def inverse_gamma(self) -> InverseGamma:\n        return InverseGamma(alpha=self.alpha, beta=self.beta)\n\n    def sample_variance(self, size: int, random_state=None) -> NUMERIC:\n        \"\"\"Sample variance from the inverse gamma distribution.\n\n        Args:\n            size: number of samples\n            random_state: random state\n\n        Returns:\n            samples from the inverse gamma distribution\n\n        \"\"\"\n        return self.inverse_gamma.dist.rvs(size=size, random_state=random_state)\n\n    def sample_beta(\n        self, size: int, return_variance: bool = False, random_state=None\n    ) -> Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]:\n        \"\"\"Sample beta from the normal distribution.\n\n        Args:\n            size: number of samples\n            return_variance: whether to return variance as well\n            random_state: random state\n\n        Returns:\n            samples from the normal distribution and optionally variance\n\n        \"\"\"\n        variance = self.sample_variance(size=size, random_state=random_state)\n\n        beta = np.stack(\n            [\n                stats.multivariate_normal(self.mu, v * self.delta_inverse).rvs(\n                    size=1, random_state=random_state\n                )\n                for v in variance\n            ]\n        )\n\n        if return_variance:\n            return beta, variance\n\n        return beta\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma.sample_beta","title":"sample_beta(size, return_variance=False, random_state=None)","text":"

    Sample beta from the normal distribution.

    Parameters:

    Name Type Description Default size int

    number of samples

    required return_variance bool

    whether to return variance as well

    False random_state

    random state

    None

    Returns:

    Type Description Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]

    samples from the normal distribution and optionally variance

    Source code in conjugate/distributions.py
    def sample_beta(\n    self, size: int, return_variance: bool = False, random_state=None\n) -> Union[NUMERIC, Tuple[NUMERIC, NUMERIC]]:\n    \"\"\"Sample beta from the normal distribution.\n\n    Args:\n        size: number of samples\n        return_variance: whether to return variance as well\n        random_state: random state\n\n    Returns:\n        samples from the normal distribution and optionally variance\n\n    \"\"\"\n    variance = self.sample_variance(size=size, random_state=random_state)\n\n    beta = np.stack(\n        [\n            stats.multivariate_normal(self.mu, v * self.delta_inverse).rvs(\n                size=1, random_state=random_state\n            )\n            for v in variance\n        ]\n    )\n\n    if return_variance:\n        return beta, variance\n\n    return beta\n
    "},{"location":"distributions/#conjugate.distributions.NormalInverseGamma.sample_variance","title":"sample_variance(size, random_state=None)","text":"

    Sample variance from the inverse gamma distribution.

    Parameters:

    Name Type Description Default size int

    number of samples

    required random_state

    random state

    None

    Returns:

    Type Description NUMERIC

    samples from the inverse gamma distribution

    Source code in conjugate/distributions.py
    def sample_variance(self, size: int, random_state=None) -> NUMERIC:\n    \"\"\"Sample variance from the inverse gamma distribution.\n\n    Args:\n        size: number of samples\n        random_state: random state\n\n    Returns:\n        samples from the inverse gamma distribution\n\n    \"\"\"\n    return self.inverse_gamma.dist.rvs(size=size, random_state=random_state)\n
    "},{"location":"distributions/#conjugate.distributions.Poisson","title":"Poisson dataclass","text":"

    Bases: DiscretePlotMixin, SliceMixin

    Poisson distribution.

    Parameters:

    Name Type Description Default lam NUMERIC

    rate parameter

    required Source code in conjugate/distributions.py
    @dataclass\nclass Poisson(DiscretePlotMixin, SliceMixin):\n    \"\"\"Poisson distribution.\n\n    Args:\n        lam: rate parameter\n\n    \"\"\"\n\n    lam: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.poisson(self.lam)\n\n    def __mul__(self, other) -> \"Poisson\":\n        return Poisson(lam=self.lam * other)\n\n    __rmul__ = __mul__\n\n    def __add__(self, other) -> \"Poisson\":\n        return Poisson(self.lam + other.lam)\n\n    __radd__ = __add__\n
    "},{"location":"distributions/#conjugate.distributions.StudentT","title":"StudentT dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    StudentT distribution.

    Parameters:

    Name Type Description Default mu NUMERIC

    mean

    required sigma NUMERIC

    standard deviation

    required nu NUMERIC

    degrees of freedom

    required Source code in conjugate/distributions.py
    @dataclass\nclass StudentT(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"StudentT distribution.\n\n    Args:\n        mu: mean\n        sigma: standard deviation\n        nu: degrees of freedom\n\n    \"\"\"\n\n    mu: NUMERIC\n    sigma: NUMERIC\n    nu: NUMERIC\n\n    @property\n    def dist(self):\n        return stats.t(self.nu, self.mu, self.sigma)\n
    "},{"location":"distributions/#conjugate.distributions.Uniform","title":"Uniform dataclass","text":"

    Bases: ContinuousPlotDistMixin, SliceMixin

    Uniform distribution.

    Parameters:

    Name Type Description Default low NUMERIC

    lower bound

    required high NUMERIC

    upper bound

    required Source code in conjugate/distributions.py
    @dataclass\nclass Uniform(ContinuousPlotDistMixin, SliceMixin):\n    \"\"\"Uniform distribution.\n\n    Args:\n        low: lower bound\n        high: upper bound\n\n    \"\"\"\n\n    low: NUMERIC\n    high: NUMERIC\n\n    def __post_init__(self):\n        self.min_value = self.low\n        self.max_value = self.high\n\n    @property\n    def dist(self):\n        return stats.uniform(self.low, self.high)\n
    "},{"location":"mixins/","title":"Mixins","text":"

    Two sets of mixins to support the plotting and slicing of the distribution parameters

    "},{"location":"mixins/#conjugate.plot.ContinuousPlotDistMixin","title":"ContinuousPlotDistMixin","text":"

    Bases: PlotDistMixin

    Functionality for plot_pdf method of continuous distributions.

    Source code in conjugate/plot.py
    class ContinuousPlotDistMixin(PlotDistMixin):\n    \"\"\"Functionality for plot_pdf method of continuous distributions.\"\"\"\n\n    def plot_pdf(self, ax: Optional[plt.Axes] = None, **kwargs) -> plt.Axes:\n        \"\"\"Plot the pdf of distribution\n\n        Args:\n            ax: matplotlib Axes, optional\n            **kwargs: Additonal kwargs to pass to matplotlib\n\n        Returns:\n            new or modified Axes\n\n        \"\"\"\n        ax = self._settle_axis(ax=ax)\n\n        x = self._create_x_values()\n        x = self._reshape_x_values(x)\n\n        return self._create_plot_on_axis(x, ax, **kwargs)\n\n    def _create_x_values(self) -> np.ndarray:\n        return np.linspace(self.min_value, self.max_value, 100)\n\n    def _setup_labels(self, ax) -> None:\n        ax.set_xlabel(\"Domain\")\n        ax.set_ylabel(\"Density $f(x)$\")\n\n    def _create_plot_on_axis(self, x, ax, **kwargs) -> plt.Axes:\n        yy = self.dist.pdf(x)\n        if \"label\" in kwargs:\n            label = kwargs.pop(\"label\")\n            label = resolve_label(label, yy)\n        else:\n            label = None\n\n        ax.plot(x, yy, label=label, **kwargs)\n        self._setup_labels(ax=ax)\n        ax.set_ylim(0, None)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.ContinuousPlotDistMixin.plot_pdf","title":"plot_pdf(ax=None, **kwargs)","text":"

    Plot the pdf of distribution

    Parameters:

    Name Type Description Default ax Optional[Axes]

    matplotlib Axes, optional

    None **kwargs

    Additonal kwargs to pass to matplotlib

    {}

    Returns:

    Type Description Axes

    new or modified Axes

    Source code in conjugate/plot.py
    def plot_pdf(self, ax: Optional[plt.Axes] = None, **kwargs) -> plt.Axes:\n    \"\"\"Plot the pdf of distribution\n\n    Args:\n        ax: matplotlib Axes, optional\n        **kwargs: Additonal kwargs to pass to matplotlib\n\n    Returns:\n        new or modified Axes\n\n    \"\"\"\n    ax = self._settle_axis(ax=ax)\n\n    x = self._create_x_values()\n    x = self._reshape_x_values(x)\n\n    return self._create_plot_on_axis(x, ax, **kwargs)\n
    "},{"location":"mixins/#conjugate.plot.DirichletPlotDistMixin","title":"DirichletPlotDistMixin","text":"

    Bases: ContinuousPlotDistMixin

    Plot the pdf using samples from the dirichlet distribution.

    Source code in conjugate/plot.py
    class DirichletPlotDistMixin(ContinuousPlotDistMixin):\n    \"\"\"Plot the pdf using samples from the dirichlet distribution.\"\"\"\n\n    def plot_pdf(\n        self, ax: Optional[plt.Axes] = None, samples: int = 1_000, **kwargs\n    ) -> plt.Axes:\n        \"\"\"Plots the pdf\"\"\"\n        distribution_samples = self.dist.rvs(size=samples)\n\n        ax = self._settle_axis(ax=ax)\n        xx = self._create_x_values()\n\n        for x in distribution_samples.T:\n            kde = gaussian_kde(x)\n\n            yy = kde(xx)\n\n            ax.plot(xx, yy, **kwargs)\n\n        self._setup_labels(ax=ax)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.DirichletPlotDistMixin.plot_pdf","title":"plot_pdf(ax=None, samples=1000, **kwargs)","text":"

    Plots the pdf

    Source code in conjugate/plot.py
    def plot_pdf(\n    self, ax: Optional[plt.Axes] = None, samples: int = 1_000, **kwargs\n) -> plt.Axes:\n    \"\"\"Plots the pdf\"\"\"\n    distribution_samples = self.dist.rvs(size=samples)\n\n    ax = self._settle_axis(ax=ax)\n    xx = self._create_x_values()\n\n    for x in distribution_samples.T:\n        kde = gaussian_kde(x)\n\n        yy = kde(xx)\n\n        ax.plot(xx, yy, **kwargs)\n\n    self._setup_labels(ax=ax)\n    return ax\n
    "},{"location":"mixins/#conjugate.plot.DiscretePlotMixin","title":"DiscretePlotMixin","text":"

    Bases: PlotDistMixin

    Adding the plot_pmf method to class.

    Source code in conjugate/plot.py
    class DiscretePlotMixin(PlotDistMixin):\n    \"\"\"Adding the plot_pmf method to class.\"\"\"\n\n    def plot_pmf(\n        self, ax: Optional[plt.Axes] = None, mark: str = \"o-\", **kwargs\n    ) -> plt.Axes:\n        ax = self._settle_axis(ax=ax)\n\n        x = self._create_x_values()\n        x = self._reshape_x_values(x)\n\n        return self._create_plot_on_axis(x, ax, mark, **kwargs)\n\n    def _create_x_values(self) -> np.ndarray:\n        return np.arange(self.min_value, self.max_value + 1, 1)\n\n    def _create_plot_on_axis(\n        self, x, ax, mark, conditional: bool = False, **kwargs\n    ) -> plt.Axes:\n        yy = self.dist.pmf(x)\n        if conditional:\n            yy = yy / np.sum(yy)\n            ylabel = f\"Conditional Probability $f(x|{self.min_value} \\\\leq x \\\\leq {self.max_value})$\"\n        else:\n            ylabel = \"Probability $f(x)$\"\n\n        if \"label\" in kwargs:\n            label = kwargs.pop(\"label\")\n            label = resolve_label(label, yy)\n        else:\n            label = None\n\n        ax.plot(x, yy, mark, label=label, **kwargs)\n\n        if self.max_value - self.min_value < 15:\n            ax.set_xticks(x.ravel())\n        else:\n            ax.set_xticks(x.ravel(), minor=True)\n            ax.set_xticks(x[::5].ravel())\n\n        ax.set_xlabel(\"Domain\")\n        ax.set_ylabel(ylabel)\n        ax.set_ylim(0, None)\n        return ax\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin","title":"PlotDistMixin","text":"

    Base mixin in order to support plotting. Requires the dist attribute of the scipy distribution.

    Source code in conjugate/plot.py
    class PlotDistMixin:\n    \"\"\"Base mixin in order to support plotting. Requires the dist attribute of the scipy distribution.\"\"\"\n\n    @property\n    def dist(self) -> Distribution:\n        raise NotImplementedError(\"Implement this property in the subclass.\")\n\n    @property\n    def max_value(self) -> float:\n        if not hasattr(self, \"_max_value\"):\n            raise ValueError(\"Set the max value before plotting.\")\n\n        return self._max_value\n\n    @max_value.setter\n    def max_value(self, value: float) -> None:\n        self._max_value = value\n\n    def set_max_value(self, value: float) -> \"PlotDistMixin\":\n        self.max_value = value\n\n        return self\n\n    @property\n    def min_value(self) -> float:\n        if not hasattr(self, \"_min_value\"):\n            self._min_value = 0.0\n\n        return self._min_value\n\n    @min_value.setter\n    def min_value(self, value: float) -> None:\n        self._min_value = value\n\n    def set_min_value(self, value: float) -> \"PlotDistMixin\":\n        \"\"\"Set the minimum value for plotting.\"\"\"\n        self.min_value = value\n\n        return self\n\n    def set_bounds(self, lower: float, upper: float) -> \"PlotDistMixin\":\n        \"\"\"Set both the min and max values for plotting.\"\"\"\n        return self.set_min_value(lower).set_max_value(upper)\n\n    def _reshape_x_values(self, x: np.ndarray) -> np.ndarray:\n        \"\"\"Make sure that the values are ready for plotting.\"\"\"\n        for value in asdict(self).values():\n            if not isinstance(value, float):\n                return x[:, None]\n\n        return x\n\n    def _settle_axis(self, ax: Optional[plt.Axes] = None) -> plt.Axes:\n        return ax if ax is not None else plt.gca()\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin.set_bounds","title":"set_bounds(lower, upper)","text":"

    Set both the min and max values for plotting.

    Source code in conjugate/plot.py
    def set_bounds(self, lower: float, upper: float) -> \"PlotDistMixin\":\n    \"\"\"Set both the min and max values for plotting.\"\"\"\n    return self.set_min_value(lower).set_max_value(upper)\n
    "},{"location":"mixins/#conjugate.plot.PlotDistMixin.set_min_value","title":"set_min_value(value)","text":"

    Set the minimum value for plotting.

    Source code in conjugate/plot.py
    def set_min_value(self, value: float) -> \"PlotDistMixin\":\n    \"\"\"Set the minimum value for plotting.\"\"\"\n    self.min_value = value\n\n    return self\n
    "},{"location":"mixins/#conjugate.plot.resolve_label","title":"resolve_label(label, yy)","text":"

    https://stackoverflow.com/questions/73662931/matplotlib-plot-a-numpy-array-as-many-lines-with-a-single-label

    Source code in conjugate/plot.py
    def resolve_label(label: LABEL_INPUT, yy: np.ndarray):\n    \"\"\"\n\n    https://stackoverflow.com/questions/73662931/matplotlib-plot-a-numpy-array-as-many-lines-with-a-single-label\n    \"\"\"\n    if yy.ndim == 1:\n        return label\n\n    ncols = yy.shape[1]\n    if ncols != 1:\n        if isinstance(label, str):\n            return [f\"{label} {i}\" for i in range(1, ncols + 1)]\n\n        if callable(label):\n            return [label(i) for i in range(ncols)]\n\n        if isinstance(label, Iterable):\n            return label\n\n        raise ValueError(\"Label must be a string, iterable, or callable.\")\n\n    return label\n
    "},{"location":"mixins/#conjugate.slice.SliceMixin","title":"SliceMixin","text":"

    Mixin in order to slice the parameters

    Source code in conjugate/slice.py
    class SliceMixin:\n    \"\"\"Mixin in order to slice the parameters\"\"\"\n\n    def __getitem__(self, key):\n        params = asdict(self)\n\n        def slice(value, key):\n            try:\n                return value[key]\n            except Exception:\n                return value\n\n        new_params = {k: slice(value=v, key=key) for k, v in params.items()}\n\n        return self.__class__(**new_params)\n
    "},{"location":"models/","title":"Models","text":"

    For more on these models, check out the Conjugate Prior Wikipedia Table

    Below are the supported models

    "},{"location":"models/#conjugate.models.binomial_beta","title":"binomial_beta(n, x, beta_prior)","text":"

    Posterior distribution for a binomial likelihood with a beta prior.

    Parameters:

    Name Type Description Default n NUMERIC

    total number of trials

    required x NUMERIC

    sucesses from that trials

    required beta_prior Beta

    Beta distribution prior

    required

    Returns:

    Type Description Beta

    Beta distribution posterior

    Source code in conjugate/models.py
    def binomial_beta(n: NUMERIC, x: NUMERIC, beta_prior: Beta) -> Beta:\n    \"\"\"Posterior distribution for a binomial likelihood with a beta prior.\n\n    Args:\n        n: total number of trials\n        x: sucesses from that trials\n        beta_prior: Beta distribution prior\n\n    Returns:\n        Beta distribution posterior\n\n    \"\"\"\n    alpha_post, beta_post = get_binomial_beta_posterior_params(\n        beta_prior.alpha, beta_prior.beta, n, x\n    )\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.binomial_beta_posterior_predictive","title":"binomial_beta_posterior_predictive(n, beta)","text":"

    Posterior predictive distribution for a binomial likelihood with a beta prior.

    Parameters:

    Name Type Description Default n NUMERIC

    number of trials

    required beta Beta

    Beta distribution

    required

    Returns:

    Type Description BetaBinomial

    BetaBinomial posterior predictive distribution

    Source code in conjugate/models.py
    def binomial_beta_posterior_predictive(n: NUMERIC, beta: Beta) -> BetaBinomial:\n    \"\"\"Posterior predictive distribution for a binomial likelihood with a beta prior.\n\n    Args:\n        n: number of trials\n        beta: Beta distribution\n\n    Returns:\n        BetaBinomial posterior predictive distribution\n\n    \"\"\"\n    return BetaBinomial(n=n, alpha=beta.alpha, beta=beta.beta)\n
    "},{"location":"models/#conjugate.models.categorical_dirichlet","title":"categorical_dirichlet(x, dirichlet_prior)","text":"

    Posterior distribution of Categorical model with Dirichlet prior.

    Source code in conjugate/models.py
    def categorical_dirichlet(x: NUMERIC, dirichlet_prior: Dirichlet) -> Dirichlet:\n    \"\"\"Posterior distribution of Categorical model with Dirichlet prior.\"\"\"\n    alpha_post = get_dirichlet_posterior_params(dirichlet_prior.alpha, x)\n\n    return Dirichlet(alpha=alpha_post)\n
    "},{"location":"models/#conjugate.models.exponetial_gamma","title":"exponetial_gamma(x_total, n, gamma_prior)","text":"

    Posterior distribution for an exponential likelihood with a gamma prior

    Source code in conjugate/models.py
    def exponetial_gamma(x_total: NUMERIC, n: NUMERIC, gamma_prior: Gamma) -> Gamma:\n    \"\"\"Posterior distribution for an exponential likelihood with a gamma prior\"\"\"\n    alpha_post, beta_post = get_exponential_gamma_posterior_params(\n        alpha=gamma_prior.alpha, beta=gamma_prior.beta, x_total=x_total, n=n\n    )\n\n    return Gamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.geometric_beta","title":"geometric_beta(x_total, n, beta_prior, one_start=True)","text":"

    Posterior distribution for a geometric likelihood with a beta prior.

    Parameters:

    Name Type Description Default x_total

    sum of all trials outcomes

    required n

    total number of trials

    required beta_prior Beta

    Beta distribution prior

    required one_start bool

    whether to outcomes start at 1, defaults to True. False is 0 start.

    True

    Returns:

    Type Description Beta

    Beta distribution posterior

    Source code in conjugate/models.py
    def geometric_beta(x_total, n, beta_prior: Beta, one_start: bool = True) -> Beta:\n    \"\"\"Posterior distribution for a geometric likelihood with a beta prior.\n\n    Args:\n        x_total: sum of all trials outcomes\n        n: total number of trials\n        beta_prior: Beta distribution prior\n        one_start: whether to outcomes start at 1, defaults to True. False is 0 start.\n\n    Returns:\n        Beta distribution posterior\n\n    \"\"\"\n    alpha_post = beta_prior.alpha + n\n    beta_post = beta_prior.beta + x_total\n\n    if one_start:\n        beta_post = beta_post - n\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.linear_regression","title":"linear_regression(X, y, normal_inverse_gamma_prior, inv=np.linalg.inv)","text":"

    Posterior distribution for a linear regression model with a normal inverse gamma prior.

    Derivation taken from this blog here.

    Parameters:

    Name Type Description Default X NUMERIC

    design matrix

    required y NUMERIC

    response vector

    required normal_inverse_gamma_prior NormalInverseGamma

    NormalInverseGamma prior

    required inv

    function to invert matrix, defaults to np.linalg.inv

    inv

    Returns:

    Type Description NormalInverseGamma

    NormalInverseGamma posterior distribution

    Source code in conjugate/models.py
    def linear_regression(\n    X: NUMERIC,\n    y: NUMERIC,\n    normal_inverse_gamma_prior: NormalInverseGamma,\n    inv=np.linalg.inv,\n) -> NormalInverseGamma:\n    \"\"\"Posterior distribution for a linear regression model with a normal inverse gamma prior.\n\n    Derivation taken from this blog [here](https://gregorygundersen.com/blog/2020/02/04/bayesian-linear-regression/).\n\n    Args:\n        X: design matrix\n        y: response vector\n        normal_inverse_gamma_prior: NormalInverseGamma prior\n        inv: function to invert matrix, defaults to np.linalg.inv\n\n    Returns:\n        NormalInverseGamma posterior distribution\n\n    \"\"\"\n    N = X.shape[0]\n\n    delta = inv(normal_inverse_gamma_prior.delta_inverse)\n\n    delta_post = (X.T @ X) + delta\n    delta_post_inverse = inv(delta_post)\n\n    mu_post = (\n        # (B, B)\n        delta_post_inverse\n        # (B, 1)\n        # (B, B) * (B, 1) +  (B, N) * (N, 1)\n        @ (delta @ normal_inverse_gamma_prior.mu + X.T @ y)\n    )\n\n    alpha_post = normal_inverse_gamma_prior.alpha + (0.5 * N)\n    beta_post = normal_inverse_gamma_prior.beta + (\n        0.5\n        * (\n            (y.T @ y)\n            # (1, B) * (B, B) * (B, 1)\n            + (normal_inverse_gamma_prior.mu.T @ delta @ normal_inverse_gamma_prior.mu)\n            # (1, B) * (B, B) * (B, 1)\n            - (mu_post.T @ delta_post @ mu_post)\n        )\n    )\n\n    return NormalInverseGamma(\n        mu=mu_post, delta_inverse=delta_post_inverse, alpha=alpha_post, beta=beta_post\n    )\n
    "},{"location":"models/#conjugate.models.linear_regression_posterior_predictive","title":"linear_regression_posterior_predictive(normal_inverse_gamma, X, eye=np.eye)","text":"

    Posterior predictive distribution for a linear regression model with a normal inverse gamma prior.

    Source code in conjugate/models.py
    def linear_regression_posterior_predictive(\n    normal_inverse_gamma: NormalInverseGamma, X: NUMERIC, eye=np.eye\n) -> MultivariateStudentT:\n    \"\"\"Posterior predictive distribution for a linear regression model with a normal inverse gamma prior.\"\"\"\n    mu = X @ normal_inverse_gamma.mu\n    sigma = (normal_inverse_gamma.beta / normal_inverse_gamma.alpha) * (\n        eye(X.shape[0]) + (X @ normal_inverse_gamma.delta_inverse @ X.T)\n    )\n    nu = 2 * normal_inverse_gamma.alpha\n\n    return MultivariateStudentT(\n        mu=mu,\n        sigma=sigma,\n        nu=nu,\n    )\n
    "},{"location":"models/#conjugate.models.multinomial_dirichlet","title":"multinomial_dirichlet(x, dirichlet_prior)","text":"

    Posterior distribution of Multinomial model with Dirichlet prior.

    Parameters:

    Name Type Description Default x NUMERIC

    counts

    required dirichlet_prior Dirichlet

    Dirichlet prior on the counts

    required

    Returns:

    Type Description Dirichlet

    Dirichlet posterior distribution

    Source code in conjugate/models.py
    def multinomial_dirichlet(x: NUMERIC, dirichlet_prior: Dirichlet) -> Dirichlet:\n    \"\"\"Posterior distribution of Multinomial model with Dirichlet prior.\n\n    Args:\n        x: counts\n        dirichlet_prior: Dirichlet prior on the counts\n\n    Returns:\n        Dirichlet posterior distribution\n\n    \"\"\"\n    alpha_post = get_dirichlet_posterior_params(dirichlet_prior.alpha, x)\n\n    return Dirichlet(alpha=alpha_post)\n
    "},{"location":"models/#conjugate.models.negative_binomial_beta","title":"negative_binomial_beta(r, n, x, beta_prior)","text":"

    Posterior distribution for a negative binomial likelihood with a beta prior.

    Args:

    Source code in conjugate/models.py
    def negative_binomial_beta(r, n, x, beta_prior: Beta) -> Beta:\n    \"\"\"Posterior distribution for a negative binomial likelihood with a beta prior.\n\n    Args:\n\n    \"\"\"\n    alpha_post = beta_prior.alpha + (r * n)\n    beta_post = beta_prior.beta + x\n\n    return Beta(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.negative_binomial_beta_posterior_predictive","title":"negative_binomial_beta_posterior_predictive(r, beta)","text":"

    Posterior predictive distribution for a negative binomial likelihood with a beta prior

    Source code in conjugate/models.py
    def negative_binomial_beta_posterior_predictive(r, beta: Beta) -> BetaNegativeBinomial:\n    \"\"\"Posterior predictive distribution for a negative binomial likelihood with a beta prior\"\"\"\n    return BetaNegativeBinomial(r=r, alpha=beta.alpha, beta=beta.beta)\n
    "},{"location":"models/#conjugate.models.normal_known_mean","title":"normal_known_mean(x_total, x2_total, n, mu, inverse_gamma_prior)","text":"

    Posterior distribution for a normal likelihood with a known mean and a variance prior.

    Parameters:

    Name Type Description Default x_total NUMERIC

    sum of all outcomes

    required x2_total NUMERIC

    sum of all outcomes squared

    required n NUMERIC

    total number of samples in x_total

    required mu NUMERIC

    known mean

    required inverse_gamma_prior InverseGamma

    InverseGamma prior for variance

    required

    Returns:

    Type Description InverseGamma

    InverseGamma posterior distribution for the variance

    Source code in conjugate/models.py
    def normal_known_mean(\n    x_total: NUMERIC,\n    x2_total: NUMERIC,\n    n: NUMERIC,\n    mu: NUMERIC,\n    inverse_gamma_prior: InverseGamma,\n) -> InverseGamma:\n    \"\"\"Posterior distribution for a normal likelihood with a known mean and a variance prior.\n\n    Args:\n        x_total: sum of all outcomes\n        x2_total: sum of all outcomes squared\n        n: total number of samples in x_total\n        mu: known mean\n        inverse_gamma_prior: InverseGamma prior for variance\n\n    Returns:\n        InverseGamma posterior distribution for the variance\n\n    \"\"\"\n    alpha_post = inverse_gamma_prior.alpha + (n / 2)\n    beta_post = inverse_gamma_prior.beta + (\n        0.5 * (x2_total - (2 * mu * x_total) + (n * (mu**2)))\n    )\n\n    return InverseGamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.normal_known_mean_posterior_predictive","title":"normal_known_mean_posterior_predictive(mu, inverse_gamma)","text":"

    Posterior predictive distribution for a normal likelihood with a known mean and a variance prior.

    Parameters:

    Name Type Description Default mu NUMERIC

    known mean

    required inverse_gamma InverseGamma

    InverseGamma prior

    required

    Returns:

    Type Description StudentT

    StudentT posterior predictive distribution

    Source code in conjugate/models.py
    def normal_known_mean_posterior_predictive(\n    mu: NUMERIC, inverse_gamma: InverseGamma\n) -> StudentT:\n    \"\"\"Posterior predictive distribution for a normal likelihood with a known mean and a variance prior.\n\n    Args:\n        mu: known mean\n        inverse_gamma: InverseGamma prior\n\n    Returns:\n        StudentT posterior predictive distribution\n\n    \"\"\"\n    return StudentT(\n        n=2 * inverse_gamma.alpha,\n        mu=mu,\n        sigma=(inverse_gamma.beta / inverse_gamma.alpha) ** 0.5,\n    )\n
    "},{"location":"models/#conjugate.models.poisson_gamma","title":"poisson_gamma(x_total, n, gamma_prior)","text":"

    Posterior distribution for a poisson likelihood with a gamma prior

    Source code in conjugate/models.py
    def poisson_gamma(x_total: NUMERIC, n: NUMERIC, gamma_prior: Gamma) -> Gamma:\n    \"\"\"Posterior distribution for a poisson likelihood with a gamma prior\"\"\"\n    alpha_post, beta_post = get_poisson_gamma_posterior_params(\n        alpha=gamma_prior.alpha, beta=gamma_prior.beta, x_total=x_total, n=n\n    )\n\n    return Gamma(alpha=alpha_post, beta=beta_post)\n
    "},{"location":"models/#conjugate.models.poisson_gamma_posterior_predictive","title":"poisson_gamma_posterior_predictive(gamma, n=1)","text":"

    Posterior predictive distribution for a poisson likelihood with a gamma prior

    Parameters:

    Name Type Description Default gamma Gamma

    Gamma distribution

    required n NUMERIC

    Number of trials for each sample, defaults to 1. Can be used to scale the distributions to a different unit of time.

    1

    Returns:

    Type Description NegativeBinomial

    NegativeBinomial distribution related to posterior predictive

    Source code in conjugate/models.py
    def poisson_gamma_posterior_predictive(\n    gamma: Gamma, n: NUMERIC = 1\n) -> NegativeBinomial:\n    \"\"\"Posterior predictive distribution for a poisson likelihood with a gamma prior\n\n    Args:\n        gamma: Gamma distribution\n        n: Number of trials for each sample, defaults to 1.\n            Can be used to scale the distributions to a different unit of time.\n\n    Returns:\n        NegativeBinomial distribution related to posterior predictive\n\n    \"\"\"\n    n = n * gamma.alpha\n    p = gamma.beta / (1 + gamma.beta)\n\n    return NegativeBinomial(n=n, p=p)\n
    "},{"location":"examples/bayesian-update/","title":"Bayesian Update","text":"

    Easy to use Bayesian inference incrementally by making the posterior the prior for the next update.

    import numpy as np\nimport matplotlib.pyplot as plt\n\nfrom conjugate.distributions import NormalInverseGamma\nfrom conjugate.models import linear_regression\n\ndef create_sampler(mu, sigma, rng): \n    \"\"\"Generate a sampler from a normal distribution with mean `mu` and standard deviation `sigma`.\"\"\"\n    def sample(n: int): \n        return rng.normal(loc=mu, scale=sigma, size=n)\n\n    return sample\n\n\nmu = 5.0\nsigma = 2.5\nrng = np.random.default_rng(0)\nsample = create_sampler(mu=mu, sigma=sigma, rng=rng)\n\n\nprior = NormalInverseGamma(\n    mu=np.array([0]), \n    delta_inverse=np.array([[1]]), \n    alpha=1, beta=1, \n)\n\n\ncumsum = 0\nbatch_sizes = [5, 10, 25]\nax = plt.gca()\nfor batch_size in batch_sizes:\n    y = sample(n=batch_size)\n    X = np.ones_like(y)[:, None]\n\n    posterior = linear_regression(X, y, prior)\n    beta_samples, variance_samples = posterior.sample_beta(size=1000, return_variance=True, random_state=rng)\n\n    cumsum += batch_size\n    label = f\"n={cumsum}\"\n    ax.scatter(variance_samples ** 0.5, beta_samples, alpha=0.25, label=label)\n\n    prior = posterior \n\nax.scatter(sigma, mu, color=\"black\", label=\"true\")\nax.set(\n    xlabel=\"$\\sigma$\", \n    ylabel=\"$\\mu$\", \n    xlim=(0, None), \n    ylim=(0, None), \n    title=\"Updated posterior samples of $\\mu$ and $\\sigma$\"\n)\nax.legend()\n\nplt.show()\n

    "},{"location":"examples/binomial/","title":"Binomial Model","text":"
    from conjugate.distributions import Beta, Binomial, BetaBinomial\nfrom conjugate.models import binomial_beta, binomial_beta_posterior_predictive\n\nimport matplotlib.pyplot as plt\n\nN = 10\ntrue_dist = Binomial(n=N, p=0.5)\n\n# Observed Data\nX = true_dist.dist.rvs(size=1, random_state=42)\n\n# Conjugate prior\nprior = Beta(alpha=1, beta=1)\nposterior: Beta = binomial_beta(n=N, x=X, beta_prior=prior)\n\n# Comparison\nprior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=prior)\nposterior_predictive: BetaBinomial = binomial_beta_posterior_predictive(n=N, beta=posterior)\n\n# Figure \nfig, axes = plt.subplots(ncols=2, nrows=1, figsize=(8, 4))\n\nax: plt.Axes = axes[0]\nposterior.plot_pdf(ax=ax, label=\"posterior\")\nprior.plot_pdf(ax=ax, label=\"prior\")\nax.axvline(x=X/N, color=\"black\", ymax=0.05, label=\"MLE\")\nax.axvline(x=true_dist.p, color=\"black\", ymax=0.05, linestyle=\"--\", label=\"True\")\nax.set_title(\"Success Rate\")\nax.legend()\n\nax: plt.Axes = axes[1]\ntrue_dist.plot_pmf(ax=ax, label=\"true distribution\", color=\"C2\")\nposterior_predictive.plot_pmf(ax=ax, label=\"posterior predictive\")\nprior_predictive.plot_pmf(ax=ax, label=\"prior predictive\")\nax.axvline(x=X, color=\"black\", ymax=0.05, label=\"Sample\")\nax.set_title(\"Number of Successes\")\nax.legend()\n\nplt.show()\n
    "},{"location":"examples/generalized-inputs/","title":"Generalized Numerical Inputs","text":"

    Though the plotting is meant for numpy and python numbers, the conjugate models work with anything that works like numbers.

    For instance, Bayesian models in SQL using the SQL Builder, PyPika

    from pypika import Field \n\n# Columns from table in database\nN = Field(\"total\")\nX = Field(\"successes\")\n\n# Conjugate prior\nprior = Beta(alpha=1, beta=1)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\nprint(\"Posterior alpha:\", posterior.alpha)\nprint(\"Posterior beta:\", posterior.beta)\n# Posterior alpha: 1+\"successes\"\n# Posterior beta: 1+\"total\"-\"successes\"\n\n# Priors can be fields too\nalpha = Field(\"previous_successes\") - 1\nbeta = Field(\"previous_failures\") - 1\n\nprior = Beta(alpha=alpha, beta=beta)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\nprint(\"Posterior alpha:\", posterior.alpha)\nprint(\"Posterior beta:\", posterior.beta)\n# Posterior alpha: \"previous_successes\"-1+\"successes\"\n# Posterior beta: \"previous_failures\"-1+\"total\"-\"successes\"\n

    Using PyMC distributions for sampling with additional uncertainty

    import pymc as pm \n\nalpha = pm.Gamma.dist(alpha=1, beta=20)\nbeta = pm.Gamma.dist(alpha=1, beta=20)\n\n# Observed Data\nN = 10\nX = 4\n\n# Conjugate prior \nprior = Beta(alpha=alpha, beta=beta)\nposterior = binomial_beta(n=N, x=X, beta_prior=prior)\n\n# Reconstruct the posterior distribution with PyMC\nprior_dist = pm.Beta.dist(alpha=prior.alpha, beta=prior.beta)\nposterior_dist = pm.Beta.dist(alpha=posterior.alpha, beta=posterior.beta)\n\nsamples = pm.draw([alpha, beta, prior_dist, posterior_dist], draws=1000)\n
    "},{"location":"examples/indexing/","title":"Indexing Parameters","text":"

    The distributions can be indexed for subsets.

    beta = np.arange(1, 10)\nprior = Beta(alpha=1, beta=beta)\n\nidx = [0, 5, -1]\nprior_subset = prior[idx]\nprior_subset.plot_pdf(label = lambda i: f\"prior {i}\")\nplt.legend()\nplt.show()\n

    "},{"location":"examples/linear-regression/","title":"Linear Regression","text":"

    We can fit linear regression that includes a predictive distribution for new data using a conjugate prior. This example only has one covariate, but the same approach can be used for multiple covariates.

    "},{"location":"examples/linear-regression/#simulate-data","title":"Simulate Data","text":"

    We are going to simulate data from a linear regression model. The true intercept is 3.5, the true slope is -2.0, and the true variance is 2.5.

    import numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\n\nfrom conjugate.distributions import NormalInverseGamma, MultivariateStudentT\nfrom conjugate.models import linear_regression, linear_regression_posterior_predictive\n\nintercept = 3.5\nslope = -2.0\nsigma = 2.5\n\nrng = np.random.default_rng(0)\n\nx_lim = 3\nn_points = 100\nx = np.linspace(-x_lim, x_lim, n_points)\ny = intercept + slope * x + rng.normal(scale=sigma, size=n_points)\n
    "},{"location":"examples/linear-regression/#define-prior-and-find-posterior","title":"Define Prior and Find Posterior","text":"

    There needs to be a prior for the intercept, slope, and the variance.

    prior = NormalInverseGamma(\n    mu=np.array([0, 0]),\n    delta_inverse=np.array([[1, 0], [0, 1]]),\n    alpha=1,\n    beta=1,\n)\n\ndef create_X(x: np.ndarray) -> np.ndarray:\n    return np.stack([np.ones_like(x), x]).T\n\nX = create_X(x)\nposterior: NormalInverseGamma = linear_regression(\n    X=X,\n    y=y,\n    normal_inverse_gamma_prior=prior,\n)\n
    "},{"location":"examples/linear-regression/#posterior-predictive-for-new-data","title":"Posterior Predictive for New Data","text":"

    The multivariate student-t distribution is used for the posterior predictive distribution. We have to draw samples from it since the scipy implementation does not have a ppf method.

    # New Data\nx_lim_new = 1.5 * x_lim\nx_new = np.linspace(-x_lim_new, x_lim_new, 20)\nX_new = create_X(x_new)\npp: MultivariateStudentT = linear_regression_posterior_predictive(normal_inverse_gamma=posterior, X=X_new)\n\nsamples = pp.dist.rvs(5_000).T\ndf_samples = pd.DataFrame(samples, index=x_new)\n
    "},{"location":"examples/linear-regression/#plot-results","title":"Plot Results","text":"

    We can see that the posterior predictive distribution begins to widen as we move away from the data.

    Overall, the posterior predictive distribution is a good fit for the data. The true line is within the 95% posterior predictive interval.

    def plot_abline(intercept: float, slope: float, ax: plt.Axes = None, **kwargs):\n    \"\"\"Plot a line from slope and intercept\"\"\"\n    if ax is None:\n        ax = plt.gca()\n\n    x_vals = np.array(ax.get_xlim())\n    y_vals = intercept + slope * x_vals\n    ax.plot(x_vals, y_vals, **kwargs)\n\n\ndef plot_lines(ax: plt.Axes, samples: np.ndarray, label: str, color: str, alpha: float):\n    for i, betas in enumerate(samples):\n        label = label if i == 0 else None\n        plot_abline(betas[0], betas[1], ax=ax, color=color, alpha=alpha, label=label)\n\n\nfig, ax = plt.subplots()\nax.set_xlim(-x_lim, x_lim)\nax.set_ylim(y.min(), y.max())\n\nax.scatter(x, y, label=\"data\")\n\nplot_lines(\n    ax=ax,\n    samples=prior.sample_beta(size=100, random_state=rng),\n    label=\"prior\",\n    color=\"blue\",\n    alpha=0.05,\n)\nplot_lines(\n    ax=ax,\n    samples=posterior.sample_beta(size=100, random_state=rng),\n    label=\"posterior\",\n    color=\"black\",\n    alpha=0.2,\n)\n\nplot_abline(intercept, slope, ax=ax, label=\"true\", color=\"red\")\n\nax.set(xlabel=\"x\", ylabel=\"y\", title=\"Linear regression with conjugate prior\")\n\n# New Data\nax.plot(x_new, pp.mu, color=\"green\", label=\"posterior predictive mean\")\ndf_quantile = df_samples.T.quantile([0.025, 0.975]).T\nax.fill_between(\n    x_new,\n    df_quantile[0.025],\n    df_quantile[0.975],\n    alpha=0.2,\n    color=\"green\",\n    label=\"95% posterior predictive interval\",\n)\nax.legend()\nax.set(xlim=(-x_lim_new, x_lim_new))\nplt.show()\n

    "},{"location":"examples/plotting/","title":"Plotting Distributions","text":"

    All the distributions can be plotted using the plot_pdf and plot_pmf methods. The plot_pdf method is used for continuous distributions and the plot_pmf method is used for discrete distributions.

    There is limited support for some distributions like the Dirichlet or those without a dist scipy.

    from conjugate.distributions import Beta, Gamma, Normal\n\nimport matplotlib.pyplot as plt\n\nbeta = Beta(1, 1)\ngamma = Gamma(1, 1)\nnormal = Normal(0, 1)\n\nbound = 3\n\ndist = [beta, gamma, normal]\nlabels = [\"beta\", \"gamma\", \"normal\"]\nax = plt.gca()\nfor label, dist in zip(labels, dist):\n    dist.set_bounds(-bound, bound).plot_pdf(label=label)\n\nax.legend()\nplt.show()\n

    The plotting is also supported for vectorized inputs.

    "},{"location":"examples/pymc-sampling/","title":"Unsupported Posterior Predictive Distributions with PyMC Sampling","text":"

    The geometric beta model posterior predictive doesn't have a common dist, but what doesn't mean the posterior predictive can be used. For instance, PyMC can be used to fill in this gap.

    import pymc as pm\n\nfrom conjugate.distribution import Beta\nfrom conjugate.models import geometric_beta\n\nprior = Beta(1, 1)\nposterior: Beta = geometric_beta(x=1, n=10, beta_prior=prior)\n\nposterior_dist = pm.Beta.dist(alpha=posterior.alpha, beta=posterior.beta)\ngeometric_posterior_predictive = pm.Geometric.dist(posterior_dist)\n\nposterior_predictive_samples = pm.draw(geometric_posterior_predictive, draws=100)\n
    "},{"location":"examples/scaling-distributions/","title":"Scaling Distributions","text":"

    Some of the distributions can be scaled by a constant factor or added together. For instance, operations with Poisson distribution represent the number of events in a given time interval.

    from conjugate.distributions import Poisson\n\nimport matplotlib.pyplot as plt\n\ndaily_rate = 0.25\ndaily_pois = Poisson(lam=daily_rate)\n\ntwo_day_pois = daily_pois + daily_pois\nweekly_pois = 7 * daily_pois\n\nmax_value = 7\nax = plt.gca()\ndists = [daily_pois, two_day_pois, weekly_pois]\nbase_labels = [\"daily\", \"two day\", \"weekly\"]\nfor dist, base_label in zip(dists, base_labels):\n    label = f\"{base_label} rate={dist.lam}\"\n    dist.set_max_value(max_value).plot_pmf(ax=ax, label=label)\n\nax.legend()\nplt.show()\n

    The normal distribution also supports scaling making use of the fact that the variance of a scaled normal distribution is the square of the scaling factor.

    from conjugate.distributions import Normal\n\nimport matplotlib.pyplot as plt\n\nnorm = Normal(mu=0, sigma=1)\nnorm_times_2 = norm * 2\n\nbound = 6\nax = norm.set_bounds(-bound, bound).plot_pdf(label=f\"normal (std = {norm.sigma:.2f})\")\nnorm_times_2.set_bounds(-bound, bound).plot_pdf(ax=ax, label=f\"normal * 2 (std = {norm_times_2.sigma:.2f})\")\nax.legend()\nplt.show()\n

    "},{"location":"examples/scipy-connection/","title":"Connection to SciPy Distributions","text":"

    Many distributions have the dist attribute which is a scipy.stats distribution object. From there, the methods from scipy.stats to get the pdf, cdf, etc can be leveraged.

    from conjugate.distribution import Beta \n\nbeta = Beta(1, 1)\nscipy_dist = beta.dist \n\nprint(scipy_dist.mean())\n# 0.5\nprint(scipy_dist.ppf([0.025, 0.975]))\n# [0.025 0.975]\n\nsamples = scipy_dist.rvs(100)\n
    "},{"location":"examples/vectorized-inputs/","title":"Vectorized Inputs","text":"

    All data and priors will allow for vectorized assuming the shapes work for broadcasting.

    The plotting also supports arrays of results

    import numpy as np\n\nfrom conjugate.distributions import Beta\nfrom conjugate.models import binomial_beta\n\nimport matplotlib.pyplot as plt\n\n# Analytics \nprior = Beta(alpha=1, beta=np.array([1, 5]))\nposterior = binomial_beta(n=N, x=x, beta_prior=prior)\n\n# Figure\nax = prior.plot_pdf(label=lambda i: f\"prior {i}\")\nposterior.plot_pdf(ax=ax, label=lambda i: f\"posterior {i}\")\nax.axvline(x=x / N, ymax=0.05, color=\"black\", linestyle=\"--\", label=\"MLE\")\nax.legend()\nplt.show()\n

    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 6164903..68897fb 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,72 +2,72 @@ https://wd60622.github.io/conjugate/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/distributions/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/mixins/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/models/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/bayesian-update/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/binomial/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/generalized-inputs/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/indexing/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/linear-regression/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/plotting/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/pymc-sampling/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/scaling-distributions/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/scipy-connection/ - 2024-01-07 + 2024-01-09 daily https://wd60622.github.io/conjugate/examples/vectorized-inputs/ - 2024-01-07 + 2024-01-09 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 5b83c91..b157b80 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ