Skip to content

Baseline Correction with AsLS and LAsLS

Baseline drift has been a persistent headache in spectroscopy for decades. In Raman spectroscopy, fluorescence interference produces broad, curved backgrounds that dwarf the actual Raman peaks. In infrared and NIR spectroscopy, scattering and instrumental effects introduce sloping baselines that shift from sample to sample. Before the mid-2000s, correcting these baselines often meant either fitting polynomials by hand (selecting baseline points manually, then hoping the polynomial behaved between them) or using rigid automated methods that struggled with the diversity of real-world spectra. Neither approach scaled well to large datasets or complex baselines.

In 2005, Paul H. C. Eilers and Hans F. M. Boelens at the Leiden University Medical Centre proposed a remarkably elegant solution in a technical report titled “Baseline Estimation by Quantile Regression.” Their method, now widely known as Asymmetric Least Squares (AsLS), built directly on Eilers’ earlier work on the Whittaker smoother (2003), which itself revived and modernized a penalized smoothing idea from 1923. The key insight was deceptively simple: take the Whittaker smoother’s objective function and make the weights asymmetric. Points lying above the current fitted curve (likely peaks) receive very small weights, while points below it (likely baseline) receive large weights. Through a few iterations of this reweighting scheme, the fitted curve is pulled down beneath the peaks and settles onto the baseline.

What made AsLS particularly appealing was its minimal parameter set: a smoothness parameter (inherited from the Whittaker smoother) and an asymmetry parameter controlling how aggressively peaks are ignored. No manual point selection, no polynomial degree to guess, and no assumptions about peak shapes. Despite being published only as an unpublished technical report rather than a peer-reviewed journal article, AsLS quickly became one of the most widely cited and implemented baseline correction methods in analytical chemistry, spawning a family of variants including airPLS (Zhang et al., 2010) and drPLS (Xu et al., 2019).

The baseline problem in spectroscopy

In spectroscopy, baseline drift is one of the most common—and frustrating—preprocessing challenges. Your spectrum might show:

  • A sloping baseline from scattering
  • A curved baseline from fluorescence interference
  • Instrumental drift over time
  • Sample-specific background contributions

The fundamental challenge: you want to fit the baseline (the slowly varying background) while ignoring the peaks (your actual signal of interest). Traditional smoothing methods like Whittaker can’t distinguish between baseline and peaks—they’ll smooth everything equally, giving you neither good baseline estimation nor preserved peak shapes.

From smoothing to baseline correction

Whittaker smoothing provides an excellent foundation. It minimizes:

This works great for smoothing, where we want z to be close to y. But for baseline correction, we need something clever: asymmetric weighting.

The key insight? Points below the fitted curve are likely baseline (high weight, fit closely), while points above the curve are likely peaks (low weight, ignore them).

This simple but brilliant idea leads to Asymmetric Least Squares (AsLS) and its more advanced cousin, Local Asymmetric Least Squares (LAsLS).


Asymmetric Least Squares (AsLS)

AsLS extends Whittaker smoothing with asymmetric weights that force the fitted curve to “hug” the baseline from below while ignoring peaks above.

The AsLS objective function

We modify the Whittaker objective by adding weights wi:

The weights are updated iteratively based on the current fit:

Where:

  • p is the asymmetry parameter (typically 0.001 to 0.1)
  • Small p → more asymmetric → ignores peaks more strongly
  • Large p → less asymmetric → closer to symmetric smoothing

The algorithm

AsLS is an iterative method:

  1. Initialize: Start with all weights wi = 1
  2. Solve: Solve the weighted system (W + λDTD)z = Wy for z
  3. Update weights: Set wi = p if yi > zi, else wi = 1-p
  4. Repeat steps 2-3 until convergence (typically 10 iterations)

The result is a smooth baseline that passes below the peaks.

Parameters

λ (lambda): Controls smoothness of the baseline

  • Larger λ → smoother baseline (less flexible)
  • Typical range: 105 to 109 (much larger than for smoothing!)
  • Start with λ = 106 and adjust
  • If baseline is too rough → increase λ
  • If baseline doesn’t follow curvature → decrease λ

