Source code for plotnine_extra.stats.stat_rle
"""
``stat_rle``: run-length encoding of a categorical x series.
Port of ``ggh4x::stat_rle``. Each contiguous run of identical
``label`` values becomes a row with ``start``, ``end``,
``run_id``, ``run_length`` and ``runvalue`` columns.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from plotnine.doctools import document
from plotnine.stats.stat import stat
from ._common import preserve_panel_columns
[docs]
@document
class stat_rle(stat):
"""
Compute run-length encoded segments of a categorical aes.
{usage}
Parameters
----------
{common_parameters}
align : str, default ``"center"``
Where to anchor the segment. One of ``"center"``,
``"start"`` or ``"end"``.
"""
REQUIRED_AES = {"x", "label"}
DEFAULT_PARAMS = {
"geom": "rect",
"position": "identity",
"na_rm": False,
"align": "center",
}
CREATES = {
"start",
"end",
"run_id",
"run_length",
"runvalue",
"xmin",
"xmax",
}
[docs]
def compute_panel(self, data, scales) -> pd.DataFrame:
if data.empty:
return data
d = data.sort_values("x").reset_index(drop=True)
labels = d["label"].to_numpy()
xs = d["x"].to_numpy(dtype=float)
change = np.concatenate(([True], labels[1:] != labels[:-1]))
run_id = np.cumsum(change) - 1
rows = []
for rid in np.unique(run_id):
mask = run_id == rid
seg_x = xs[mask]
rows.append(
{
"run_id": int(rid),
"runvalue": labels[mask][0],
"run_length": int(mask.sum()),
"start": float(seg_x.min()),
"end": float(seg_x.max()),
"xmin": float(seg_x.min()),
"xmax": float(seg_x.max()),
"x": float(seg_x.mean()),
"y": 0.0,
"ymin": 0.0,
"ymax": 1.0,
}
)
result = pd.DataFrame(rows)
# Carry PANEL / group through so plotnine's position
# scale training can join against the computed rows.
return preserve_panel_columns(result, data)