Skip to main content

High-performance LOWESS smoothing for Python.

Project description

LOWESS Project

License Docs Crates.io PyPI Conda R-universe npm

One LOWESS to Rule Them All
One LOWESS to Rule Them All

The fastest, most robust, and most feature-complete language-agnostic LOWESS (Locally Weighted Scatterplot Smoothing) implementation for Rust, Python, R, Julia, JavaScript, C++, and WebAssembly.

[!IMPORTANT]

The lowess-project contains a complete ecosystem for LOWESS smoothing:

LOESS vs. LOWESS

Feature LOESS (This Crate) LOWESS
Polynomial Degree Linear, Quadratic, Cubic, Quartic Linear (Degree 1)
Dimensions Multivariate (n-D support) Univariate (1-D only)
Flexibility High (Distance metrics) Standard
Complexity Higher (Matrix inversion) Lower (Weighted average/slope)

[!TIP] Note: For a LOESS implementation, use loess-project.


Documentation

[!NOTE]

📚 View the full documentation

Why this package?

Speed

The lowess project crushes the competition in terms of speed, wether in single-threaded or multi-threaded parallel execution.

Speedup relative to Python's statsmodels.lowess (higher is better):

Category statsmodels R (stats) Serial Parallel GPU
Clustered 163ms 83× 203× 433× 32×
Constant Y 134ms 92× 212× 410× 18×
Delta (large–none) 105ms 16×
Extreme Outliers 489ms 106× 201× 388× 29×
Financial (500–10K) 106ms 105× 252× 293× 12×
Fraction (0.05–0.67) 221ms 104× 228× 391× 22×
Genomic (1K–50K) 1833ms 20× 95×
High Noise 435ms 133× 134× 375× 32×
Iterations (0–10) 204ms 115× 224× 386× 18×
Scale (1K–50K) 1841ms 264× 487× 581× 98×
Scientific (500–10K) 167ms 109× 205× 314× 15×
Scale Large* (100K–2M) 1.4× 0.3×

*Scale Large benchmarks are relative to Serial (statsmodels cannot handle these sizes)

The numbers are the average across a range of scenarios for each category (e.g., Delta from none, to small, medium, and large).

Robustness

This implementation is more robust than R's lowess and Python's statsmodels due to two key design choices:

MAD-Based Scale Estimation:

For robustness weight calculations, this crate uses Median Absolute Deviation (MAD) for scale estimation:

s = median(|r_i - median(r)|)

In contrast, statsmodels and R's lowess uses the median of absolute residuals (MAR):

s = median(|r_i|)
  • MAD is a breakdown-point-optimal estimator—it remains valid even when up to 50% of data are outliers.
  • The median-centering step removes asymmetric bias from residual distributions.
  • MAD provides consistent outlier detection regardless of whether residuals are centered around zero.

Boundary Padding:

This crate applies a range of different boundary policies at dataset edges:

  • Extend: Repeats edge values to maintain local neighborhood size.
  • Reflect: Mirrors data symmetrically around boundaries.
  • Zero: Pads with zeros (useful for signal processing).
  • NoBoundary: Original Cleveland behavior

statsmodels and R's lowess do not apply boundary padding, which can lead to:

  • Biased estimates near boundaries due to asymmetric local neighborhoods.
  • Increased variance at the edges of the smoothed curve.

Features

A variety of features, supporting a range of use cases:

Feature This package statsmodels R (stats)
Kernel 7 options only Tricube only Tricube
Robustness Weighting 3 options only Huber only Huber
Scale Estimation 2 options only MAR only MAR
Boundary Padding 4 options no padding no padding
Zero Weight Fallback 3 options no no
Auto Convergence yes no no
Online Mode yes no no
Streaming Mode yes no no
Confidence Intervals yes no no
Prediction Intervals yes no no
Cross-Validation 2 options no no
Parallel Execution yes no no
GPU Acceleration yes* no no
no-std Support yes no no

* GPU acceleration is currently in beta and may not be available on all platforms.

Validation

All implementations are numerical twins of R's lowess:

Aspect Status Details
Accuracy ✅ EXACT MATCH Max diff < 1e-12 across all scenarios
Consistency ✅ PERFECT Multiple scenarios pass with strict tolerance
Robustness ✅ VERIFIED Robust smoothing matches R exactly

Installation

Currently available for R, Python, Julia, and Rust:

R (from R-universe, recommended):

install.packages("rfastlowess", repos = "https://thisisamirv.r-universe.dev")

Python (from PyPI):

pip install fastlowess

Or from conda-forge:

conda install -c conda-forge fastlowess