p: Controls asymmetry (how much to ignore peaks)

  • Smaller p → more asymmetric, ignores peaks more
  • Typical range: 0.001 to 0.1
  • Start with p = 0.01 (1%)
  • If baseline cuts into peaks → decrease p (e.g., 0.001)
  • If baseline is similar height to peaks → increase p (e.g., 0.05)

d: Difference order

  • Usually d = 2 (same as Whittaker smoothing)
  • Penalizes curvature changes

niter: Number of iterations

  • Typically 10 is sufficient
  • Algorithm usually converges in 5-10 iterations

Interactive visualization

Let’s explore how λ and p affect baseline correction. The visualization below shows a Raman spectrum with fluorescence baseline. You can adjust the parameters and see the results in real-time:

Try this:

  1. Start with the default parameters (λ = 106, p = 0.01)
  2. Increase λ and watch the baseline become smoother
  3. Decrease λ and see it follow the baseline curvature more closely
  4. Decrease p to make it more asymmetric (ignore peaks more)
  5. Increase p and observe how the baseline starts cutting into peaks
  6. Toggle between “None” and “AsLS” to compare original vs corrected

Notice how you can find parameter values that fit the baseline well while preserving the peak shapes!

Code examples: AsLS baseline correction

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
import matplotlib.pyplot as plt
def baseline_asls(y, lam=1e6, p=0.01, niter=10, d=2):
"""
Asymmetric Least Squares baseline correction.
Parameters:
-----------
y : array-like
Input spectrum
lam : float
Smoothness parameter (lambda). Larger = smoother baseline.
Typical range: 1e5 to 1e9
p : float
Asymmetry parameter. Smaller = more asymmetric.
Typical range: 0.001 to 0.1
niter : int
Number of iterations (typically 10)
d : int
Order of differences (typically 2)
Returns:
--------
z : array
Fitted baseline
"""
n = len(y)
# Difference matrix
D = sparse.diags([1, -2, 1], [0, 1, 2], shape=(n-2, n), format='csc')
for _ in range(d - 2):
D = D[1:] - D[:-1]
# Precompute D'D
DTD = D.T @ D
# Initialize weights
w = np.ones(n)
# Iterative reweighting
for _ in range(niter):
W = sparse.diags(w, 0, shape=(n, n), format='csc')
Z = W + lam * DTD
z = spsolve(Z, w * y)
# Update weights asymmetrically
w = p * (y > z) + (1 - p) * (y <= z)
return z
# Example: Raman spectrum with fluorescence baseline
np.random.seed(42)
wavelength = np.linspace(400, 1800, 700)
# True Raman peaks
peaks = (0.8 * np.exp(-((wavelength - 600)**2) / 500) +
0.6 * np.exp(-((wavelength - 900)**2) / 400) +
0.5 * np.exp(-((wavelength - 1200)**2) / 600))
# Fluorescence baseline (curved)
baseline = 3 + 0.002 * wavelength - 1e-6 * wavelength**2
# Noisy spectrum
noise = np.random.normal(0, 0.05, len(wavelength))
spectrum = baseline + peaks + noise
# Apply AsLS baseline correction
fitted_baseline = baseline_asls(spectrum, lam=1e6, p=0.01, niter=10)
corrected = spectrum - fitted_baseline
# Plot
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# Original spectrum with fitted baseline
ax1.plot(wavelength, spectrum, label='Spectrum', linewidth=1.5)
ax1.plot(wavelength, fitted_baseline, label='Fitted baseline',
linewidth=2, color='red')
ax1.set_xlabel('Raman shift (cm⁻¹)')
ax1.set_ylabel('Intensity')
ax1.set_title('AsLS Baseline Correction')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Baseline-corrected spectrum
ax2.plot(wavelength, corrected, linewidth=1.5, color='green')
ax2.axhline(0, color='black', linestyle='--', linewidth=1, alpha=0.5)
ax2.set_xlabel('Raman shift (cm⁻¹)')
ax2.set_ylabel('Intensity (corrected)')
ax2.set_title('After Baseline Correction')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Local Asymmetric Least Squares (LAsLS): Adaptive baseline correction

AsLS works remarkably well for many spectroscopic baselines, but it has a limitation: it uses global parameters (λ and p) for the entire spectrum. This means:

  • The same smoothness is applied everywhere
  • The same asymmetry is applied everywhere

