-
Notifications
You must be signed in to change notification settings - Fork 8
Smoothers
# Both smoothers are part of base R (the stats package)
lowess(x, y = NULL, f = 2/3, iter = 3, delta = 0.01 * diff(range(x)))
supsmu(x, y, wt, span = "cv", periodic = FALSE, bass = 0, trace = FALSE)def lowess(x, y=None, f=2/3, iter=3, delta=None): ... # returns {"x", "y"}
def supsmu(x, y, wt=None, span="cv", periodic=False, bass=0.0): ... # returns {"x", "y"}greybox exposes two non-parametric scatterplot smoothers that mirror R's stats::lowess() and stats::supsmu() exactly:
-
lowess()— Cleveland's locally-weighted scatterplot smoother (1979). Robust to outliers via iterative reweighting. -
supsmu()— Friedman's variable-span SuperSmoother (1984). Picks the bandwidth locally via cross-validated residuals.
In R both are part of base stats; in Python they are implemented as native pybind11 extensions inside greybox and reproduce R outputs to ~1e-15 (machine precision). They are also used internally by AID: LOWESS smooths the inter-demand intervals for stockout detection and SuperSmoother builds the demand-size regressor.
A weighted local linear regression at each point uses a tricube kernel
over a span of f * n neighbours. After the initial fit, iter robustness iterations downweight large residuals using a bisquare weight, making the smoother resilient to outliers.
| Parameter | R | Python | Type | Default | Description |
|---|---|---|---|---|---|
| x | x |
x |
numeric / array_like
|
— | Abscissa values. In Python may also be a 2-D array whose first column is x and second is y. |
| y | y |
y |
numeric / array_like
|
— | Ordinate values. Optional in Python if x already contains both columns. |
| span | f |
f |
numeric / float
|
2/3 |
Smoother span — fraction of points in each local fit. Larger values produce smoother curves. |
| iterations | iter |
iter |
integer / int
|
3 |
Number of robustness iterations. |
| delta | delta |
delta |
numeric / float
|
0.01 * diff(range(x)) |
Distance threshold within which the fit is linearly interpolated. |
| Implementation | Return |
|---|---|
| R |
list with $x (sorted x) and $y (smoothed) |
| Python |
dict with keys "x" and "y" (both numpy.ndarray) |
# R
set.seed(0)
x <- seq(0, 6, length.out = 60)
y <- sin(x) + rnorm(60, 0, 0.2)
sm <- lowess(x, y, f = 0.4)
plot(x, y); lines(sm$x, sm$y, col = "red")# Python
import numpy as np
from greybox import lowess
rng = np.random.default_rng(0)
x = np.linspace(0, 6, 60)
y = np.sin(x) + rng.normal(0, 0.2, 60)
sm = lowess(x, y, f=0.4)
# sm["x"] holds sorted x; sm["y"] holds the smoothed values# R
x <- 1:100
y <- 0.05 * x + rnorm(100, 0, 1)
sm <- lowess(x, y)# Python
import numpy as np
from greybox import lowess
x = np.arange(100, dtype=float)
y = 0.05 * x + np.random.default_rng(1).normal(0, 1, 100)
sm = lowess(x, y)SuperSmoother evaluates three running linear smoothers ("tweeters") with spans 0.05, 0.2, and 0.5 of the sample size. At each abscissa it picks the span minimising the smoothed cross-validated residual. A final pass with the smallest span produces the output. This yields a variable-bandwidth fit that adapts to the local signal-to-noise ratio.
The Python port is a direct translation of R's FORTRAN supsmu (from stats/src/ppr.f).
| Parameter | R | Python | Type | Default | Description |
|---|---|---|---|---|---|
| x | x |
x |
numeric / array_like
|
— | Abscissa values; sorted internally if not already ascending. |
| y | y |
y |
numeric / array_like
|
— | Ordinate values; same length as x. |
| weights | wt |
wt |
numeric / array_like
|
uniform | Per-observation weights. |
| span | span |
span |
numeric or "cv" / float or "cv"
|
"cv" |
Fixed span in (0, 1], or "cv" (alias 0) for cross-validated automatic selection. |
| periodic | periodic |
periodic |
logical / bool
|
FALSE / False
|
If TRUE, treat x as periodic in [0, 1]. |
| bass tone | bass |
bass |
numeric / float
|
0 |
Bass-tone control in [0, 10]; larger values bias span selection toward more smoothing. Values outside the range disable the adjustment. |
| Implementation | Return |
|---|---|
| R |
list with $x (sorted x) and $y (smoothed) |
| Python |
dict with keys "x" and "y" (both numpy.ndarray) |
# R
set.seed(1)
x <- seq(0, 6, length.out = 100)
y <- sin(x) + rnorm(100, 0, 0.3)
sm <- supsmu(x, y)# Python
import numpy as np
from greybox import supsmu
rng = np.random.default_rng(1)
x = np.linspace(0, 6, 100)
y = np.sin(x) + rng.normal(0, 0.3, 100)
sm = supsmu(x, y)# R
sm_fixed <- supsmu(x, y, span = 0.3)# Python
sm_fixed = supsmu(x, y, span=0.3)# R
sm_heavy <- supsmu(x, y, bass = 5)# Python
sm_heavy = supsmu(x, y, bass=5.0)Neither lowess() nor supsmu() has a dedicated method on either side — the return value is just list(x, y) in R / {"x", "y"} in Python. Plotting is therefore a standard one-liner.
# R
plot(x, y)
sm <- lowess(x, y)
lines(sm$x, sm$y, col = "red")# Python
import matplotlib.pyplot as plt
from greybox import lowess, supsmu
plt.scatter(x, y, s=10, alpha=0.5, label="data")
lo = lowess(x, y, f=0.4)
sm = supsmu(x, y)
plt.plot(lo["x"], lo["y"], color="red", label="LOWESS")
plt.plot(sm["x"], sm["y"], color="blue", label="SuperSmoother")
plt.legend()
plt.show()- Top-level import:
from greybox import lowess, supsmu. - Both functions are thin Python wrappers around C++/pybind11 extensions (
greybox._native_lowess,greybox._native_supsmu). They reproduce R's outputs to within ~1e-15 on the same inputs. - The Python API returns a
dictrather than an R-stylelist; access values withresult["x"]andresult["y"].
- Cleveland, W. S. (1979). "Robust Locally Weighted Regression and Smoothing Scatterplots". Journal of the American Statistical Association, 74(368), 829–836. DOI: 10.1080/01621459.1979.10481038
- Friedman, J. H. (1984). "A Variable Span Smoother". Technical Report 5 (SLAC-PUB-3477; STAN-LCS-005), Laboratory for Computational Statistics, Department of Statistics, Stanford University. OSTI 1447470