Source code for plotnine_extra.stats.stat_theodensity
"""
``stat_theodensity``: theoretical density curve.
Port of ``ggh4x::stat_theodensity``. Fits a parametric
distribution to the data via maximum likelihood and returns the
density evaluated on a grid spanning the observed range.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from plotnine.doctools import document
from plotnine.stats.stat import stat
from scipy import stats as _sps
_DIST_MAP = {
"norm": _sps.norm,
"normal": _sps.norm,
"lnorm": _sps.lognorm,
"lognormal": _sps.lognorm,
"gamma": _sps.gamma,
"beta": _sps.beta,
"exp": _sps.expon,
"exponential": _sps.expon,
"weibull": _sps.weibull_min,
"cauchy": _sps.cauchy,
"logistic": _sps.logistic,
"t": _sps.t,
}
[docs]
@document
class stat_theodensity(stat):
"""
Fit a parametric distribution and return its density curve.
{usage}
Parameters
----------
{common_parameters}
distri : str, default ``"norm"``
Name of the distribution to fit. Anything in
:data:`_DIST_MAP`.
n : int, default 256
Number of grid points along the x range.
"""
REQUIRED_AES = {"x"}
DEFAULT_PARAMS = {
"geom": "line",
"position": "identity",
"na_rm": False,
"distri": "norm",
"n": 256,
}
[docs]
def compute_group(self, data, scales) -> pd.DataFrame:
distri = self.params["distri"]
if distri not in _DIST_MAP:
raise ValueError(
f"Unknown distri {distri!r}; expected one of "
f"{sorted(_DIST_MAP)}"
)
dist = _DIST_MAP[distri]
x = data["x"].to_numpy(dtype=float)
x = x[~np.isnan(x)]
if x.size < 2:
return pd.DataFrame({"x": [], "y": [], "density": []})
params = dist.fit(x)
grid = np.linspace(x.min(), x.max(), int(self.params["n"]))
density = dist.pdf(grid, *params)
return pd.DataFrame(
{
"x": grid,
"y": density,
"density": density,
}
)