1-D Density

A one-dimensional density estimate (a KDE) draws the shape of a numeric column as a smooth curve — the smooth counterpart of a histogram.

mark_density is the convenient way to draw one. It is a composite mark: behind the scenes it expands into the ordinary recipe

transform_density   // numeric column  →  (value, density)
  → mark_area       // draw the curve

so you can always drop to the pieces. The last section writes the same picture out by hand.

Density plot

Read the value from x; each color group gets its own curve.

//! Density plot — the convenience `mark_density`.
//!
//! `mark_density` is a **composite** mark: it expands into the public
//! `transform_density` and the shared area geometry. For the composition written
//! out by hand, see `density_manual.rs`. It is the smooth counterpart of a
//! histogram; a violin is the same curve stood upright and mirrored.

use charton::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ds = load_dataset("iris")?;

    chart!(ds)?
        .mark_density()?
        .configure_density(|density| density.with_opacity(0.5))
        .encode((alt::x("sepal_length"), alt::color("species")))?
        .save("docs/src/images/density.svg")?;

    Ok(())
}

By default the estimator keeps the tails (trim = false), so the curve fades out past the last observation — the geom_density look. A violin is the very same curve stood upright and mirrored; see Violin.

Tuning the curve

Estimating the curve (statistics) and drawing it (geometry) are configured separately. configure_density forwards the estimator options:

MethodMeaning
configure_density(…).with_bandwidth(BandwidthType::Silverman)smoothing rule (Scott by default)
.with_kernel(KernelType::Epanechnikov)smoothing kernel
.with_trim(true)stop each curve at its own data range instead of the shared grid
.with_counts(true)scale each curve by its group size (counts, not density)
.with_cumulative(true)draw the cumulative distribution instead
.with_color(…), .with_opacity(…), .with_stroke(…)the area's visual style

The underlying transform_density replaces the table: it reads the numeric column and emits (value, density) plus one column per groupby field. See Transforms & Columns for the full column contract.

Built from the primitives

The same picture without mark_density: transform_density estimates the curve and mark_area draws it.

//! Density plot — built by hand from the primitives.
//!
//! The same picture as `density.rs`, without `mark_density`. A one-dimensional
//! density estimate (a KDE) draws the shape of a numeric column as a smooth
//! curve: `transform_density` estimates the curve and `mark_area` draws it.
//!
//! ```text
//! transform_density   // numeric column  →  (value, density)
//!   → mark_area       // draw the curve
//! ```

use charton::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ds = load_dataset("iris")?;

    chart!(ds)?
        .transform_density(
            DensityTransform::new("sepal_length")
                .with_as("sepal_length", "density")
                .with_groupbys(["species"]),
        )?
        .mark_area()?
        .configure_area(|a| a.with_opacity(0.5))
        .encode((
            alt::x("sepal_length"),
            alt::y("density"),
            alt::color("species"),
        ))?
        .save("target/example-output/density_manual.svg")?;

    Ok(())
}

See also