Skip to main content

LOWESS Project

lowess fastLowess PyPI R-universe npm Julia WASM C++
fastlowess (Python) libfastlowess (C++) rfastlowess (R)
CI Status at rOpenSci Software Peer Review

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:


Installation

[!NOTE]

Currently available for R, Python, Rust, Julia, Node.js, WebAssembly, and C++. See the Installation Guide for detailed installation instructions.

GPU Backend

In addition to parallel = true (multi-core CPU), the batch Lowess class in every binding — except WebAssembly — as well as the fastLowess Rust crate itself, can run on the GPU via wgpu (Vulkan/Metal/DX12). It's opt-in and worth enabling for high-throughput processing of large datasets (roughly 10k+ points); for smaller inputs the CPU backend is typically faster. StreamingLowess/OnlineLowess remain CPU-only. See the GPU Backend guide for installation instructions and usage.

Documentation

[!NOTE]

📚 View the full documentation


LOESS vs. LOWESS

Feature LOESS LOWESS (This Crate)
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.


Why this package?

Speed

The lowess project beats the competition in terms of speed, whether in single-threaded or multi-threaded parallel execution. It is on average 200-327x faster than Python's statsmodels.lowess and 2-3x faster than R's lowess.

For more details on the performance comparison, see the Benchmarks page.

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

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

API Reference

R:

library(rfastlowess)

model <- Lowess(
    fraction = 0.5,
    iterations = 3L,
    delta = 0.01,
    weight_function = "tricube",
    robustness_method = "bisquare",
    scaling_method = "mad",
    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,
    return_se = TRUE,
    cv_fractions = c(0.3, 0.5, 0.7),
    cv_method = "kfold",
    cv_k = 5L,
    cv_seed = 123L,
    auto_converge = 1e-4,
    parallel = TRUE
)
custom_weights <- rep(1, length(x))
result <- fit(model, x, y, custom_weights = custom_weights)

# Result structure:
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:

from fastlowess import Lowess

model = Lowess(
    fraction=0.5,
    iterations=3,
    delta=0.01,
    weight_function="tricube",
    robustness_method="bisquare",
    scaling_method="mad",
    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,
    return_se=True,
    cv_fractions=[0.3, 0.5, 0.7],
    cv_method="kfold",
    cv_k=5,
    cv_seed=123,
    auto_converge=1e-4,
    parallel=True
)
custom_weights = [1.0] * len(x)
result = model.fit(x, y, custom_weights=custom_weights)

# Result structure:
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:

use lowess::prelude::*;

let model = Lowess::new()
    .fraction(0.5)
    .iterations(3)
    .delta(0.01)
    .weight_function("tricube")
    .robustness_method("bisquare")
    .scaling_method("mad")
    .zero_weight_fallback("use_local_mean")
    .boundary_policy("extend")
    .return_se()
    .confidence_intervals(0.95)
    .prediction_intervals(0.95)
    .return_diagnostics()
    .return_residuals()
    .return_robustness_weights()
    .cv_method("kfold")
    .cv_k(5)
    .cv_fractions(vec![0.3, 0.5, 0.7])
    .cv_seed(123)
    .auto_converge(1e-4)
    .custom_weights(vec![1.0; x.len()])
    .build()?;

let result = model.fit(&x, &y)?;

// Result structure:
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:

using FastLOWESS

model = Lowess(;
    fraction=0.5,
    iterations=3,
    delta=NaN,  # NaN for auto
    weight_function="tricube",
    robustness_method="bisquare",
    scaling_method="mad",
    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,
    return_se=true,
    cv_fractions=Float64[], # e.g. [0.3, 0.5]
    cv_method="kfold",
    cv_k=5,
    cv_seed=123,
    auto_converge=NaN,
    parallel=true
)
custom_weights = ones(length(x))
result = fit(model, x, y; custom_weights=custom_weights)

# Result structure:
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

Node.js:

import { Lowess } from "fastlowess"

const model = new Lowess({
    fraction: 0.5,
    iterations: 3,
    delta: 0.01,
    weight_function: "tricube",
    robustness_method: "bisquare",
    scaling_method: "mad",
    zero_weight_fallback: "use_local_mean",
    boundary_policy: "extend",
    return_se: true,
    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,
    cv_seed: 123,
    auto_converge: 1e-4,
    parallel: true
})
const custom_weights = Array(x.length).fill(1.0)
const result = model.fit(x, y, custom_weights)

// Result structure:
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

WebAssembly:

import { Lowess } from "fastlowess-wasm"

const model = new Lowess({
    fraction: 0.5,
    iterations: 3,
    delta: 0.01,
    weight_function: "tricube",
    robustness_method: "bisquare",
    scaling_method: "mad",
    zero_weight_fallback: "use_local_mean",
    boundary_policy: "extend",
    return_se: true,
    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,
    cv_seed: 123,
    auto_converge: 1e-4,
    parallel: true
})
const custom_weights = new Float64Array(x.length).fill(1)
const result = model.fit(x, y, custom_weights)

