How to Create a Graph Like Criterion?

I'm wanting to create my own graph like this one that criterion created, but based on my own list of numbers.

image

I'm using the plotters library ( which is amazing, thank you @381 :sparkles: :slight_smile: ), but I don't know how to do the math to get the density value used for the y axis. Does anybody know of an easy crate or else just a code snippet that would give me that density value for a slice of f64s?

Not sure what you mean by "density" value? Do you mean the frequency of a value in a given set? Since these are f64s, you can have rounding errors, hence I cast to integers so that it additionally satisfies Eq:

use std::collections::HashMap;

const SCALE: f64 = 1.0;

fn main() {
    let given_slice: &[f64] = &[1., 2., 3.];
    
    let mut hashmap = HashMap::<i128, usize>::new();
    
    given_slice
        .iter()
        .for_each(
            |x| {
                let rounded = (x * SCALE) as i128;
                *hashmap
                    .entry(rounded)
                    .or_insert(0)
                    += 1;
            }
        );
}

If you mean something more like calculating the values between the ticks on the Y-axis, then something like this would work:

const NUM_TICKS: f64 = 10.0;

fn main() {
    let given_slice: &[f64] = &[1., 2., 3.];
    
    let mut max = f64::MIN;
    let mut min = f64::MAX;
    
    given_slice
        .iter()
        .copied()
        .for_each(|x| {
            if x > max {
                max = x;
            }
            if x < min {
                min = x;
            }
        });
        
    let size = max - min;
    let distance_ticks = size / NUM_TICKS;
    
    println!("{}", distance_ticks);
}

If I completely misinterpreted your question, please let me know!

I mean what it calls "Density" on the Y axis in that graph. It's also called PDF ( I can't remember what that stands for ). I just found this statrs::distribution::Normal struct that should calculate it for me I think ( assuming I give it the standard distribution which I also have to figure out how to calculate ). But there are a bunch of different distributions and I don't know which one to use so I just guessed Normal seemed pretty, "normal" :stuck_out_tongue_winking_eye:.

I'm looking to render that shaded blue area which somehow represents the probability that a value will fall within that range. Does that make any sense? I have no background in statistics. :slight_smile:

Ah, alright.

Unfortunately I'm not knowledgeable in statistics beyond basic number processing, so I can't help you choose a good distribution or an accurate one. Sorry for the let down.

No problem, thanks for trying. :+1: :smile:

1 Like

It looks like criterion's logic for calculating those densities (PDF = probability density function) is in this module:

The actual smoothing seems to be done by calling out to criterion::kde::sweep_and_estimate:

That in turn uses some functionality from criterion::stats::univariate::kde. Ultimately this is performing kernel density estimation using a gaussian kernel, which is standard enough that you may be able to find a crate to do it for you. (Unfortunately the stats and plot modules are private so you can't just import from criterion.)

2 Likes

Hey, actually I just found the criterion_stats library, which had what I needed in it:

It's not all fancy interpolated, but I don't really care. It gets the message across.

let dist = criterion_stats::Distribution::from(frame_avgs.clone().into_boxed_slice());
let points: Vec<(f64, f64)> = frame_avgs
                .iter()
                .map(|&x| (x, dist.p_value(x, &Tails::Two)))
                .collect();

Not sure what Tails::Two is compared to the alternative Tails::One, but oh well. :man_shrugging:

2 Likes

Haha! Now I've got some more neat graphs.

Plotters is awesome. Adding the text to the graph for the percentage difference and the average line was super easy.

1 Like