# Custom Distribution with pm.CustomDist

**URL:** <https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071>\
**Category:** v5\
**Tags:** modeling\
**Created:** [December 27, 2022, 8:53pm UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071 "2022-12-27T20:53:36Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![zult](https://avatars.discourse-cdn.com/v4/letter/z/e99b99/32.png) [@zult](https://discourse.pymc.io/u/zult)\
**Post date:** [December 27, 2022, 8:53pm UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071/1 "2022-12-27T20:53:36Z")

</div>

Hello,

For this question, I’m currently using PyMC v5.0.1.

This is an extension from a previous [topic](https://discourse.pymc.io/t/combined-likelihoods-for-fitting-observed-individual-and-summary-level-data), but I was hoping to get some advice on using PyMC’s `pm.CustomDist` (or something similar) to define the likelihood such that a posterior predictive check (ppc) can be run on the custom likelihood. I understand how `pm.Potential` can be used to tack on the custom logp, but as has been outlined in numerous topics, you can’t do anything more after that.

To recap, I have summary-level concentration data (mean and standard deviation) reported from a study and I’d like to fit these data with a custom log-normally distributed log-likelihood function. The specifics of that function are in the previous [topic](https://discourse.pymc.io/t/combined-likelihoods-for-fitting-observed-individual-and-summary-level-data). I’ve attempted this two different ways as outlined below.

# 1. Use pm.CustomDist

First, I attempted to follow the documentation on `pm.CustomDist` to manually define the `logp` and `random` functions:

```auto
import pymc as pm
from pytensor.tensor import TensorVariable

def _lognormal_logp(value: TensorVariable, mu: TensorVariable, sigma: TensorVariable, Ni_summary: TensorVariable, lnsd2_summary: TensorVariable) -> TensorVariable:
    N = np.sum(Ni_summary)
    term1 = (Ni_summary*value)
    term2 = (Ni_summary/2.)*pm.math.log(sigma**2)
    term3 = ((Ni_summary - 1)*lnsd2_summary)/(2*sigma**2)
    term4 = (Ni_summary*(value - mu)**2)/(2*sigma**2)
    LL = (N/2)*pm.math.log(2*np.pi) - pm.math.sum((term1 + term2 + term3 + term4))
    return LL

def _lognormal_random(mu: TensorVariable, sigma: TensorVariable, size: TensorVariable) -> TensorVariable:
    return pm.LogNormal.dist(mu, sigma=sigma, size=size)

```

within `pm.Model`, I use `pm.CustomDist`:

```auto
with pm.Model as model:
   " <All the required model specs>"

    # lnconc_summary is the model predicted concentrations (log-transformed) for the summary-level data
    # sigma_summary is the estimated model error for the summary level data (log-space)
    # Ni_summary is the number of animals contributing to each observed summary-level data point
    # lnsd2_summary is the observed variance (log-transformed)
    # lny_summary is the observed mean (log-transformed)
    pm.CustomDist("conc_summary", lnconc_summary, sigma_summary, Ni_summary, lnsd2_summary, logp=lognormal_logp, random=_lognormal_random, observed=lny_summary

```

Unfortunately, this implementation gets hung up on the `logp` function with:  
`TypeError: logp() takes 2 positional arguments but 3 were given`

It would appear I’m not able to pass in the additional parameters (`Ni_summary`, `lnsd2_summary`) to `_lognormal_logp`, but perhaps I’m wrong in this interpretation.

# 2. Define custom log-normal distribution

The alternative approach defines a custom log-normal distribution with the desired `logp` using:

```auto
class CustomLogNormal(pm.Continuous):
    def __init__ (self, mu, sigma, Ni_summary, lnsd2_summary, *args, **kwargs):
        super(CustomLogNormal, self). __init__ (*args, **kwargs)
        self.mu = mu = pt.as_tensor_variable(mu)
        self.sigma = sigma = pt.as_tensor_variable(sigma)
        self.N = np.sum(Ni_summary)
        self.Ni_summary = Ni_summary = pt.as_tensor_variable(Ni_summary) # Number of animals per time point
        self.lnsd2_summary = lnsd2_summary = pt.as_tensor_variable(lnsd2_summary) # Measured variance (in log-space)

    def logp(self, x):
        term1 = (self.Ni_summary*value)
        term2 = (self.Ni_summary/2.)*pm.math.log(self.sigma**2)
        term3 = ((self.Ni_summary - 1)*self.lnsd2_summary)/(2*self.sigma**2)
        term4 = (self.Ni_summary*(value - self.mu)**2)/(2*self.sigma**2)
        LL = (self.N/2)*pm.math.log(2*np.pi) - pm.math.sum((term1 + term2 + term3 + term4))
        return LL

    def random(self, mu, sigma, size):
        return pm.LogNormal.dist(mu, sigma=sigma, size=size)

```

Then within the model, I try to define the observed data with

```auto
with pm.Model as model:
   " <All the required model specs>"
   CustomLogNormal('conc_summary', mu=lnconc_summary, sigma=sigma_summary, Ni_summary=Ni_summary, lnsd2_summary=lnsd2_summary, observed=lny_summary)

```

This implementation give the error:  
`TypeError: dist() missing 1 required positional argument: 'dist_params'`

This `dist_params` requirement might be new and I haven’t been able to figure out how to satisfy it.

Any help here would be greatly appreciated. Ultimately, I would like to be able to use the custom logp for the summary-level data in a log-normal distribution and then run a posterior predictive check on the summary-level predictions. Appending the logp with `pm.Potential` works really nicely so let me know if expanding to a ppc for summary-level data isn’t feasible.

Thanks again for all your help.

---

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.pymc.io/ricardov94/32/5775_2.png) [@ricardoV94](https://discourse.pymc.io/u/ricardoV94)\
**Post date:** [December 27, 2022, 10:17pm UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071/2 "2022-12-27T22:17:27Z")

</div>

The CustomDist random function should accept the 2 summary parameters just like the logp function, even if it doesn’t use them.

---

<div class="post-metadata">

**Author:** ![zult](https://avatars.discourse-cdn.com/v4/letter/z/e99b99/32.png) [@zult](https://discourse.pymc.io/u/zult)\
**Post date:** [January 10, 2023, 7:33pm UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071/3 "2023-01-10T19:33:47Z")

</div>

Thanks for response @ricardoV94 and you’re exactly right about passing the arguments from `pm.CustomDist` into the custom logp and random functions. I’ve learned now that all variables after the `name` in `pm.CustomDist` are passed into the logp and random functions so I needed all of them to be available as inputs in the custom functions, even if those variables aren’t all used.

I updated the code with:

```auto
def _lognormal_logp(value, mu, sigma, Ni_summary, lnsd2_summary):
    # First input is observed value
    term1 = (Ni_summary*value)
    term2 = (Ni_summary/2.)*pm.math.log(sigma**2)
    term3 = ((Ni_summary - 1)*lnsd2_summary)/(2*sigma**2)
    term4 = (Ni_summary*(value - mu)**2)/(2*sigma**2)
    logp = (Ni_summary/2)*pm.math.log(2*np.pi) - (term1 + term2 + term3 + term4)
    return logp

def _lognormal_random(mu, sigma, Ni_summary, lnsd2_summary, rng=None, size=None):
    # Need Ni_summary and lnsd2_summary even though they aren't used
    return rng.normal(loc=mu, scale=sigma, size=size)

pm.CustomDist("conc_summary", lnconc_summary, sigma_summary, Ni_summary, lnsd2_summary, logp=_lognormal_logp, random=_lognormal_random, observed=lny_summary

```

And now everything runs nicely with `pm.CustomDist` and I’m able to rum `pm.sample_posterior_predictive` as well.

Thanks!

---

<div class="post-metadata">

**Author:** ![DanWeitzenfeld](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.pymc.io/danweitzenfeld/32/30_2.png) [@DanWeitzenfeld](https://discourse.pymc.io/u/DanWeitzenfeld)\
**Post date:** [June 26, 2023, 8:18pm UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071/4 "2023-06-26T20:18:03Z")

</div>

I have a few custom distributions defined like Zulu’s #2 above, from pymc3. I’m trying to transfer them to pymc v5 and I’m getting the same error he mentions. Is there a solution for this, other than transferring to method #1?

---

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.pymc.io/ricardov94/32/5775_2.png) [@ricardoV94](https://discourse.pymc.io/u/ricardoV94)\
**Post date:** [June 27, 2023, 5:18am UTC](https://discourse.pymc.io/t/custom-distribution-with-pm-customdist/11071/5 "2023-06-27T05:18:06Z")

</div>

Can you share some code?