In real spectra, baseline complexity often varies locally:

  • Some regions might need more smoothness (gentle baseline drift)
  • Other regions might need more flexibility (rapid baseline changes)
  • Peak heights may vary dramatically (requiring different asymmetry)

LAsLS (Local Asymmetric Least Squares) solves this by making the parameters adaptive—they adjust automatically to local signal characteristics.

The core innovation: local parameter adaptation

Instead of using global λ and p for all points, LAsLS computes local versions for each spectral region:

  1. Local smoothness λi adapts to the local baseline curvature
  2. Local asymmetry pi adapts to the local peak height

This allows the algorithm to be:

  • Very smooth in flat regions
  • More flexible in regions with rapid baseline changes
  • More asymmetric where peaks are tall
  • Less asymmetric where peaks are small

The LAsLS objective function

The mathematical formulation extends AsLS:

Notice that λ is now λi—a different smoothness parameter for each difference term.

Local parameter computation

At each iteration, LAsLS computes local parameters in a window around each point:

Local smoothness λi:

Where:

  • sd = robust estimate of local curvature (median absolute deviation)
  • α = adaptation strength (typically 0.5 to 2)

This means: in regions with high curvature (rapid baseline changes), λi decreases → more flexibility.

Local asymmetry pi:

Where:

  • sy = robust estimate of signal scale (MAD of positive residuals)
  • β = adaptation strength (typically 0.5 to 2)

This means: where peaks are tall (large positive residuals), pi decreases → more asymmetric (ignore peaks more).

The LAsLS algorithm

  1. Initialize: Compute initial baseline (use AsLS or Whittaker)
  2. Compute local parameters:
    • Calculate local curvature and update λi for all i
    • Calculate local peak heights and update pi for all i
  3. Update weights: wi = pi if yi > zi, else wi = 1-pi
  4. Solve: (W + ΛlocalDTD)z = Wy where Λlocal = diag(λi)
  5. Repeat steps 2-4 until convergence (typically 10-15 iterations)

Parameters

LAsLS has more parameters than AsLS, but most can use default values:

Global parameters (same as AsLS):

  • λglobal: Initial smoothness (start with 105 to 107)
  • pglobal: Initial asymmetry (start with 0.01)
  • d: Difference order (typically 2)
  • niter: Iterations (15-20 for LAsLS, more than AsLS)

Adaptation parameters (new):

  • α: Smoothness adaptation strength (default: 1.0)
    • Larger α → more local smoothness variation
    • Typical range: 0.5 to 2.0
  • β: Asymmetry adaptation strength (default: 1.0)
    • Larger β → more local asymmetry variation
    • Typical range: 0.5 to 2.0
  • window_size: Window for computing local statistics (default: 10-50 points)
    • Depends on your spectral resolution
    • Should be larger than typical peak width

When to use LAsLS vs AsLS

Use AsLS when:

  • Baseline character is similar across the spectrum
  • You want a simple, fast method
  • Parameters are easy to tune visually
  • Your baseline is smoothly varying

Use LAsLS when:

  • Baseline complexity varies significantly across the spectrum
  • You have regions with very different curvature
  • Peak heights vary dramatically (some tall, some small)
  • AsLS either over-corrects some regions or under-corrects others
  • You’re willing to invest time in parameter tuning

Real-world examples favoring LAsLS:

  • Raman spectra with both sharp peaks and broad bands
  • NIR spectra with variable scattering + absorption
  • Chromatograms with baseline drift + very tall peaks
  • Fluorescence with multiple emission bands of different intensities