Rust (lowess, no_std compatible):

[dependencies]
lowess = "0.99"

Rust (fastLowess, parallel + GPU):

[dependencies]
fastLowess = { version = "0.99", features = ["cpu"] }

Julia (from Julia General Registry):

using Pkg
Pkg.add("fastLowess")

Node.js (from npm):

npm install fastlowess

WebAssembly (from npm):

npm install fastlowess-wasm

Or via CDN:

<script type="module">
  import init, { smooth } from 'https://unpkg.com/fastlowess-wasm@latest';
  await init();
</script>

C++ (build from source):

make cpp
# Links against libfastlowess_cpp.so

Quick Example

R:

library(rfastlowess)

x <- c(1, 2, 3, 4, 5)
y <- c(2.0, 4.1, 5.9, 8.2, 9.8)

model <- Lowess(fraction = 0.5, iterations = 3)
result <- model$fit(x, y)
print(result$y)

Python:

import fastlowess as fl
import numpy as np

x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([2.0, 4.1, 5.9, 8.2, 9.8])

result = fl.smooth(x, y, fraction=0.5, iterations=3)
print(result["y"])

Rust:

use lowess::prelude::*;

let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = vec![2.0, 4.1, 5.9, 8.2, 9.8];

let model = Lowess::new()
    .fraction(0.5)
    .iterations(3)
    .adapter(Batch)
    .build()?;

let result = model.fit(&x, &y)?;
println!("{}", result);

Julia:

using fastlowess

x = [1.0, 2.0, 3.0, 4.0, 5.0]
y = [2.0, 4.1, 5.9, 8.2, 9.8]

result = fit(Lowess(fraction=0.5, iterations=3), x, y)
println(result.y)

Node.js:

const { Lowess } = require('fastlowess');

const x = [1.0, 2.0, 3.0, 4.0, 5.0];
const y = [2.0, 4.1, 5.9, 8.2, 9.8];

const model = new Lowess({ fraction: 0.5, iterations: 3 });
const result = model.fit(x, y);
console.log(result.y);

WebAssembly:

import init, { smooth } from 'fastlowess-wasm';

await init();

const x = new Float64Array([1.0, 2.0, 3.0, 4.0, 5.0]);
const y = new Float64Array([2.0, 4.1, 5.9, 8.2, 9.8]);

const result = smooth(x, y, { fraction: 0.5, iterations: 3 });
console.log(result.y);

C++:

#include <fastlowess.hpp>

std::vector<double> x = {1.0, 2.0, 3.0, 4.0, 5.0};
std::vector<double> y = {2.0, 4.1, 5.9, 8.2, 9.8};

fastlowess::LowessOptions options;
options.fraction = 0.5;
options.iterations = 3;

fastlowess::Lowess model(options);
auto result = model.fit(x, y);

for (double val : result.y_vector()) std::cout << val << " ";

API Reference

R:

Lowess(
    fraction = 0.5,
    iterations = 3L,
    delta = 0.01,
    weight_function = "tricube",
    robustness_method = "bisquare",
    zero_weight_fallback = "use_local_mean",
    boundary_policy = "extend",
    confidence_intervals = 0.95,
    prediction_intervals = 0.95,
    return_diagnostics = TRUE,
    return_residuals = TRUE,
    return_robustness_weights = TRUE,
    cv_fractions = c(0.3, 0.5, 0.7),
    cv_method = "kfold",
    cv_k = 5L,
    auto_converge = 1e-4,
    parallel = TRUE
)$fit(x, y)

Python:

fastlowess.smooth(
    x, y,
    fraction=0.5,
    iterations=3,
    delta=0.01,
    weight_function="tricube",
    robustness_method="bisquare",
    zero_weight_fallback="use_local_mean",
    boundary_policy="extend",
    confidence_intervals=0.95,
    prediction_intervals=0.95,
    return_diagnostics=True,
    return_residuals=True,
    return_robustness_weights=True,
    cv_fractions=[0.3, 0.5, 0.7],
    cv_method="kfold",
    cv_k=5,
    auto_converge=1e-4,
    parallel=True
)

Rust:

Lowess::new()
    .fraction(0.5)              // Smoothing span (0, 1]
    .iterations(3)              // Robustness iterations
    .delta(0.01)                // Interpolation threshold
    .weight_function(Tricube)   // Kernel selection
    .robustness_method(Bisquare)
    .zero_weight_fallback(UseLocalMean)
    .boundary_policy(Extend)
    .confidence_intervals(0.95)
    .prediction_intervals(0.95)
    .return_diagnostics()
    .return_residuals()
    .return_robustness_weights()
    .cross_validate(KFold(5, &[0.3, 0.5, 0.7]).seed(123))
    .auto_converge(1e-4)
    .adapter(Batch)             // or Streaming, Online
    .parallel(true)             // fastLowess only
    .backend(CPU)               // fastLowess only: CPU or GPU
    .build()?;

