Source code for plotnine_extra.stats.stat_rollingkernel
"""
``stat_rollingkernel``: rolling kernel smoother.
Port of ``ggh4x::stat_rollingkernel``. Computes a kernel-weighted
moving average of ``y`` along ``x``. Output columns are
``x`` (the evaluation grid) and ``y`` (the smoothed value).
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from plotnine.doctools import document
from plotnine.stats.stat import stat
_KERNELS = {
"gaussian": lambda u: np.exp(-0.5 * u * u),
"triangular": lambda u: np.maximum(0, 1 - np.abs(u)),
"epanechnikov": lambda u: np.maximum(0, 0.75 * (1 - u * u)),
"uniform": lambda u: (np.abs(u) <= 1).astype(float),
}
[docs]
@document
class stat_rollingkernel(stat):
"""
Smooth a series with a rolling kernel.
{usage}
Parameters
----------
{common_parameters}
bw : float, default 1.0
Kernel bandwidth (in x units).
kernel : str, default ``"gaussian"``
One of ``"gaussian"``, ``"triangular"``,
``"epanechnikov"`` or ``"uniform"``.
n : int, default 100
Number of evaluation points along the x range.
"""
REQUIRED_AES = {"x", "y"}
DEFAULT_PARAMS = {
"geom": "line",
"position": "identity",
"na_rm": False,
"bw": 1.0,
"kernel": "gaussian",
"n": 100,
}
[docs]
def compute_group(self, data, scales) -> pd.DataFrame:
kernel = _KERNELS.get(self.params["kernel"])
if kernel is None:
raise ValueError(
f"Unknown kernel {self.params['kernel']!r}; "
f"expected one of {sorted(_KERNELS)}"
)
bw = float(self.params["bw"])
n = int(self.params["n"])
xs = data["x"].to_numpy(dtype=float)
ys = data["y"].to_numpy(dtype=float)
if xs.size == 0:
return pd.DataFrame({"x": [], "y": []})
grid = np.linspace(xs.min(), xs.max(), n)
out = np.empty(n, dtype=float)
for i, g in enumerate(grid):
w = kernel((xs - g) / bw)
total = w.sum()
out[i] = (w * ys).sum() / total if total > 0 else np.nan
return pd.DataFrame({"x": grid, "y": out})