Cumulative Density

A cumulative density is the integral of a density curve: the share of the data at or below each value. It rises from 0 to 1 and reads the same as an empirical CDF, but is smoothed by the same kernel used for the 1-D density.

mark_density draws it with configure_density(…).with_cumulative(true); behind the scenes that is still the ordinary recipe

transform_density(cumulative = true)   //  →  (value, cumulative density)
  → mark_area

Cumulative density curve

//! Cumulative density — the convenience `mark_density` with `with_cumulative`.
//!
//! A cumulative density is the integral of a density curve: the share of the
//! data at or below each value. It rises from 0 to 1 and reads like an empirical
//! CDF, but is smoothed by the same kernel used for the 1-D density. For the
//! composition written out by hand, see `distribution_manual.rs`.

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

fn main() -> Result<(), Box<dyn Error>> {
    // Create sample data for demonstration
    let imdb_rating = [
        7.1, 6.8, 7.5, 8.2, 6.9, 7.3, 7.7, 8.0, 6.5, 7.2, 7.9, 6.7, 7.4, 7.8, 8.1, 6.6, 7.0, 7.6,
        8.3, 6.4, 7.2, 7.5, 7.9, 6.8, 7.1, 7.7, 8.0, 6.9, 7.3, 7.6, 7.4, 7.8, 8.2, 6.7, 7.0, 7.5,
        7.9, 6.6, 7.1, 7.7,
    ];
    let genre = [
        "Action", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy",
    ];
    let year = [
        2010, 2011, 2010, 2012, 2011, 2010, 2013, 2012, 2011, 2010, 2014, 2013, 2012, 2011, 2010,
        2015, 2014, 2013, 2012, 2011, 2010, 2016, 2015, 2014, 2013, 2012, 2011, 2010, 2017, 2016,
        2015, 2014, 2013, 2012, 2011, 2010, 2018, 2017, 2016, 2015,
    ];

    chart!(imdb_rating, genre, year)?
        .mark_density()?
        .configure_density(|density| {
            density
                .with_cumulative(true)
                .with_color("purple")
                .with_opacity(0.3)
        })
        .encode(alt::x("imdb_rating"))?
        .with_title("Cumulative Density Estimation")
        .with_x_label("IMDB Rating")
        .with_y_label("Cumulative Density")
        .save("docs/src/images/distribution.svg")?;

    Ok(())
}

Empirical CDF (cumulative frequency)

When you want the exact, unsmoothed version, use a window CumeDist and draw it as a step line. No binning or smoothing is involved — every observation is a step.

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

fn main() -> Result<(), Box<dyn Error>> {
    let ds = load_dataset("iris")?;
    // Create a chart with window transform
    chart!(ds)?
        .transform_window(
            WindowTransform::new(WindowFieldDef::new(
                "sepal_length",
                WindowOnlyOp::CumeDist,
                "ecdf", // This will be the output column name
            ))
            .with_groupbys(["species"])
            .with_normalize(false),
        )?
        .mark_line()?
        .configure_line(|l| l.with_interpolation("step")) // Add step interpolation
        .encode((
            alt::x("sepal_length"),
            alt::y("ecdf"),
            alt::color("species"),
        ))?
        .with_title("Empirical Cumulative Distribution")
        .save("docs/src/images/cumulative_frequency.svg")?;

    Ok(())
}

Both pictures answer "what fraction is below x?", one smooth and one as a step function. Use transform_window(WindowOnlyOp::CumeDist) for the exact ECDF, mark_density with with_cumulative(true) for the kernel-smoothed curve.

Built from the primitives

The same picture without mark_density: set .with_cumulative(true) on DensityTransform and draw the curve with mark_area.

//! Cumulative density — built by hand from the primitives.
//!
//! The same picture as `distribution.rs`, without `mark_density`:
//! `transform_density(cumulative = true)` estimates the curve and `mark_area`
//! draws it.

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

fn main() -> Result<(), Box<dyn Error>> {
    // Create sample data for demonstration
    let imdb_rating = [
        7.1, 6.8, 7.5, 8.2, 6.9, 7.3, 7.7, 8.0, 6.5, 7.2, 7.9, 6.7, 7.4, 7.8, 8.1, 6.6, 7.0, 7.6,
        8.3, 6.4, 7.2, 7.5, 7.9, 6.8, 7.1, 7.7, 8.0, 6.9, 7.3, 7.6, 7.4, 7.8, 8.2, 6.7, 7.0, 7.5,
        7.9, 6.6, 7.1, 7.7,
    ];
    let genre = [
        "Action", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy", "Drama", "Action", "Comedy", "Drama", "Action",
        "Comedy", "Drama", "Action", "Comedy",
    ];
    let year = [
        2010, 2011, 2010, 2012, 2011, 2010, 2013, 2012, 2011, 2010, 2014, 2013, 2012, 2011, 2010,
        2015, 2014, 2013, 2012, 2011, 2010, 2016, 2015, 2014, 2013, 2012, 2011, 2010, 2017, 2016,
        2015, 2014, 2013, 2012, 2011, 2010, 2018, 2017, 2016, 2015,
    ];

    let chart = chart!(imdb_rating, genre, year)?
        .transform_density(
            DensityTransform::new("imdb_rating")
                .with_as("imdb_rating", "cumulative_density")
                .with_cumulative(true),
        )?
        .mark_area()?
        .configure_area(|a| a.with_color("purple").with_opacity(0.3))
        .encode((alt::x("imdb_rating"), alt::y("cumulative_density")))?;

    chart
        .with_title("Cumulative Density Estimation")
        .with_x_label("IMDB Rating")
        .with_y_label("Cumulative Density")
        .save("target/example-output/distribution_manual.svg")?;

    Ok(())
}

See also