├── assets ├── kernel.png └── univariate.png ├── src ├── kde │ ├── mod.rs │ ├── multivariate.rs │ └── univariate.rs ├── bandwidth │ ├── mod.rs │ ├── scott.rs │ └── silverman.rs ├── kernel │ ├── mod.rs │ ├── uniform.rs │ ├── triangular.rs │ ├── epanechnikov.rs │ ├── normal.rs │ ├── tricube.rs │ ├── logistic.rs │ ├── triweight.rs │ ├── quartic.rs │ ├── sigmoid.rs │ ├── cosine.rs │ └── silverman.rs ├── float.rs ├── lib.rs └── internal.rs ├── .gitignore ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE ├── README.md └── examples ├── univariate.rs └── kernel.rs /assets/kernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seatonullberg/kernel-density-estimation/HEAD/assets/kernel.png -------------------------------------------------------------------------------- /assets/univariate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seatonullberg/kernel-density-estimation/HEAD/assets/univariate.png -------------------------------------------------------------------------------- /src/kde/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod multivariate; 2 | pub mod univariate; 3 | 4 | use crate::internal::Sealed; 5 | 6 | /// Representation of a kernel density estimate with custom bandwith selector and kernel function. 7 | #[derive(Clone, Debug)] 8 | pub struct KernelDensityEstimator { 9 | observations: T, 10 | bandwidth: B, 11 | kernel: K, 12 | } 13 | 14 | impl Sealed for KernelDensityEstimator {} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | /Cargo.lock 17 | -------------------------------------------------------------------------------- /src/kde/multivariate.rs: -------------------------------------------------------------------------------- 1 | use crate::float::KDEFloat; 2 | use crate::internal::Sealed; 3 | 4 | pub type Matrix2D = nalgebra::DMatrix; 5 | 6 | pub trait MultivariateKDE: Sealed { 7 | fn new>>(observations: T, bandwidth: B, kernel: K) -> Self; 8 | fn pdf(&self, dataset: &Matrix2D) -> Vec; 9 | fn cdf(&self, dataset: &Matrix2D) -> Vec; 10 | fn sample(&self, dataset: &Matrix2D, n_samples: usize) -> Matrix2D; 11 | } 12 | -------------------------------------------------------------------------------- /src/bandwidth/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bandwidth selection strategies. 2 | 3 | pub mod scott; 4 | pub mod silverman; 5 | 6 | use crate::float::KDEFloat; 7 | 8 | /// Shared behavior for bandwidth selection strategies. 9 | pub trait Bandwidth { 10 | /// Returns a bandwidth value estimated from the points in `data`. 11 | fn bandwidth(&self, data: &[F]) -> F; 12 | } 13 | 14 | impl Bandwidth for T 15 | where 16 | T: Fn(&[F]) -> F, 17 | F: KDEFloat, 18 | { 19 | fn bandwidth(&self, data: &[F]) -> F { 20 | self(data) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel-density-estimation" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["Seaton Ullberg "] 6 | repository = "https://github.com/seatonullberg/kernel-density-estimation" 7 | description = "Kernel density estimation in Rust." 8 | license = "MIT" 9 | keywords = [ 10 | "probability-density", 11 | "non-parametric", 12 | ] 13 | categories = [ 14 | "algorithms", 15 | "mathematics", 16 | ] 17 | 18 | [dependencies] 19 | fastrand = "1.8" 20 | nalgebra = "0.31" 21 | num-traits = "0.2" 22 | 23 | [dev-dependencies] 24 | approx = "0.5" 25 | criterion = "0.4" 26 | plotly = "0.8" 27 | rand = "0.8" 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.0] - 2024-03-22 10 | 11 | ### Added 12 | 13 | - Many new kernel functions added by @ndcroos in #4 resolves #1. 14 | 15 | ### Fixed 16 | 17 | - Fix potential API incompatibility with `f64` feature (#3). 18 | 19 | ### Removed 20 | 21 | - `f64` feature. 22 | 23 | ## [0.1.0] - 2022-10-16 24 | Initial release with support for univariate distributions. 25 | 26 | Bandwidth Selection 27 | * Scott's Rule 28 | * Silverman's Rule 29 | 30 | Kernel Functions 31 | * Epanechnikov 32 | * Normal (Gaussian) 33 | * Uniform -------------------------------------------------------------------------------- /src/kernel/mod.rs: -------------------------------------------------------------------------------- 1 | //! Kernel functions. 2 | 3 | pub mod cosine; 4 | pub mod epanechnikov; 5 | pub mod logistic; 6 | pub mod normal; 7 | pub mod quartic; 8 | pub mod sigmoid; 9 | pub mod silverman; 10 | pub mod triangular; 11 | pub mod tricube; 12 | pub mod triweight; 13 | pub mod uniform; 14 | 15 | use crate::float::KDEFloat; 16 | 17 | /// Shared behavior for kernel functions. 18 | /// 19 | /// Well-behaved kernel functions exhibit three properties: 20 | /// 1) The function is non-negative and real-valued. 21 | /// 2) The function should integrate to 1. 22 | /// 3) The function should be symmetrical. 23 | pub trait Kernel { 24 | /// Returns the probability density function for a value `x`. 25 | fn pdf(&self, x: F) -> F; 26 | } 27 | 28 | impl Kernel for T 29 | where 30 | T: Fn(F) -> F, 31 | F: KDEFloat, 32 | { 33 | fn pdf(&self, x: F) -> F { 34 | self(x) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/bandwidth/scott.rs: -------------------------------------------------------------------------------- 1 | //! Scott's rule for bandwidth selection. 2 | 3 | use crate::bandwidth::Bandwidth; 4 | use crate::float::{float, KDEFloat}; 5 | use crate::internal::variance; 6 | 7 | /// Scott's rule for bandwidth selection. 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct Scott; 10 | 11 | impl Bandwidth for Scott { 12 | fn bandwidth(&self, data: &[F]) -> F { 13 | let prefactor = float!(1.06); 14 | let n = float!(data.len()); 15 | let var = variance(data); 16 | let numerator = prefactor * var.sqrt(); 17 | let denominator = float!(n.powf(float!(0.2))); 18 | numerator / denominator 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::{Bandwidth, Scott}; 25 | use approx::*; 26 | 27 | #[test] 28 | fn scott() { 29 | let data = vec![1.0, 1.5, 2.0, 2.5, 3.0]; 30 | let res = Scott.bandwidth(&data); 31 | assert_relative_eq!(res, 0.60736, epsilon = 1.0e-5); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/kernel/uniform.rs: -------------------------------------------------------------------------------- 1 | //! Uniform kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | /// Uniform kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Uniform; 9 | 10 | impl Kernel for Uniform { 11 | fn pdf(&self, x: F) -> F { 12 | if x.abs() > F::one() { 13 | F::zero() 14 | } else { 15 | float!(0.5) 16 | } 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::Uniform; 23 | use crate::kernel::Kernel; 24 | use approx::*; 25 | 26 | #[test] 27 | fn uniform() { 28 | let kernel = Uniform; 29 | 30 | let x = 0.0; 31 | let res = kernel.pdf(x); 32 | assert_relative_eq!(res, 0.5); 33 | 34 | let x = 2.0; 35 | let res = kernel.pdf(x); 36 | assert_relative_eq!(res, 0.0); 37 | 38 | let x = -2.0; 39 | let res = kernel.pdf(x); 40 | assert_relative_eq!(res, 0.0); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/kernel/triangular.rs: -------------------------------------------------------------------------------- 1 | //! Triangular kernel function. 2 | 3 | use crate::float::KDEFloat; 4 | use crate::kernel::Kernel; 5 | 6 | /// Triangular kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Triangular; 9 | 10 | impl Kernel for Triangular { 11 | fn pdf(&self, x: F) -> F { 12 | let abs_x = x.abs(); 13 | if abs_x <= F::one() { 14 | F::one() - abs_x 15 | } else { 16 | F::zero() 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::Triangular; 24 | use crate::kernel::Kernel; 25 | use approx::*; 26 | 27 | #[test] 28 | fn triangular() { 29 | let kernel = Triangular; 30 | 31 | let x = -1.0; 32 | let res = kernel.pdf(x); 33 | assert_relative_eq!(res, 0.0); 34 | 35 | let x = 0.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 1.0); 38 | 39 | let x = 1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/kernel/epanechnikov.rs: -------------------------------------------------------------------------------- 1 | //! Epanechnikov kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | /// Epanechnikov kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Epanechnikov; 9 | 10 | impl Kernel for Epanechnikov { 11 | fn pdf(&self, x: F) -> F { 12 | let term = F::one() - x.powi(2); 13 | if term > F::zero() { 14 | float!(3.0 / 4.0) * term 15 | } else { 16 | F::zero() 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::Epanechnikov; 24 | use crate::kernel::Kernel; 25 | use approx::*; 26 | 27 | #[test] 28 | fn epanechnikov() { 29 | let kernel = Epanechnikov; 30 | 31 | let x = 0.0; 32 | let res = kernel.pdf(x); 33 | assert_relative_eq!(res, 0.75); 34 | 35 | let x = -1.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 0.0); 38 | 39 | let x = 1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Seaton Ullberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/kernel/normal.rs: -------------------------------------------------------------------------------- 1 | //! Normal (Gaussian) kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | use std::f64::consts::PI; 7 | 8 | /// Normal (Gaussian) kernel function. 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Normal; 11 | 12 | impl Kernel for Normal { 13 | fn pdf(&self, x: F) -> F { 14 | let frac_sqrt2pi = F::one() / F::sqrt(float!(2.0) * float!(PI)); 15 | let exponent = float!(-1.0 / 2.0) * x.powi(2); 16 | frac_sqrt2pi * exponent.exp() 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::Normal; 23 | use crate::kernel::Kernel; 24 | use approx::*; 25 | 26 | #[test] 27 | fn normal() { 28 | let kernel = Normal; 29 | 30 | let x = 0.0; 31 | let res = kernel.pdf(x); 32 | assert_relative_eq!(res, 0.39894, epsilon = 1.0e-5); 33 | 34 | let x = -1.0; 35 | let res = kernel.pdf(x); 36 | assert_relative_eq!(res, 0.24197, epsilon = 1.0e-5); 37 | 38 | let x = 1.0; 39 | let res = kernel.pdf(x); 40 | assert_relative_eq!(res, 0.24197, epsilon = 1.0e-5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/bandwidth/silverman.rs: -------------------------------------------------------------------------------- 1 | //! Silverman's rule for bandwidth selection. 2 | 3 | use crate::bandwidth::Bandwidth; 4 | use crate::float::{float, KDEFloat}; 5 | use crate::internal::{interquartile_range, variance}; 6 | 7 | /// Silverman's rule for bandwidth selection. 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct Silverman; 10 | 11 | impl Bandwidth for Silverman { 12 | fn bandwidth(&self, data: &[F]) -> F { 13 | let var = variance(data); 14 | let var_term = var.sqrt(); 15 | let iqr = interquartile_range(data); 16 | let iqr_term = iqr / float!(1.349); 17 | let n = float!(data.len()); 18 | let m = if var_term < iqr_term { 19 | var_term 20 | } else { 21 | iqr_term 22 | }; 23 | let numerator = float!(0.9) * m; 24 | let denominator = n.powf(float!(0.2)); 25 | numerator / denominator 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::{Bandwidth, Silverman}; 32 | use approx::*; 33 | 34 | #[test] 35 | fn silverman() { 36 | let data = vec![1.0, 1.5, 2.0, 2.5, 3.0]; 37 | let res = Silverman.bandwidth(&data); 38 | assert_relative_eq!(res, 0.51568, epsilon = 1.0e-5); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/kernel/tricube.rs: -------------------------------------------------------------------------------- 1 | //! Tricube kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | /// Tricube kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Tricube; 9 | 10 | impl Kernel for Tricube { 11 | fn pdf(&self, x: F) -> F { 12 | let abs_x = x.abs(); 13 | if abs_x <= F::one() { 14 | float!(70.0 / 81.0) * (F::one() - abs_x.powi(3)).powi(3) 15 | } else { 16 | F::zero() 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::Tricube; 24 | use crate::kernel::Kernel; 25 | use approx::*; 26 | 27 | #[test] 28 | fn tricube() { 29 | let kernel = Tricube; 30 | 31 | let x = 0.0; 32 | let res = kernel.pdf(x); 33 | assert_relative_eq!(res, 0.86419, epsilon = 1.0e-5); 34 | 35 | let x = -1.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 38 | 39 | let x = 1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 42 | 43 | let x = 0.5; 44 | let res = kernel.pdf(x); 45 | assert_relative_eq!(res, 0.57895, epsilon = 1.0e-5); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/kernel/logistic.rs: -------------------------------------------------------------------------------- 1 | //! Logistic kernel function. 2 | 3 | use crate::float::{float, primitive, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | use std::f64::consts::E; 7 | 8 | /// Logistic kernel function. 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Logistic; 11 | 12 | impl Kernel for Logistic { 13 | fn pdf(&self, x: F) -> F { 14 | let exp_neg_x = E.powf(primitive!(-x)); 15 | let exp_x = E.powf(primitive!(x)); 16 | float!(1.0 / (exp_neg_x + 2.0 + exp_x)) 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::Logistic; 23 | use crate::kernel::Kernel; 24 | use approx::*; 25 | 26 | #[test] 27 | fn logistic() { 28 | let kernel = Logistic; 29 | 30 | let x = 0.0; 31 | let res = kernel.pdf(x); 32 | assert_relative_eq!(res, 0.25, epsilon = 1.0e-5); 33 | 34 | let x = -1.0; 35 | let res = kernel.pdf(x); 36 | assert_relative_eq!(res, 0.19661, epsilon = 1.0e-5); 37 | 38 | let x = 1.0; 39 | let res = kernel.pdf(x); 40 | assert_relative_eq!(res, 0.19661, epsilon = 1.0e-5); 41 | 42 | let x = 0.5; 43 | let res = kernel.pdf(x); 44 | assert_relative_eq!(res, 0.23500, epsilon = 1.0e-5); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/kernel/triweight.rs: -------------------------------------------------------------------------------- 1 | //! Triweight kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | /// Triweight kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Triweight; 9 | 10 | impl Kernel for Triweight { 11 | fn pdf(&self, x: F) -> F { 12 | let abs_x = x.abs(); 13 | if abs_x <= F::one() { 14 | float!(35.0 / 32.0) * (F::one() - x.powi(2)).powi(3) 15 | } else { 16 | F::zero() 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::Triweight; 24 | use crate::kernel::Kernel; 25 | use approx::*; 26 | 27 | #[test] 28 | fn triweight() { 29 | let kernel = Triweight; 30 | 31 | let x = 0.0; 32 | let res = kernel.pdf(x); 33 | assert_relative_eq!(res, 1.09375, epsilon = 1.0e-5); 34 | 35 | let x = -1.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 38 | 39 | let x = 1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 42 | 43 | let x = 0.5; 44 | let res = kernel.pdf(x); 45 | assert_relative_eq!(res, 0.46143, epsilon = 1.0e-5); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/kernel/quartic.rs: -------------------------------------------------------------------------------- 1 | //! Quartic (biweight) kernel function. 2 | 3 | use crate::float::{float, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | /// Quartic (biweight) kernel function. 7 | #[derive(Clone, Copy, Debug)] 8 | pub struct Quartic; 9 | 10 | impl Kernel for Quartic { 11 | fn pdf(&self, x: F) -> F { 12 | let abs_x = x.abs(); 13 | if abs_x <= F::one() { 14 | float!(15.0 / 16.0) * (F::one() - x.powi(2)).powi(2) 15 | } else { 16 | F::zero() 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::Quartic; 24 | use crate::kernel::Kernel; 25 | use approx::*; 26 | 27 | #[test] 28 | fn quartic() { 29 | let kernel = Quartic; 30 | 31 | let x = 0.0; 32 | let res = kernel.pdf(x); 33 | assert_relative_eq!(res, 0.93750, epsilon = 1.0e-5); 34 | 35 | let x = -1.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 38 | 39 | let x = 1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 42 | 43 | let x = 0.5; 44 | let res = kernel.pdf(x); 45 | assert_relative_eq!(res, 0.52734, epsilon = 1.0e-5); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/kernel/sigmoid.rs: -------------------------------------------------------------------------------- 1 | //! Sigmoid kernel function. 2 | 3 | use crate::float::{float, primitive, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | use std::f64::consts::{E, FRAC_2_PI}; 7 | 8 | /// Sigmoid kernel function. 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Sigmoid; 11 | 12 | impl Kernel for Sigmoid { 13 | fn pdf(&self, x: F) -> F { 14 | let numerator = float!(FRAC_2_PI); 15 | let denominator = float!(E.powf(primitive!(x)) + E.powf(primitive!(-x))); 16 | numerator / denominator 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::Sigmoid; 23 | use crate::kernel::Kernel; 24 | use approx::*; 25 | 26 | #[test] 27 | fn sigmoid() { 28 | let kernel = Sigmoid; 29 | 30 | let x = 0.0; 31 | let res = kernel.pdf(x); 32 | assert_relative_eq!(res, 0.31831, epsilon = 1.0e-5); 33 | 34 | let x = -1.0; 35 | let res = kernel.pdf(x); 36 | assert_relative_eq!(res, 0.20628, epsilon = 1.0e-5); 37 | 38 | let x = 1.0; 39 | let res = kernel.pdf(x); 40 | assert_relative_eq!(res, 0.20628, epsilon = 1.0e-5); 41 | 42 | let x = 0.5; 43 | let res = kernel.pdf(x); 44 | assert_relative_eq!(res, 0.28228, epsilon = 1.0e-5); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/kernel/cosine.rs: -------------------------------------------------------------------------------- 1 | //! Cosine kernel function. 2 | 3 | use crate::float::{float, primitive, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; 7 | 8 | /// Cosine kernel function. 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Cosine; 11 | 12 | impl Kernel for Cosine { 13 | fn pdf(&self, x: F) -> F { 14 | let abs_x = x.abs(); 15 | if abs_x <= F::one() { 16 | let res = FRAC_PI_4 * (FRAC_PI_2 * primitive!(abs_x)).cos(); 17 | float!(res) 18 | } else { 19 | F::zero() 20 | } 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::Cosine; 27 | use crate::kernel::Kernel; 28 | use approx::*; 29 | 30 | #[test] 31 | fn cosine() { 32 | let kernel = Cosine; 33 | 34 | let x = 0.0; 35 | let res = kernel.pdf(x); 36 | assert_relative_eq!(res, 0.78539, epsilon = 1.0e-5); 37 | 38 | let x = -1.0; 39 | let res = kernel.pdf(x); 40 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 41 | 42 | let x = 1.0; 43 | let res = kernel.pdf(x); 44 | assert_relative_eq!(res, 0.0, epsilon = 1.0e-5); 45 | 46 | let x = 0.5; 47 | let res = kernel.pdf(x); 48 | assert_relative_eq!(res, 0.55536, epsilon = 1.0e-5); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/kernel/silverman.rs: -------------------------------------------------------------------------------- 1 | //! Silverman kernel function. 2 | 3 | use crate::float::{float, primitive, KDEFloat}; 4 | use crate::kernel::Kernel; 5 | 6 | use std::f64::consts::{E, FRAC_PI_4, SQRT_2}; 7 | 8 | /// Silverman kernel function. 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct SilvermanKernel; 11 | 12 | impl Kernel for SilvermanKernel { 13 | fn pdf(&self, x: F) -> F { 14 | let abs_x = x.abs(); 15 | if abs_x <= F::one() { 16 | let term_a = 0.5 * E.powf(-primitive!(x.abs()) / SQRT_2); 17 | let term_b = ((primitive!(x.abs()) / SQRT_2) + FRAC_PI_4).sin(); 18 | float!(term_a * term_b) 19 | } else { 20 | F::zero() 21 | } 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::SilvermanKernel; 28 | use crate::kernel::Kernel; 29 | use approx::*; 30 | 31 | #[test] 32 | fn silverman_kernel() { 33 | let kernel = SilvermanKernel; 34 | 35 | let x = 0.0; 36 | let res = kernel.pdf(x); 37 | assert_relative_eq!(res, 0.35355, epsilon = 1.0e-5); 38 | 39 | let x = -1.0; 40 | let res = kernel.pdf(x); 41 | assert_relative_eq!(res, 0.24578, epsilon = 1.0e-5); 42 | 43 | let x = 1.0; 44 | let res = kernel.pdf(x); 45 | assert_relative_eq!(res, 0.24578, epsilon = 1.0e-5); 46 | 47 | let x = 0.5; 48 | let res = kernel.pdf(x); 49 | assert_relative_eq!(res, 0.31886, epsilon = 1.0e-5); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/float.rs: -------------------------------------------------------------------------------- 1 | //! Float constraints for generic math. 2 | 3 | use num_traits; 4 | 5 | // Custom Float implementation based on argmin crate: 6 | // https://docs.rs/argmin/latest/argmin/core/trait.ArgminFloat.html 7 | 8 | /// An alias for float types (f32, f64) which 9 | /// combines many commonly required traits. 10 | /// It is automatically implemented for all 11 | /// types which fulfill the trait bounds. 12 | pub trait KDEFloat: 13 | 'static 14 | + num_traits::Float 15 | + num_traits::FloatConst 16 | + num_traits::FromPrimitive 17 | + num_traits::ToPrimitive 18 | + std::fmt::Debug 19 | + std::fmt::Display 20 | + std::iter::Sum 21 | + std::ops::AddAssign 22 | { 23 | } 24 | 25 | /// `KDEFloat` is automatically implemented for all 26 | /// types which fulfill the trait bounds. 27 | impl KDEFloat for T where 28 | T: 'static 29 | + num_traits::Float 30 | + num_traits::FloatConst 31 | + num_traits::FromPrimitive 32 | + num_traits::ToPrimitive 33 | + std::fmt::Debug 34 | + std::fmt::Display 35 | + std::iter::Sum 36 | + std::ops::AddAssign 37 | { 38 | } 39 | 40 | // Macro to simplify primitive to Float conversion. 41 | // Note that the name of the generic parameter MUST be F. 42 | macro_rules! float { 43 | ($x:expr) => { 44 | F::from($x).unwrap() 45 | }; 46 | } 47 | pub(crate) use float; 48 | 49 | // Macro to simplify Float to f64 conversion. 50 | // Note that the argument MUST implement Float. 51 | macro_rules! primitive { 52 | ($x:expr) => { 53 | $x.to_f64().unwrap() 54 | }; 55 | } 56 | pub(crate) use primitive; 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Kernel density estimation in Rust. 2 | //! 3 | //! Kernel density estimation (KDE) is a non-parametric method to estimate the probability 4 | //! density function of a random variable by taking the summation of kernel functions centered 5 | //! on each data point. This crate serves three major purposes based on this idea: 6 | //! 1) Evaluate the probability density function of a random variable. 7 | //! 2) Evaluate the cumulative distribution function of a random variable. 8 | //! 3) Sample data points from the probability density function. 9 | //! 10 | //! An excellent technical description of the method is available 11 | //! [here](https://bookdown.org/egarpor/NP-UC3M/kde-i.html)[^citation]. 12 | //! 13 | //! [^citation]: García-Portugués, E. (2022). Notes for Nonparametric Statistics. 14 | //! Version 6.5.9. ISBN 978-84-09-29537-1. 15 | 16 | #[warn(missing_docs)] 17 | pub mod bandwidth; 18 | pub mod float; 19 | mod internal; 20 | pub mod kde; 21 | pub mod kernel; 22 | 23 | pub mod prelude { 24 | //! `use kernel_density_estimation::prelude::*;` to import all common functionality. 25 | 26 | pub use crate::bandwidth::scott::Scott; 27 | pub use crate::bandwidth::silverman::Silverman; 28 | pub use crate::bandwidth::Bandwidth; 29 | 30 | pub use crate::float::KDEFloat; 31 | 32 | pub use crate::kde::univariate::UnivariateKDE; 33 | pub use crate::kde::KernelDensityEstimator; 34 | 35 | pub use crate::kernel::cosine::Cosine; 36 | pub use crate::kernel::epanechnikov::Epanechnikov; 37 | pub use crate::kernel::logistic::Logistic; 38 | pub use crate::kernel::normal::Normal; 39 | pub use crate::kernel::quartic::Quartic; 40 | pub use crate::kernel::sigmoid::Sigmoid; 41 | pub use crate::kernel::silverman::SilvermanKernel; 42 | pub use crate::kernel::triangular::Triangular; 43 | pub use crate::kernel::tricube::Tricube; 44 | pub use crate::kernel::triweight::Triweight; 45 | pub use crate::kernel::uniform::Uniform; 46 | pub use crate::kernel::Kernel; 47 | } 48 | -------------------------------------------------------------------------------- /src/internal.rs: -------------------------------------------------------------------------------- 1 | use crate::float::{float, primitive, KDEFloat}; 2 | 3 | pub trait Sealed {} 4 | 5 | pub(crate) fn variance(data: &[F]) -> F { 6 | let n = float!(data.len()); 7 | let mean = data.iter().copied().sum::() / n; 8 | let squares_sum = data.iter().map(|&x| (x - mean).powi(2)).sum::(); 9 | squares_sum / (n - F::one()) 10 | } 11 | 12 | pub(crate) fn quantile(data: &[F], tau: F) -> F { 13 | assert!((0.0..=1.0).contains(&primitive!(tau))); 14 | let mut data = data.to_owned(); 15 | data.sort_by(|a, b| a.partial_cmp(b).unwrap()); 16 | let n = float!(data.len()); 17 | let index = F::round(tau * (n + F::one())).to_usize().unwrap(); 18 | data[index - 1] 19 | } 20 | 21 | pub(crate) fn interquartile_range(data: &[F]) -> F { 22 | let upper = float!(0.75); 23 | let lower = float!(0.25); 24 | quantile(data, upper) - quantile(data, lower) 25 | } 26 | 27 | pub(crate) fn cumsum(data: &[F]) -> Vec { 28 | (0..data.len()) 29 | .map(|i| data[..i + 1].iter().copied().sum()) 30 | .collect() 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use approx::*; 36 | 37 | use super::{cumsum, interquartile_range, quantile, variance}; 38 | 39 | #[test] 40 | fn test_variance() { 41 | let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; 42 | let res = variance(&data); 43 | assert_relative_eq!(res, 2.5); 44 | } 45 | 46 | #[test] 47 | fn test_quantile() { 48 | let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; 49 | let res = quantile(&data, 0.5); 50 | assert_relative_eq!(res, 3.0); 51 | } 52 | 53 | #[test] 54 | fn test_interquartile_range() { 55 | let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; 56 | let res = interquartile_range(&data); 57 | assert_relative_eq!(res, 3.0); 58 | } 59 | 60 | #[test] 61 | fn test_cumsum() { 62 | let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; 63 | let res = cumsum(&data); 64 | let target = vec![1.0, 3.0, 6.0, 10.0, 15.0]; 65 | assert_eq!(res, target); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kernel-density-estimation 2 | ![Crates.io](https://img.shields.io/crates/d/kernel-density-estimation) 3 | ![Crates.io](https://img.shields.io/crates/l/kernel-density-estimation) 4 | ![Crates.io](https://img.shields.io/crates/v/kernel-density-estimation) 5 | 6 | Kernel density estimation in Rust. 7 | 8 | Kernel density estimation (KDE) is a non-parametric method to estimate the probability 9 | density function of a random variable by taking the summation of kernel functions centered 10 | on each data point. This crate serves three major purposes based on this idea: 11 | 1) Evaluate the probability density function of a random variable. 12 | 2) Evaluate the cumulative distribution function of a random variable. 13 | 3) Sample data points from the probability density function. 14 | 15 | An excellent technical description of the method is available 16 | [here](https://bookdown.org/egarpor/NP-UC3M/kde-i.html). 17 | 18 | __Note:__ Currently only univariate distributions are supported but multivariate is a goal in the future! 19 | 20 | ## Examples 21 | 22 | __[univariate](examples/univariate.rs)__ - This example showcases the core `pdf`, `cdf`, and `sample` functionalities for a univariate distribution. 23 | ``` 24 | cargo run --example univariate 25 | ``` 26 | ![Univariate Distribution](assets/univariate.png) 27 | 28 | __[kernel](examples/kernel.rs)__ - This example showcases each of the available kernel functions. 29 | ``` 30 | cargo run --example kernel 31 | ``` 32 | ![Kernel Functions](assets/kernel.png) 33 | 34 | ## Roadmap 35 | 36 | Refer to the [milestone issues](https://github.com/seatonullberg/kernel-density-estimation/issues) to see the direction the project is headed in future releases or [CHANGELOG.md](./CHANGELOG.md) to see the changes between each release. 37 | 38 | ## License 39 | 40 | Distributed under the MIT License. See [LICENSE](./LICENSE) for more information. 41 | 42 | ## Acknowledgements 43 | 44 | * Notes for Nonparametric Statistics[^citation] - An excellent technical description of nonparametric methods referenced heavily in the development of this project. 45 | 46 | [^citation]: García-Portugués, E. (2022). Notes for Nonparametric Statistics. 47 | Version 6.5.9. ISBN 978-84-09-29537-1. 48 | -------------------------------------------------------------------------------- /examples/univariate.rs: -------------------------------------------------------------------------------- 1 | use kernel_density_estimation::prelude::*; 2 | 3 | use plotly::color::NamedColor; 4 | use plotly::common::{Marker, Mode}; 5 | use plotly::{Histogram, Plot, Scatter}; 6 | 7 | fn main() { 8 | // Create a distribution. 9 | let observations: Vec = vec![ 10 | 0.9, 1.0, 1.1, 4.8, 4.9, 5.0, 5.1, 5.2, 8.25, 8.5, 8.75, 9.0, 9.25, 9.5, 9.75, 11 | ]; 12 | 13 | // Observations are plotted along the X axis. 14 | let x1 = observations.clone(); 15 | let y1 = vec![0.0; observations.len()]; 16 | 17 | // Use Scott's rule to determine the bandwidth automatically. 18 | let bandwidth = Scott; 19 | // Use the Epanechnikov kernel. 20 | let kernel = Epanechnikov; 21 | // Initialize the KDE. 22 | let kde = KernelDensityEstimator::new(observations, bandwidth, kernel); 23 | 24 | // Create a grid of points to evaluate the KDE on. 25 | let pdf_dataset: Vec = (0..101).into_iter().map(|x| x as f32 * 0.1).collect(); 26 | let cdf_dataset = pdf_dataset.clone(); 27 | let sample_dataset = cdf_dataset.clone(); 28 | 29 | // Evaluate the PDF. 30 | let x2 = pdf_dataset.clone(); 31 | let y2 = kde.pdf(pdf_dataset.as_slice()); 32 | 33 | // Evaluate the CDF. 34 | let x3 = cdf_dataset.clone(); 35 | let y3 = kde.cdf(cdf_dataset.as_slice()); 36 | 37 | // Sample the distribution. 38 | let x4 = kde.sample(sample_dataset.as_slice(), 10_000); 39 | 40 | // Plot the observations. 41 | let trace1 = Scatter::new(x1, y1) 42 | .mode(Mode::Markers) 43 | .marker(Marker::new().color(NamedColor::CornflowerBlue)) 44 | .name("Data"); 45 | // Plot the PDF. 46 | let trace2 = Scatter::new(x2, y2) 47 | .mode(Mode::Lines) 48 | .marker(Marker::new().color(NamedColor::Black)) 49 | .name("PDF"); 50 | // Plot the CDF. 51 | let trace3 = Scatter::new(x3, y3) 52 | .mode(Mode::Lines) 53 | .marker(Marker::new().color(NamedColor::YellowGreen)) 54 | .name("CDF"); 55 | // Plot the samples as a histogram. 56 | let trace4 = Histogram::new(x4) 57 | .hist_norm(plotly::histogram::HistNorm::ProbabilityDensity) 58 | .marker(Marker::new().color(NamedColor::Bisque)) 59 | .n_bins_x(100) 60 | .name("Histogram"); 61 | 62 | // Render the plot. 63 | let mut plot = Plot::new(); 64 | plot.add_trace(trace1); 65 | plot.add_trace(trace2); 66 | plot.add_trace(trace3); 67 | plot.add_trace(trace4); 68 | plot.show(); 69 | } 70 | -------------------------------------------------------------------------------- /src/kde/univariate.rs: -------------------------------------------------------------------------------- 1 | use crate::bandwidth::Bandwidth; 2 | use crate::float::{float, KDEFloat}; 3 | use crate::internal::{cumsum, Sealed}; 4 | use crate::kde::KernelDensityEstimator; 5 | use crate::kernel::Kernel; 6 | 7 | pub trait UnivariateKDE: Sealed { 8 | fn new>>(observations: T, bandwidth: B, kernel: K) -> Self; 9 | fn pdf(&self, dataset: &[F]) -> Vec; 10 | fn cdf(&self, dataset: &[F]) -> Vec; 11 | fn sample(&self, dataset: &[F], n_samples: usize) -> Vec; 12 | } 13 | 14 | impl UnivariateKDE for KernelDensityEstimator, B, K> 15 | where 16 | B: Bandwidth, 17 | K: Kernel, 18 | { 19 | /// Returns an initialized `KernelDensityEstimator`. 20 | fn new>>(observations: T, bandwidth: B, kernel: K) -> Self { 21 | let observations: Vec = observations.into(); 22 | KernelDensityEstimator { 23 | observations, 24 | bandwidth, 25 | kernel, 26 | } 27 | } 28 | 29 | /// Returns the estimated probability density function evaluated at the points in `dataset`. 30 | fn pdf(&self, dataset: &[F]) -> Vec { 31 | let n = float!(self.observations.len()); 32 | let h = self.bandwidth.bandwidth(&self.observations); 33 | let prefactor = F::one() / (n * h); 34 | self.observations 35 | .iter() 36 | .fold(vec![F::zero(); dataset.len()], |mut acc, &x| { 37 | dataset.iter().enumerate().for_each(|(i, &xi)| { 38 | let kernel_arg = (x - xi) / h; 39 | acc[i] += prefactor * self.kernel.pdf(kernel_arg); 40 | }); 41 | acc 42 | }) 43 | } 44 | 45 | /// Returns the estimated cumulative distribution function evaluated at the points in `dataset`. 46 | fn cdf(&self, dataset: &[F]) -> Vec { 47 | let pdf = self.pdf(dataset); 48 | let sum = cumsum(&pdf); 49 | let max = sum[sum.len() - 1]; 50 | sum.iter().map(|&x| x / max).collect() 51 | } 52 | 53 | /// Generates samples from the estimated probability density function. 54 | fn sample(&self, dataset: &[F], n_samples: usize) -> Vec { 55 | let rng = fastrand::Rng::new(); 56 | let cdf = self.cdf(dataset); 57 | (0..n_samples) 58 | .map(|_| { 59 | let rand = float!(rng.f64()); 60 | let mut lower_index = 0; 61 | let mut upper_index = 0; 62 | for (j, elem) in cdf.iter().enumerate() { 63 | if elem < &rand { 64 | lower_index = j; 65 | } else { 66 | upper_index = j; 67 | break; 68 | } 69 | } 70 | let xmin = dataset[lower_index]; 71 | let xmax = dataset[upper_index]; 72 | let xrange = xmax - xmin; 73 | let ymin = cdf[lower_index]; 74 | let ymax = cdf[upper_index]; 75 | let yrange = ymax - ymin; 76 | let yrange_rand = rand - ymin; 77 | let yratio = yrange_rand / yrange; 78 | (xrange * yratio) + xmin 79 | }) 80 | .collect() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/kernel.rs: -------------------------------------------------------------------------------- 1 | use kernel_density_estimation::prelude::*; 2 | 3 | use plotly::common::Mode; 4 | use plotly::{Plot, Scatter}; 5 | 6 | fn main() { 7 | // Create a distribution. 8 | let observations: Vec = vec![1.0, 1.2, 1.4, 1.6, 3.0, 3.4, 3.8]; 9 | 10 | // Observations are plotted as points along the X axis. 11 | let x1 = observations.clone(); 12 | let y1 = vec![0.0; observations.len()]; 13 | 14 | // Each KDE uses a bandwidth of 1. 15 | let bandwidth = Box::new(|_: &[f32]| 1.0); 16 | 17 | // Initialize a KDE for each kernel type. 18 | let kde1 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Epanechnikov); 19 | let kde2 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Normal); 20 | let kde3 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Uniform); 21 | let kde4 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Triangular); 22 | let kde5 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Quartic); 23 | let kde6 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Triweight); 24 | let kde7 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Tricube); 25 | let kde8 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Cosine); 26 | let kde9 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Logistic); 27 | let kde10 = KernelDensityEstimator::new(observations.clone(), &bandwidth, Sigmoid); 28 | let kde11 = KernelDensityEstimator::new(observations.clone(), &bandwidth, SilvermanKernel); 29 | 30 | // Create a grid of points to evaluate each KDE on. 31 | let dataset: Vec = (0..101) 32 | .into_iter() 33 | .map(|x| x as f32 * (5. / 100.)) 34 | .collect(); 35 | 36 | // Evaluate PDFs. 37 | let x2 = dataset.clone(); 38 | let y2 = kde1.pdf(&dataset); 39 | let x3 = dataset.clone(); 40 | let y3 = kde2.pdf(&dataset); 41 | let x4 = dataset.clone(); 42 | let y4 = kde3.pdf(&dataset); 43 | let x5 = dataset.clone(); 44 | let y5 = kde4.pdf(&dataset); 45 | let x6 = dataset.clone(); 46 | let y6 = kde5.pdf(&dataset); 47 | let x7 = dataset.clone(); 48 | let y7 = kde6.pdf(&dataset); 49 | let x8 = dataset.clone(); 50 | let y8 = kde7.pdf(&dataset); 51 | let x9 = dataset.clone(); 52 | let y9 = kde8.pdf(&dataset); 53 | 54 | let x10 = dataset.clone(); 55 | let y10 = kde9.pdf(&dataset); 56 | let x11 = dataset.clone(); 57 | let y11 = kde10.pdf(&dataset); 58 | let x12 = dataset.clone(); 59 | let y12 = kde11.pdf(&dataset); 60 | 61 | // Plot the observations and each of the PDFs. 62 | let trace1 = Scatter::new(x1, y1).mode(Mode::Markers).name("Data"); 63 | let trace2 = Scatter::new(x2, y2).mode(Mode::Lines).name("Epanechnikov"); 64 | let trace3 = Scatter::new(x3, y3).mode(Mode::Lines).name("Normal"); 65 | let trace4 = Scatter::new(x4, y4).mode(Mode::Lines).name("Uniform"); 66 | let trace5 = Scatter::new(x5, y5).mode(Mode::Lines).name("Triangular"); 67 | let trace6 = Scatter::new(x6, y6).mode(Mode::Lines).name("Quartic"); 68 | let trace7 = Scatter::new(x7, y7).mode(Mode::Lines).name("Triweight"); 69 | let trace8 = Scatter::new(x8, y8).mode(Mode::Lines).name("Tricube"); 70 | let trace9 = Scatter::new(x9, y9).mode(Mode::Lines).name("Cosine"); 71 | let trace10 = Scatter::new(x10, y10).mode(Mode::Lines).name("Logistic"); 72 | let trace11 = Scatter::new(x11, y11).mode(Mode::Lines).name("Sigmoid"); 73 | let trace12 = Scatter::new(x12, y12).mode(Mode::Lines).name("Silverman"); 74 | 75 | // Render the plot. 76 | let mut plot = Plot::new(); 77 | plot.add_trace(trace1); 78 | plot.add_trace(trace2); 79 | plot.add_trace(trace3); 80 | plot.add_trace(trace4); 81 | plot.add_trace(trace5); 82 | plot.add_trace(trace6); 83 | plot.add_trace(trace7); 84 | plot.add_trace(trace8); 85 | plot.add_trace(trace9); 86 | plot.add_trace(trace10); 87 | plot.add_trace(trace11); 88 | plot.add_trace(trace12); 89 | plot.show(); 90 | } 91 | --------------------------------------------------------------------------------