Skip to content

density_outline

Outlines of a 2D point cloud at a fraction of its peak density, computed in Rust.

Not in lets-plot. density_outline() evaluates a Gaussian kernel density with a full covariance (as scipy.stats.gaussian_kde) on a padded grid and traces the contour at level times its peak by marching squares (as skimage.measure.find_contours), so outlines need neither scipy nor scikit-image. The result is a polars frame to draw with geom_path.

Bandwidth module-attribute

Bandwidth = typing.Literal['scott', 'silverman'] | float

Represent a union type

E.g. for int | str

Pairs module-attribute

Pairs = (
    collections.abc.Sequence[
        collections.abc.Sequence[float]
    ]
    | ggrust.density_outline._ArrayLike
)

Represent a union type

E.g. for int | str

SpecError

SpecError(
    message: str, *, path: str = "", reason: str = ""
)

Bases: ggrust.errors.GgrustError, builtins.ValueError

Raised when the plot specification holds an invalid value.

Parameters:

  • message (str) –

    The full message, including the location.

  • path (str, default: "" ) –

    Spec path of the offending value, e.g. "layers[0].size" or "theme.axis_text.size"; empty when the error concerns the whole spec.

  • reason (str, default: "" ) –

    What was wrong and what is accepted, without the location.

Examples:

>>> from ggrust import *
>>> from ggrust.errors import SpecError
>>> data = {"weight": [2.6, 2.9, 3.2, 3.4, 3.6, 4.1], "mpg": [31, 28, 24, 22, 19, 15]}
>>> chart = ggplot(data, aes("weight", "mpg")) + geom_point(shape="star")
>>> try:
...     svg = chart.to_svg()
... except SpecError as error:
...     print(error.path)
layers[0].shape

density_outline

density_outline(
    points: Pairs,
    *,
    level: float = 0.1,
    padding: float = 1.0,
    grid_size: int = 200,
    bandwidth: Bandwidth = "scott",
) -> pl.DataFrame

Outline a 2D point cloud where its kernel density falls to level of the peak.

The density is a Gaussian kernel estimate with a full covariance (the data covariance scaled by the bandwidth factor, as scipy.stats.gaussian_kde). It is evaluated on a grid_size by grid_size grid spanning the points' bounding box widened by padding on every side, and the contour at level * peak is traced by marching squares, as skimage.measure.find_contours. The vertices match that scipy and scikit-image recipe to about 1e-15. Draw the result with geom_path.

Parameters:

  • points ((n, 2) array) –

    Point coordinates, one (x, y) row per point: a numpy array, nested sequences or a two-column data frame. Rows with a non-finite coordinate (or a masked entry) are dropped; at least 3 points must remain, and they must not lie on one line.

  • level (float, default: 0.1 ) –

    Outline level as a fraction of the peak density, between 0 and 1 (exclusive). Lower values draw a wider outline that takes in sparse points.

  • padding (float, default: 1.0 ) –

    Space added to each side of the points' bounding box before gridding, in data units. Too little padding cuts low-level outlines open at the grid edge.

  • grid_size (int, default: 200 ) –

    Grid points per axis, from 2 to 3162. Higher values give smoother outlines; the work grows with grid_size ** 2 times the number of points.

  • bandwidth ((scott, silverman), default: 'scott' ) –

    Kernel bandwidth: Scott's or Silverman's rule (scipy's bw_method), or a positive factor that multiplies the data standard deviations.

Returns:

  • DataFrame –

    One row per outline vertex: x and y (Float64) and path (UInt32, the outline number from 0, for aes(group="path")). Closed outlines repeat their first vertex. A cloud with several dense spots can give several outlines; a level the density never crosses gives none.

Raises:

  • SpecError –

    If points is not an (n, 2) array of numbers, fewer than 3 finite points remain, the points lie on one line, or an option is out of range.

Examples:

>>> import math
>>> from ggrust import *
>>> points = [[math.cos(i) * (i % 7) / 7, math.sin(i) * (i % 5) / 5] for i in range(200)]
>>> outline = density_outline(points, level=0.1, padding=0.5, grid_size=80)
>>> outline.columns
['x', 'y', 'path']
>>> outline["path"].unique().to_list()
[0]
>>> plot = (
...     ggplot()
...     + geom_point(aes("x", "y"), data={"x": [p[0] for p in points],
...                                       "y": [p[1] for p in points]}, size=1)
...     + geom_path(aes("x", "y", group="path"), data=outline, linetype="dashed")
...     + coord_fixed()
... )