Julia:

Lowess(;
    fraction=0.5,
    iterations=3,
    delta=NaN,  # NaN for auto
    weight_function="tricube",
    robustness_method="bisquare",
    zero_weight_fallback="use_local_mean",
    boundary_policy="extend",
    confidence_intervals=NaN,
    prediction_intervals=NaN,
    return_diagnostics=true,
    return_residuals=true,
    return_robustness_weights=true,
    cv_fractions=Float64[], # e.g. [0.3, 0.5]
    cv_method="kfold",
    cv_k=5,
    auto_converge=NaN,
    parallel=true
)

Node.js:

new Lowess({
    fraction: 0.5,
    iterations: 3,
    delta: 0.01,
    weightFunction: "tricube",
    robustnessMethod: "bisquare",
    zeroWeightFallback: "use_local_mean",
    boundaryPolicy: "extend",
    confidenceIntervals: 0.95,
    predictionIntervals: 0.95,
    returnDiagnostics: true,
    returnResiduals: true,
    returnRobustnessWeights: true,
    cvFractions: [0.3, 0.5, 0.7],
    cvMethod: "kfold",
    cvK: 5,
    autoConverge: 1e-4,
    parallel: true
}).fit(x, y)

WebAssembly:

smooth(x, y, {
    fraction: 0.5,
    iterations: 3,
    delta: 0.01,
    weightFunction: "tricube",
    robustnessMethod: "bisquare",
    zeroWeightFallback: "use_local_mean",
    boundaryPolicy: "extend",
    confidenceIntervals: 0.95,
    predictionIntervals: 0.95,
    returnDiagnostics: true,
    returnResiduals: true,
    returnRobustnessWeights: true,
    cvFractions: [0.3, 0.5, 0.7],
    cvMethod: "kfold",
    cvK: 5,
    autoConverge: 1e-4
})

C++:

fastlowess::LowessOptions options;
options.fraction = 0.5;
options.iterations = 3;
options.delta = 0.01;
options.weight_function = "tricube";
options.robustness_method = "bisquare";
options.zero_weight_fallback = "use_local_mean";
options.boundary_policy = "extend";
options.confidence_intervals = 0.95;
options.prediction_intervals = 0.95;
options.return_diagnostics = true;
options.return_residuals = true;
options.return_robustness_weights = true;
options.cv_fractions = {0.3, 0.5, 0.7};
options.cv_method = "kfold";
options.cv_k = 5;
options.auto_converge = 1e-4;
options.parallel = true;

fastlowess::Lowess model(options);
auto result = model.fit(x, y);

Result Structure

R:

result$x, result$y, result$standard_errors
result$confidence_lower, result$confidence_upper
result$prediction_lower, result$prediction_upper
result$residuals, result$robustness_weights
result$diagnostics, result$iterations_used
result$fraction_used, result$cv_scores

Python:

result.x, result.y, result.standard_errors
result.confidence_lower, result.confidence_upper
result.prediction_lower, result.prediction_upper
result.residuals, result.robustness_weights
result.diagnostics, result.iterations_used
result.fraction_used, result.cv_scores

Rust:

pub struct LowessResult<T> {
    pub x: Vec<T>,                           // Sorted x values
    pub y: Vec<T>,                           // Smoothed y values
    pub standard_errors: Option<Vec<T>>,
    pub confidence_lower: Option<Vec<T>>,
    pub confidence_upper: Option<Vec<T>>,
    pub prediction_lower: Option<Vec<T>>,
    pub prediction_upper: Option<Vec<T>>,
    pub residuals: Option<Vec<T>>,
    pub robustness_weights: Option<Vec<T>>,
    pub diagnostics: Option<Diagnostics<T>>,
    pub iterations_used: Option<usize>,
    pub fraction_used: T,
    pub cv_scores: Option<Vec<T>>,
}

Julia:

result.x, result.y, result.standard_errors
result.confidence_lower, result.confidence_upper
result.prediction_lower, result.prediction_upper
result.residuals, result.robustness_weights
result.diagnostics, result.iterations_used
result.fraction_used

Node.js:

result.x, result.y, result.standardErrors
result.confidenceLower, result.confidenceUpper
result.predictionLower, result.predictionUpper
result.residuals, result.robustnessWeights
result.diagnostics, result.iterationsUsed
result.fractionUsed, result.cvScores

