fastlowess-wasm 1.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -104
- package/fastlowess_wasm.d.ts +159 -75
- package/fastlowess_wasm.js +230 -142
- package/fastlowess_wasm_bg.wasm +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<a href="https://anaconda.org/conda-forge/r-rfastlowess"><img src="https://img.shields.io/badge/rfastlowess_(R)-44A833?logo=anaconda&logoColor=white" alt="rfastlowess (R)"></a>
|
|
17
17
|
<br>
|
|
18
18
|
<a href="https://github.com/thisisamirv/lowess-project/actions/workflows/ci.yml"><img src="https://github.com/thisisamirv/lowess-project/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
19
|
+
<a href="https://github.com/ropensci/software-review/issues/769"><img src="https://badges.ropensci.org/769_status.svg" alt="Status at rOpenSci Software Peer Review"></a>
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
@@ -45,7 +46,11 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
|
|
|
45
46
|
|
|
46
47
|
> [!NOTE]
|
|
47
48
|
>
|
|
48
|
-
> Currently available for R, Python, Rust, Julia, Node.js, WebAssembly, and C++. See [
|
|
49
|
+
> Currently available for R, Python, Rust, Julia, Node.js, WebAssembly, and C++. See the [Installation Guide](https://lowess.readthedocs.io/getting-started/installation/) for detailed installation instructions.
|
|
50
|
+
|
|
51
|
+
### GPU Backend
|
|
52
|
+
|
|
53
|
+
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](https://lowess.readthedocs.io/en/latest/user-guide/gpu-backend/) for installation instructions and usage.
|
|
49
54
|
|
|
50
55
|
## Documentation
|
|
51
56
|
|
|
@@ -57,12 +62,12 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
|
|
|
57
62
|
|
|
58
63
|
## LOESS vs. LOWESS
|
|
59
64
|
|
|
60
|
-
| Feature
|
|
61
|
-
|
|
62
|
-
| **Polynomial Degree** | Linear, Quadratic, Cubic, Quartic | Linear (Degree 1)
|
|
63
|
-
| **Dimensions**
|
|
64
|
-
| **Flexibility**
|
|
65
|
-
| **Complexity**
|
|
65
|
+
| Feature | LOESS | LOWESS (This Crate) |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| **Polynomial Degree** | Linear, Quadratic, Cubic, Quartic | Linear (Degree 1) |
|
|
68
|
+
| **Dimensions** | Multivariate (n-D support) | Univariate (1-D only) |
|
|
69
|
+
| **Flexibility** | High (Distance metrics) | Standard |
|
|
70
|
+
| **Complexity** | Higher (Matrix inversion) | Lower (Weighted average/slope) |
|
|
66
71
|
|
|
67
72
|
> [!TIP]
|
|
68
73
|
> **Note:** For a **LOESS** implementation, use [`loess-project`](https://github.com/thisisamirv/loess-project).
|
|
@@ -75,7 +80,7 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
|
|
|
75
80
|
|
|
76
81
|
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`.
|
|
77
82
|
|
|
78
|
-
For more details on the performance comparison, see the [
|
|
83
|
+
For more details on the performance comparison, see the [Benchmarks](https://lowess.readthedocs.io/benchmarks/) page.
|
|
79
84
|
|
|
80
85
|
### Robustness
|
|
81
86
|
|
|
@@ -117,46 +122,47 @@ This crate applies a range of different *boundary policies* at dataset edges:
|
|
|
117
122
|
|
|
118
123
|
A variety of features, supporting a range of use cases:
|
|
119
124
|
|
|
120
|
-
| Feature
|
|
121
|
-
|
|
122
|
-
| Kernel
|
|
123
|
-
| Robustness Weighting | 3 options
|
|
124
|
-
| Scale Estimation
|
|
125
|
-
| Boundary Padding
|
|
126
|
-
| Zero Weight Fallback | 3 options
|
|
127
|
-
| Auto Convergence
|
|
128
|
-
| Online Mode
|
|
129
|
-
| Streaming Mode
|
|
130
|
-
| Confidence Intervals | yes
|
|
131
|
-
| Prediction Intervals | yes
|
|
132
|
-
| Cross-Validation
|
|
133
|
-
| Parallel Execution
|
|
134
|
-
| GPU Acceleration
|
|
135
|
-
| `no-std` Support
|
|
136
|
-
|
|
137
|
-
\* GPU acceleration is currently in beta and may not be available on all platforms.
|
|
125
|
+
| Feature | This package | statsmodels | R (stats) |
|
|
126
|
+
| --- | :---: | :---: | :---: |
|
|
127
|
+
| Kernel | 7 options | only Tricube | only Tricube |
|
|
128
|
+
| Robustness Weighting | 3 options | only Huber | only Huber |
|
|
129
|
+
| Scale Estimation | 2 options | only MAR | only MAR |
|
|
130
|
+
| Boundary Padding | 4 options | no padding | no padding |
|
|
131
|
+
| Zero Weight Fallback | 3 options | no | no |
|
|
132
|
+
| Auto Convergence | yes | no | no |
|
|
133
|
+
| Online Mode | yes | no | no |
|
|
134
|
+
| Streaming Mode | yes | no | no |
|
|
135
|
+
| Confidence Intervals | yes | no | no |
|
|
136
|
+
| Prediction Intervals | yes | no | no |
|
|
137
|
+
| Cross-Validation | 2 options | no | no |
|
|
138
|
+
| Parallel Execution | yes | no | no |
|
|
139
|
+
| GPU Acceleration | yes | no | no |
|
|
140
|
+
| `no-std` Support | yes | no | no |
|
|
138
141
|
|
|
139
142
|
## Validation
|
|
140
143
|
|
|
141
144
|
All implementations are **numerical twins** of R's `lowess`:
|
|
142
145
|
|
|
143
|
-
| Aspect
|
|
144
|
-
|
|
145
|
-
| **Accuracy**
|
|
146
|
-
| **Consistency** | ✅ PERFECT
|
|
147
|
-
| **Robustness**
|
|
146
|
+
| Aspect | Status | Details |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| **Accuracy** | ✅ EXACT MATCH | Max diff < 1e-12 across all scenarios |
|
|
149
|
+
| **Consistency** | ✅ PERFECT | Multiple scenarios pass with strict tolerance |
|
|
150
|
+
| **Robustness** | ✅ VERIFIED | Robust smoothing matches R exactly |
|
|
148
151
|
|
|
149
152
|
## API Reference
|
|
150
153
|
|
|
151
154
|
**R:**
|
|
152
155
|
|
|
153
156
|
```r
|
|
154
|
-
|
|
157
|
+
library(rfastlowess)
|
|
158
|
+
|
|
159
|
+
model <- Lowess(
|
|
155
160
|
fraction = 0.5,
|
|
156
161
|
iterations = 3L,
|
|
157
162
|
delta = 0.01,
|
|
158
163
|
weight_function = "tricube",
|
|
159
164
|
robustness_method = "bisquare",
|
|
165
|
+
scaling_method = "mad",
|
|
160
166
|
zero_weight_fallback = "use_local_mean",
|
|
161
167
|
boundary_policy = "extend",
|
|
162
168
|
confidence_intervals = 0.95,
|
|
@@ -164,12 +170,16 @@ Lowess(
|
|
|
164
170
|
return_diagnostics = TRUE,
|
|
165
171
|
return_residuals = TRUE,
|
|
166
172
|
return_robustness_weights = TRUE,
|
|
173
|
+
return_se = TRUE,
|
|
167
174
|
cv_fractions = c(0.3, 0.5, 0.7),
|
|
168
175
|
cv_method = "kfold",
|
|
169
176
|
cv_k = 5L,
|
|
177
|
+
cv_seed = 123L,
|
|
170
178
|
auto_converge = 1e-4,
|
|
171
179
|
parallel = TRUE
|
|
172
|
-
)
|
|
180
|
+
)
|
|
181
|
+
custom_weights <- rep(1, length(x))
|
|
182
|
+
result <- fit(model, x, y, custom_weights = custom_weights)
|
|
173
183
|
|
|
174
184
|
# Result structure:
|
|
175
185
|
result$x,
|
|
@@ -198,6 +208,7 @@ model = Lowess(
|
|
|
198
208
|
delta=0.01,
|
|
199
209
|
weight_function="tricube",
|
|
200
210
|
robustness_method="bisquare",
|
|
211
|
+
scaling_method="mad",
|
|
201
212
|
zero_weight_fallback="use_local_mean",
|
|
202
213
|
boundary_policy="extend",
|
|
203
214
|
confidence_intervals=0.95,
|
|
@@ -205,13 +216,16 @@ model = Lowess(
|
|
|
205
216
|
return_diagnostics=True,
|
|
206
217
|
return_residuals=True,
|
|
207
218
|
return_robustness_weights=True,
|
|
219
|
+
return_se=True,
|
|
208
220
|
cv_fractions=[0.3, 0.5, 0.7],
|
|
209
221
|
cv_method="kfold",
|
|
210
222
|
cv_k=5,
|
|
223
|
+
cv_seed=123,
|
|
211
224
|
auto_converge=1e-4,
|
|
212
225
|
parallel=True
|
|
213
226
|
)
|
|
214
|
-
|
|
227
|
+
custom_weights = [1.0] * len(x)
|
|
228
|
+
result = model.fit(x, y, custom_weights=custom_weights)
|
|
215
229
|
|
|
216
230
|
# Result structure:
|
|
217
231
|
result.x,
|
|
@@ -232,27 +246,32 @@ result.cv_scores
|
|
|
232
246
|
**Rust:**
|
|
233
247
|
|
|
234
248
|
```rust
|
|
235
|
-
|
|
249
|
+
use lowess::prelude::*;
|
|
250
|
+
|
|
251
|
+
let model = Lowess::new()
|
|
236
252
|
.fraction(0.5)
|
|
237
253
|
.iterations(3)
|
|
238
254
|
.delta(0.01)
|
|
239
|
-
.weight_function(
|
|
240
|
-
.robustness_method(
|
|
241
|
-
.
|
|
242
|
-
.
|
|
255
|
+
.weight_function("tricube")
|
|
256
|
+
.robustness_method("bisquare")
|
|
257
|
+
.scaling_method("mad")
|
|
258
|
+
.zero_weight_fallback("use_local_mean")
|
|
259
|
+
.boundary_policy("extend")
|
|
260
|
+
.return_se()
|
|
243
261
|
.confidence_intervals(0.95)
|
|
244
262
|
.prediction_intervals(0.95)
|
|
245
263
|
.return_diagnostics()
|
|
246
264
|
.return_residuals()
|
|
247
265
|
.return_robustness_weights()
|
|
248
|
-
.
|
|
266
|
+
.cv_method("kfold")
|
|
267
|
+
.cv_k(5)
|
|
268
|
+
.cv_fractions(vec![0.3, 0.5, 0.7])
|
|
269
|
+
.cv_seed(123)
|
|
249
270
|
.auto_converge(1e-4)
|
|
250
|
-
.
|
|
251
|
-
.parallel(true) // fastLowess only
|
|
252
|
-
.backend(CPU) // fastLowess only: CPU or GPU
|
|
271
|
+
.custom_weights(vec![1.0; x.len()])
|
|
253
272
|
.build()?;
|
|
254
273
|
|
|
255
|
-
let result = model.fit(x, y)
|
|
274
|
+
let result = model.fit(&x, &y)?;
|
|
256
275
|
|
|
257
276
|
// Result structure:
|
|
258
277
|
pub struct LowessResult<T> {
|
|
@@ -275,12 +294,15 @@ pub struct LowessResult<T> {
|
|
|
275
294
|
**Julia:**
|
|
276
295
|
|
|
277
296
|
```julia
|
|
278
|
-
|
|
297
|
+
using FastLOWESS
|
|
298
|
+
|
|
299
|
+
model = Lowess(;
|
|
279
300
|
fraction=0.5,
|
|
280
301
|
iterations=3,
|
|
281
302
|
delta=NaN, # NaN for auto
|
|
282
303
|
weight_function="tricube",
|
|
283
304
|
robustness_method="bisquare",
|
|
305
|
+
scaling_method="mad",
|
|
284
306
|
zero_weight_fallback="use_local_mean",
|
|
285
307
|
boundary_policy="extend",
|
|
286
308
|
confidence_intervals=NaN,
|
|
@@ -288,12 +310,16 @@ Lowess(;
|
|
|
288
310
|
return_diagnostics=true,
|
|
289
311
|
return_residuals=true,
|
|
290
312
|
return_robustness_weights=true,
|
|
313
|
+
return_se=true,
|
|
291
314
|
cv_fractions=Float64[], # e.g. [0.3, 0.5]
|
|
292
315
|
cv_method="kfold",
|
|
293
316
|
cv_k=5,
|
|
317
|
+
cv_seed=123,
|
|
294
318
|
auto_converge=NaN,
|
|
295
319
|
parallel=true
|
|
296
320
|
)
|
|
321
|
+
custom_weights = ones(length(x))
|
|
322
|
+
result = fit(model, x, y; custom_weights=custom_weights)
|
|
297
323
|
|
|
298
324
|
# Result structure:
|
|
299
325
|
result.x,
|
|
@@ -314,90 +340,107 @@ result.cv_scores
|
|
|
314
340
|
**Node.js:**
|
|
315
341
|
|
|
316
342
|
```javascript
|
|
317
|
-
|
|
343
|
+
import { Lowess } from "fastlowess"
|
|
344
|
+
|
|
345
|
+
const model = new Lowess({
|
|
318
346
|
fraction: 0.5,
|
|
319
347
|
iterations: 3,
|
|
320
348
|
delta: 0.01,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
349
|
+
weight_function: "tricube",
|
|
350
|
+
robustness_method: "bisquare",
|
|
351
|
+
scaling_method: "mad",
|
|
352
|
+
zero_weight_fallback: "use_local_mean",
|
|
353
|
+
boundary_policy: "extend",
|
|
354
|
+
return_se: true,
|
|
355
|
+
confidence_intervals: 0.95,
|
|
356
|
+
prediction_intervals: 0.95,
|
|
357
|
+
return_diagnostics: true,
|
|
358
|
+
return_residuals: true,
|
|
359
|
+
return_robustness_weights: true,
|
|
360
|
+
cv_fractions: [0.3, 0.5, 0.7],
|
|
361
|
+
cv_method: "kfold",
|
|
362
|
+
cv_k: 5,
|
|
363
|
+
cv_seed: 123,
|
|
364
|
+
auto_converge: 1e-4,
|
|
334
365
|
parallel: true
|
|
335
|
-
})
|
|
366
|
+
})
|
|
367
|
+
const custom_weights = Array(x.length).fill(1.0)
|
|
368
|
+
const result = model.fit(x, y, custom_weights)
|
|
336
369
|
|
|
337
370
|
// Result structure:
|
|
338
371
|
result.x,
|
|
339
372
|
result.y,
|
|
340
|
-
result.
|
|
341
|
-
result.
|
|
342
|
-
result.
|
|
343
|
-
result.
|
|
344
|
-
result.
|
|
373
|
+
result.standard_errors,
|
|
374
|
+
result.confidence_lower,
|
|
375
|
+
result.confidence_upper,
|
|
376
|
+
result.prediction_lower,
|
|
377
|
+
result.prediction_upper,
|
|
345
378
|
result.residuals,
|
|
346
|
-
result.
|
|
379
|
+
result.robustness_weights,
|
|
347
380
|
result.diagnostics,
|
|
348
|
-
result.
|
|
349
|
-
result.
|
|
350
|
-
result.
|
|
381
|
+
result.iterations_used,
|
|
382
|
+
result.fraction_used,
|
|
383
|
+
result.cv_scores
|
|
351
384
|
```
|
|
352
385
|
|
|
353
386
|
**WebAssembly:**
|
|
354
387
|
|
|
355
388
|
```javascript
|
|
356
|
-
|
|
389
|
+
import { Lowess } from "fastlowess-wasm"
|
|
390
|
+
|
|
391
|
+
const model = new Lowess({
|
|
357
392
|
fraction: 0.5,
|
|
358
393
|
iterations: 3,
|
|
359
394
|
delta: 0.01,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
395
|
+
weight_function: "tricube",
|
|
396
|
+
robustness_method: "bisquare",
|
|
397
|
+
scaling_method: "mad",
|
|
398
|
+
zero_weight_fallback: "use_local_mean",
|
|
399
|
+
boundary_policy: "extend",
|
|
400
|
+
return_se: true,
|
|
401
|
+
confidence_intervals: 0.95,
|
|
402
|
+
prediction_intervals: 0.95,
|
|
403
|
+
return_diagnostics: true,
|
|
404
|
+
return_residuals: true,
|
|
405
|
+
return_robustness_weights: true,
|
|
406
|
+
cv_fractions: [0.3, 0.5, 0.7],
|
|
407
|
+
cv_method: "kfold",
|
|
408
|
+
cv_k: 5,
|
|
409
|
+
cv_seed: 123,
|
|
410
|
+
auto_converge: 1e-4,
|
|
373
411
|
parallel: true
|
|
374
412
|
})
|
|
413
|
+
const custom_weights = new Float64Array(x.length).fill(1)
|
|
414
|
+
const result = model.fit(x, y, custom_weights)
|
|
375
415
|
|
|
376
416
|
// Result structure:
|
|
377
417
|
result.x,
|
|
378
418
|
result.y,
|
|
379
|
-
result.
|
|
380
|
-
result.
|
|
381
|
-
result.
|
|
382
|
-
result.
|
|
383
|
-
result.
|
|
419
|
+
result.standard_errors,
|
|
420
|
+
result.confidence_lower,
|
|
421
|
+
result.confidence_upper,
|
|
422
|
+
result.prediction_lower,
|
|
423
|
+
result.prediction_upper,
|
|
384
424
|
result.residuals,
|
|
385
|
-
result.
|
|
425
|
+
result.robustness_weights,
|
|
386
426
|
result.diagnostics,
|
|
387
|
-
result.
|
|
388
|
-
result.
|
|
389
|
-
result.
|
|
427
|
+
result.iterations_used,
|
|
428
|
+
result.fraction_used,
|
|
429
|
+
result.cv_scores
|
|
390
430
|
```
|
|
391
431
|
|
|
392
432
|
**C++:**
|
|
393
433
|
|
|
394
434
|
```cpp
|
|
435
|
+
#include "fastlowess.hpp"
|
|
436
|
+
|
|
395
437
|
fastlowess::LowessOptions options;
|
|
396
438
|
options.fraction = 0.5;
|
|
397
439
|
options.iterations = 3;
|
|
398
440
|
options.delta = 0.01;
|
|
399
441
|
options.weight_function = "tricube";
|
|
400
442
|
options.robustness_method = "bisquare";
|
|
443
|
+
options.scaling_method = "mad";
|
|
401
444
|
options.zero_weight_fallback = "use_local_mean";
|
|
402
445
|
options.boundary_policy = "extend";
|
|
403
446
|
options.confidence_intervals = 0.95;
|
|
@@ -405,28 +448,31 @@ options.prediction_intervals = 0.95;
|
|
|
405
448
|
options.return_diagnostics = true;
|
|
406
449
|
options.return_residuals = true;
|
|
407
450
|
options.return_robustness_weights = true;
|
|
451
|
+
options.return_se = true;
|
|
408
452
|
options.cv_fractions = {0.3, 0.5, 0.7};
|
|
409
453
|
options.cv_method = "kfold";
|
|
410
454
|
options.cv_k = 5;
|
|
455
|
+
options.cv_seed = 123;
|
|
411
456
|
options.auto_converge = 1e-4;
|
|
412
457
|
options.parallel = true;
|
|
413
458
|
|
|
414
459
|
fastlowess::Lowess model(options);
|
|
415
|
-
|
|
460
|
+
std::vector<double> custom_weights(x.size(), 1.0);
|
|
461
|
+
const auto result = model.fit(x, y, custom_weights).value();
|
|
416
462
|
|
|
417
463
|
// Result structure:
|
|
418
|
-
result.
|
|
419
|
-
result.
|
|
420
|
-
result.
|
|
421
|
-
result.
|
|
422
|
-
result.
|
|
423
|
-
result.
|
|
424
|
-
result.
|
|
464
|
+
result.x_vector(),
|
|
465
|
+
result.y_vector(),
|
|
466
|
+
result.standard_errors(),
|
|
467
|
+
result.confidence_lower(),
|
|
468
|
+
result.confidence_upper(),
|
|
469
|
+
result.prediction_lower(),
|
|
470
|
+
result.prediction_upper(),
|
|
425
471
|
result.residuals(),
|
|
426
|
-
result.
|
|
472
|
+
result.robustness_weights(),
|
|
427
473
|
result.diagnostics(),
|
|
428
|
-
result.
|
|
429
|
-
result.
|
|
474
|
+
result.iterations_used(),
|
|
475
|
+
result.fraction_used(),
|
|
430
476
|
result.cv_scores()
|
|
431
477
|
```
|
|
432
478
|
|
|
@@ -434,7 +480,11 @@ result.cv_scores()
|
|
|
434
480
|
|
|
435
481
|
## Contributing
|
|
436
482
|
|
|
437
|
-
Contributions are welcome! Please see the [
|
|
483
|
+
Contributions are welcome! Please see the [Contributing Guide](https://lowess.readthedocs.io/en/latest/contributing/) for more information.
|
|
484
|
+
|
|
485
|
+
## Changelog
|
|
486
|
+
|
|
487
|
+
See the [Changelog](https://lwoess.readthedocs.io/en/latest/changelog/) for a history of changes.
|
|
438
488
|
|
|
439
489
|
## License
|
|
440
490
|
|