Code examples: LAsLS baseline correction

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
from scipy.ndimage import uniform_filter1d
def baseline_lasls(y, lam=1e6, p=0.01, alpha=1.0, beta=1.0,
niter=15, d=2, window=20):
"""
Local Asymmetric Least Squares baseline correction.
Parameters:
-----------
y : array-like
Input spectrum
lam : float
Global smoothness parameter
p : float
Global asymmetry parameter
alpha : float
Smoothness adaptation strength (default: 1.0)
beta : float
Asymmetry adaptation strength (default: 1.0)
niter : int
Number of iterations (default: 15)
d : int
Difference order (default: 2)
window : int
Window size for local statistics (default: 20)
Returns:
--------
z : array
Fitted baseline
"""
n = len(y)
# Difference matrix
D = sparse.diags([1, -2, 1], [0, 1, 2], shape=(n-2, n), format='csc')
for _ in range(d - 2):
D = D[1:] - D[:-1]
DTD = D.T @ D
# Initialize with AsLS
w = np.ones(n)
for _ in range(3):
W = sparse.diags(w, 0, shape=(n, n), format='csc')
Z = W + lam * DTD
z = spsolve(Z, w * y)
w = p * (y > z) + (1 - p) * (y <= z)
# LAsLS iterations
for iteration in range(niter):
# Compute residuals
residuals = y - z
# Local smoothness adaptation
# Compute local curvature
if d == 2:
curvature = np.abs(np.diff(z, n=2))
else:
curvature = np.abs(np.diff(z, n=d))
# Pad to match length
curvature = np.pad(curvature, (d//2, n - len(curvature) - d//2),
mode='edge')
# Smooth curvature estimate
local_curve = uniform_filter1d(curvature, size=window, mode='nearest')
# Robust scale estimate
mad_curve = np.median(np.abs(curvature - np.median(curvature)))
if mad_curve == 0:
mad_curve = 1.0
# Adaptive lambda (smaller where curvature is high)
lambda_local = lam / (1 + alpha * local_curve / mad_curve)
# Local asymmetry adaptation
# Compute local peak heights
peak_heights = np.maximum(0, residuals)
local_peaks = uniform_filter1d(peak_heights, size=window, mode='nearest')
# Robust scale estimate
mad_peaks = np.median(np.abs(peak_heights[peak_heights > 0]))
if mad_peaks == 0:
mad_peaks = 1.0
# Adaptive p (smaller where peaks are tall)
p_local = p / (1 + beta * local_peaks / mad_peaks)
# Update weights with local asymmetry
w = p_local * (y > z) + (1 - p_local) * (y <= z)
# Build local smoothness matrix
lambda_diff = lambda_local[:-d] if d > 0 else lambda_local
Lambda = sparse.diags(lambda_diff, 0, format='csc')
# Solve with local parameters
W = sparse.diags(w, 0, shape=(n, n), format='csc')
Z = W + D.T @ Lambda @ D
z_new = spsolve(Z, w * y)
# Check convergence
if np.max(np.abs(z_new - z)) < 1e-6:
break
z = z_new
return z
# Example: Complex baseline with variable features
np.random.seed(42)
wavelength = np.linspace(400, 2000, 800)
# Multiple peaks of different heights
peaks = (2.0 * np.exp(-((wavelength - 600)**2) / 400) + # Tall peak
0.3 * np.exp(-((wavelength - 900)**2) / 300) + # Small peak
1.5 * np.exp(-((wavelength - 1200)**2) / 500) + # Medium peak
0.4 * np.exp(-((wavelength - 1500)**2) / 350)) # Small peak
# Complex baseline: smooth in some regions, curved in others
baseline = (2 + 0.003 * wavelength - 2e-6 * wavelength**2 +
0.5 * np.exp(-((wavelength - 1000)**2) / 50000)) # Local bump
# Noisy spectrum
noise = np.random.normal(0, 0.03, len(wavelength))
spectrum = baseline + peaks + noise
# Compare AsLS vs LAsLS
baseline_asls_fit = baseline_asls(spectrum, lam=1e6, p=0.01, niter=10)
baseline_lasls_fit = baseline_lasls(spectrum, lam=1e6, p=0.01,
alpha=1.0, beta=1.0, niter=15)
corrected_asls = spectrum - baseline_asls_fit
corrected_lasls = spectrum - baseline_lasls_fit
# Plot comparison
fig, axes = plt.subplots(3, 1, figsize=(12, 10))
# Original with fitted baselines
axes[0].plot(wavelength, spectrum, label='Spectrum', linewidth=1.5, alpha=0.7)
axes[0].plot(wavelength, baseline_asls_fit, label='AsLS baseline',
linewidth=2, color='red')
axes[0].plot(wavelength, baseline_lasls_fit, label='LAsLS baseline',
linewidth=2, color='purple', linestyle='--')
axes[0].set_xlabel('Wavenumber (cm⁻¹)')
axes[0].set_ylabel('Intensity')
axes[0].set_title('Baseline Correction Comparison')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# AsLS corrected
axes[1].plot(wavelength, corrected_asls, linewidth=1.5, color='red')
axes[1].axhline(0, color='black', linestyle='--', linewidth=1, alpha=0.5)
axes[1].set_xlabel('Wavenumber (cm⁻¹)')
axes[1].set_ylabel('Intensity')
axes[1].set_title('AsLS Corrected')
axes[1].grid(True, alpha=0.3)
# LAsLS corrected
axes[2].plot(wavelength, corrected_lasls, linewidth=1.5, color='purple')
axes[2].axhline(0, color='black', linestyle='--', linewidth=1, alpha=0.5)
axes[2].set_xlabel('Wavenumber (cm⁻¹)')
axes[2].set_ylabel('Intensity')
axes[2].set_title('LAsLS Corrected')
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Print statistics
print(f"AsLS - Baseline residuals std: {np.std(corrected_asls):.4f}")
print(f"LAsLS - Baseline residuals std: {np.std(corrected_lasls):.4f}")

Practical guidelines

Choosing between AsLS and LAsLS

Start with AsLS for most applications:

  • Simpler with fewer parameters
  • Faster computation
  • Works well for uniform baselines
  • Easier to understand and tune

Upgrade to LAsLS when AsLS fails:

  • Baseline varies significantly across spectrum
  • Some regions need different smoothness
  • Peak heights vary dramatically
  • AsLS over/under-corrects in different regions

Common applications

AsLS is ideal for:

  • Standard Raman spectra with fluorescence
  • NIR spectra with multiplicative scatter
  • FTIR spectra with sloping baselines
  • Chromatography with smooth baseline drift

LAsLS excels at:

  • Complex Raman with varying peak heights
  • Mixed spectral regions (some flat, some curved)
  • Spectra with localized baseline artifacts
  • Data with heterogeneous noise levels

Parameter tuning workflow

  1. Start with defaults: λ = 106, p = 0.01
  2. Visual inspection: Plot spectrum + fitted baseline
  3. Adjust smoothness (λ):
    • Too wiggly? Increase λ
    • Missing curves? Decrease λ
  4. Adjust asymmetry (p):
    • Cuts into peaks? Decrease p
    • Doesn’t reach baseline? Increase p
  5. Check residuals: Corrected spectrum should have flat baseline around zero

Summary: AsLS vs LAsLS

FeatureAsLSLAsLS
ComplexityLow (2-3 parameters)High (4-6 parameters)
SpeedFast (10 iterations)Slower (15-20 iterations)
Best forUniform baselinesVariable baselines
TuningEasy (visual)Moderate (requires testing)
RobustnessHighModerate (can overfit)
Typical λ105 – 109105 – 107
Typical p0.001 – 0.10.001 – 0.1

Decision rule:

  1. Try AsLS first
  2. If it works → stop (don’t overcomplicate)
  3. If baseline fit is inconsistent across spectrum → try LAsLS
  4. If LAsLS doesn’t help → your baseline may need manual correction or different preprocessing

Further reading

AsLS baseline correction:

  • Eilers, P. H. C., & Boelens, H. F. M. (2005). “Baseline correction with asymmetric least squares smoothing”. Unpublished manuscript.
    • The original AsLS paper

LAsLS and adaptive variants:

  • Zhang, Z.-M., Chen, S., & Liang, Y.-Z. (2010). “Baseline correction using adaptive iteratively reweighted penalized least squares”. Analyst, 135(5), 1138-1146.
    • airPLS method with adaptive parameters
  • Xu, D., et al. (2019). “Baseline correction method based on doubly reweighted penalized least squares”. Applied Optics, 58(14), 3913-3920.
    • drPLS extension with robust weighting

Related methods:

  • Whittaker smoothing (foundation for these methods)
  • Polynomial baseline fitting
  • Rubberband baseline correction
  • Morphological baseline estimation

These methods are among the most powerful and widely-used baseline correction techniques in modern spectroscopy. Master them, and you’ll be equipped to handle the vast majority of baseline challenges in chemometric applications.