WebAssembly:

result.x, result.y, result.standardErrors
result.confidenceLower, result.confidenceUpper
result.predictionLower, result.predictionUpper
result.residuals, result.robustnessWeights
result.diagnostics, result.iterationsUsed
result.fractionUsed, result.cvScores

C++:

result.y_vector()              // std::vector<double>
result.confidence_lower()      // std::vector<double>
result.confidence_upper()      // std::vector<double>
result.prediction_lower()      // std::vector<double>
result.prediction_upper()      // std::vector<double>
result.residuals()             // std::vector<double>
result.robustness_weights()    // std::vector<double>
result.diagnostics()           // Diagnostics struct
result.iterations_used()       // size_t
result.fraction_used()         // double

Contributing

Contributions are welcome! Please see the CONTRIBUTING.md file for more information.

License

Licensed under either of:

at your option.

References

  • Cleveland, W.S. (1979). "Robust Locally Weighted Regression and Smoothing Scatterplots". JASA.
  • Cleveland, W.S. (1981). "LOWESS: A Program for Smoothing Scatterplots". The American Statistician.

Citation

If you use this software in your research, please cite it using the CITATION.cff file or the BibTeX entry below:

@software{lowess_project,
  author = {Valizadeh, Amir},
  title = {LOWESS Project: High-Performance Locally Weighted Scatterplot Smoothing},
  year = {2026},
  url = {https://github.com/thisisamirv/lowess-project},
  license = {MIT OR Apache-2.0}
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastlowess-0.99.9.tar.gz (48.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fastlowess-0.99.9-cp38-abi3-win_amd64.whl (324.5 kB view details)

Uploaded CPython 3.8+Windows x86-64

fastlowess-0.99.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (452.7 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

fastlowess-0.99.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (438.4 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

fastlowess-0.99.9-cp38-abi3-macosx_11_0_arm64.whl (401.1 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

fastlowess-0.99.9-cp38-abi3-macosx_10_12_x86_64.whl (425.7 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file fastlowess-0.99.9.tar.gz.

File metadata

  • Download URL: fastlowess-0.99.9.tar.gz
  • Upload date:
  • Size: 48.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastlowess-0.99.9.tar.gz
Algorithm Hash digest
SHA256 95ca63ee1d808e9ef926d98d5738fe69d9b047ff4917a5aa77f3b57a42a792a8
MD5 2e1b2d13bf1838070ac1d60ca6485072
BLAKE2b-256 5ccbcc726a6294090c675b4ac360a742c04944d75229cc7ab6417eb1a1b37254

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9.tar.gz:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastlowess-0.99.9-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: fastlowess-0.99.9-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 324.5 kB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastlowess-0.99.9-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 f638f4ba2664920c0712842a0cad3f624a78f5cc3e8656a3344f7937a681604b
MD5 8b850fc9e5efef175dab6791103e7642
BLAKE2b-256 286885784d29c81471b356295f5a9e65250a38338b7275b5d974e1792f3fe881

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9-cp38-abi3-win_amd64.whl:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastlowess-0.99.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fastlowess-0.99.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e04f1175420b87108fa6bed21b0de635b2ee68dddd2d0203ab8518d63b515068
MD5 268a22144811536e64d4f137aeec5c61
BLAKE2b-256 f1bc5401fd11033d631ac701fac25f3f1779efb0b38b1a5eba5f6cca581590d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastlowess-0.99.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fastlowess-0.99.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 2cfdb422d8a2b06e5100ec33e000e9913f77ecd5c00d1449b7ee243802769d42
MD5 1726ba49057a3977d19bc8c358249360
BLAKE2b-256 498a0ac1e73dd4125c7af50523f5157c1cceaea14b78ca64b773247f69b90a7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastlowess-0.99.9-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fastlowess-0.99.9-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8d1bc09d5471587b1cf6d1c831f247217b694664c2d13364de8e936fa65d6421
MD5 519468de39acb3e176b5db261e5a3846
BLAKE2b-256 4f2b18147d4b7f2ce6d4d168a6a3e2eae2c1c61d2ae68526740d691dac0505e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastlowess-0.99.9-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for fastlowess-0.99.9-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3d2adca24ba799d29e343f08257ab6f1b328fdb33a2c33dfcaed19085550dc2a
MD5 6d962752425937ff367b458528e4c043
BLAKE2b-256 76ef3d04bc28c0a5db74312db76e4c6ba7b982665c3bf58da3fe1ed22f5cd283

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-0.99.9-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: release-pypi.yml on thisisamirv/lowess-project

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page