// Result structure:
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

C++:

#include "fastlowess.hpp"

fastlowess::LowessOptions options;
options.fraction = 0.5;
options.iterations = 3;
options.delta = 0.01;
options.weight_function = "tricube";
options.robustness_method = "bisquare";
options.scaling_method = "mad";
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.return_se = true;
options.cv_fractions = {0.3, 0.5, 0.7};
options.cv_method = "kfold";
options.cv_k = 5;
options.cv_seed = 123;
options.auto_converge = 1e-4;
options.parallel = true;

fastlowess::Lowess model(options);
std::vector<double> custom_weights(x.size(), 1.0);
const auto result = model.fit(x, y, custom_weights).value();

// Result structure:
result.x_vector(),
result.y_vector(),
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()

Contributing

Contributions are welcome! Please see the Contributing Guide for more information.

Changelog

See the Changelog for a history of changes.

License

Licensed under MIT or Apache-2.0.

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}
}

Download files

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

Source Distribution

fastlowess-3.0.0.tar.gz (170.1 kB view details)

Uploaded Source

Built Distributions

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

fastlowess-3.0.0-cp38-abi3-win_amd64.whl (354.2 kB view details)

Uploaded CPython 3.8+Windows x86-64

fastlowess-3.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (475.7 kB view details)

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

fastlowess-3.0.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (459.4 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

fastlowess-3.0.0-cp38-abi3-macosx_11_0_arm64.whl (428.9 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

fastlowess-3.0.0-cp38-abi3-macosx_10_12_x86_64.whl (443.8 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: fastlowess-3.0.0.tar.gz
  • Upload date:
  • Size: 170.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for fastlowess-3.0.0.tar.gz
Algorithm Hash digest
SHA256 c752bf82da727d2d2b282f36d08f89253f2241b8a49316749151dad983950bd9
MD5 5a01e3ac2d35afcf491a6a53e5087c64
BLAKE2b-256 99026bcf4fcd6d7757d12acfe72b09f4c90cbf9b27eaf3b20c36efbc2ada7b00

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0.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-3.0.0-cp38-abi3-win_amd64.whl.

File metadata

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

File hashes

Hashes for fastlowess-3.0.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e2e0fb4a10fc3bf43202b0c22301df09691824dfa152e9ee3020dc6c0370e364
MD5 6077eb816f20759885e6c50c09d137da
BLAKE2b-256 253dc60f442cf8cde44da8df20c6bded528128c6a2f39a45058dbccaee0a669f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0-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-3.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fastlowess-3.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e82ad75514f85a9693b7f01ec53b79cbdb20b1fdab8cecf1218566c1fe8f8eef
MD5 513a8231cd700869587edbc59e9f7f6e
BLAKE2b-256 37264eebb9cf540620e31c4d05898349d30f4a0ae0c2cb7c007202d08ce7917c

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0-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-3.0.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fastlowess-3.0.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 1de7ad5dec7712389e909b0148507b663001f01a2f82b98c961745b7b6bec29d
MD5 c237824eaea74ccf53a59418cb478c45
BLAKE2b-256 f5826329b52a66168781e59b4de61395f801e3c8d3af6ec905db2727565b7789

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0-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-3.0.0-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fastlowess-3.0.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b2833ba0ee09027360f319b8b73dfa19d8025825f3dc250b060878db70f4f051
MD5 57fe7013bcd783a7ca8389728fe86c06
BLAKE2b-256 db339cf3ea2c021de2614c7950ae15ebc7f3725778b760cd15d119a4be6ca3b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0-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-3.0.0-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for fastlowess-3.0.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ce4853e76039d3fc5028be7c212979a89ae57507949058ac1cdd9b46ca530a90
MD5 1b6d5ce93ef4c621b65a66a4497b1709
BLAKE2b-256 82af116b7d2c42418fa52f8fce0deaba3cadcf70e32eccf18a1f2ae787b042da

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastlowess-3.0.0-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.

Release history Release notifications | RSS feed

4.0.0

7 files

3.2.1

7 files

3.2.0

6 files

3.1.0

6 files

This release

3.0.0 This release

6 files

2.0.0

6 files

1.3.0

6 files

1.2.0

6 files

1.0.0

6 files

0.99.10

6 files

0.99.9

6 files

0.99.8

6 files

0.99.7

6 files

0.99.6

6 files

0.99.5

6 files

0.99.4

35 files

0.4.0

36 files

0.3.1

36 files

0.3.0

36 files

0.2.0

36 files

0.1.1

36 files

0.1.0

27 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page