linreg-core 0.6.0 → 0.7.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 +518 -8
- package/linreg_core.d.ts +324 -1
- package/linreg_core.js +624 -0
- package/linreg_core_bg.wasm +0 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/linreg-core)
|
|
8
8
|
[](https://pypi.org/project/linreg-core/)
|
|
9
9
|
[](https://docs.rs/linreg-core)
|
|
10
|
+
[](https://jesse-anderson.net/linreg-core/)
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
A lightweight, self-contained linear regression library written in Rust. Compiles to WebAssembly for browser use, Python bindings via PyO3, or runs as a native Rust crate.
|
|
@@ -37,6 +38,8 @@ A lightweight, self-contained linear regression library written in Rust. Compile
|
|
|
37
38
|
- **Lasso Regression:** L1-regularized regression via coordinate descent with automatic variable selection, convergence tracking, model selection criteria
|
|
38
39
|
- **Elastic Net:** Combined L1 + L2 regularization for variable selection with multicollinearity handling, active set convergence, model selection criteria
|
|
39
40
|
- **LOESS:** Locally estimated scatterplot smoothing for non-parametric curve fitting with configurable span, polynomial degree, and robust fitting
|
|
41
|
+
- **WLS (Weighted Least Squares):** Regression with observation weights for heteroscedastic data, includes confidence intervals
|
|
42
|
+
- **K-Fold Cross Validation:** Model evaluation and hyperparameter tuning for all regression types (OLS, Ridge, Lasso, Elastic Net) with customizable folds, shuffling, and seeding
|
|
40
43
|
- **Lambda Path Generation:** Create regularization paths for cross-validation
|
|
41
44
|
|
|
42
45
|
### Model Statistics
|
|
@@ -64,7 +67,7 @@ Add to your `Cargo.toml`:
|
|
|
64
67
|
|
|
65
68
|
```toml
|
|
66
69
|
[dependencies]
|
|
67
|
-
linreg-core = { version = "0.
|
|
70
|
+
linreg-core = { version = "0.6", default-features = false }
|
|
68
71
|
```
|
|
69
72
|
|
|
70
73
|
### OLS Regression (Rust)
|
|
@@ -198,35 +201,199 @@ fn main() -> Result<(), linreg_core::Error> {
|
|
|
198
201
|
```rust
|
|
199
202
|
use linreg_core::diagnostics::{
|
|
200
203
|
breusch_pagan_test, durbin_watson_test, jarque_bera_test,
|
|
201
|
-
shapiro_wilk_test,
|
|
204
|
+
shapiro_wilk_test, rainbow_test, harvey_collier_test,
|
|
205
|
+
white_test, anderson_darling_test, breusch_godfrey_test,
|
|
206
|
+
cooks_distance_test, dfbetas_test, dffits_test, vif_test,
|
|
207
|
+
reset_test, BGTestType, RainbowMethod, ResetType, WhiteMethod
|
|
202
208
|
};
|
|
203
209
|
|
|
204
210
|
fn main() -> Result<(), linreg_core::Error> {
|
|
205
211
|
let y = vec![/* your data */];
|
|
206
212
|
let x = vec![vec![/* predictor 1 */], vec![/* predictor 2 */]];
|
|
207
213
|
|
|
208
|
-
// Heteroscedasticity
|
|
214
|
+
// Heteroscedasticity tests
|
|
209
215
|
let bp = breusch_pagan_test(&y, &x)?;
|
|
210
216
|
println!("Breusch-Pagan: LM={:.4}, p={:.4}", bp.statistic, bp.p_value);
|
|
211
217
|
|
|
212
|
-
|
|
218
|
+
let white = white_test(&y, &x, WhiteMethod::R)?;
|
|
219
|
+
println!("White: statistic={:.4}, p={:.4}", white.statistic, white.p_value);
|
|
220
|
+
|
|
221
|
+
// Autocorrelation tests
|
|
213
222
|
let dw = durbin_watson_test(&y, &x)?;
|
|
214
223
|
println!("Durbin-Watson: {:.4}", dw.statistic);
|
|
215
224
|
|
|
216
|
-
|
|
225
|
+
let bg = breusch_godfrey_test(&y, &x, 2, BGTestType::Chisq)?;
|
|
226
|
+
println!("Breusch-Godfrey (order 2): statistic={:.4}, p={:.4}", bg.statistic, bg.p_value);
|
|
227
|
+
|
|
228
|
+
// Normality tests
|
|
217
229
|
let jb = jarque_bera_test(&y, &x)?;
|
|
218
230
|
println!("Jarque-Bera: JB={:.4}, p={:.4}", jb.statistic, jb.p_value);
|
|
219
231
|
|
|
220
|
-
|
|
232
|
+
let sw = shapiro_wilk_test(&y, &x)?;
|
|
233
|
+
println!("Shapiro-Wilk: W={:.4}, p={:.4}", sw.statistic, sw.p_value);
|
|
234
|
+
|
|
235
|
+
let ad = anderson_darling_test(&y, &x)?;
|
|
236
|
+
println!("Anderson-Darling: A={:.4}, p={:.4}", ad.statistic, ad.p_value);
|
|
237
|
+
|
|
238
|
+
// Linearity tests
|
|
221
239
|
let rainbow = rainbow_test(&y, &x, 0.5, RainbowMethod::R)?;
|
|
222
240
|
println!("Rainbow: F={:.4}, p={:.4}",
|
|
223
241
|
rainbow.r_result.as_ref().unwrap().statistic,
|
|
224
242
|
rainbow.r_result.as_ref().unwrap().p_value);
|
|
225
243
|
|
|
244
|
+
let hc = harvey_collier_test(&y, &x)?;
|
|
245
|
+
println!("Harvey-Collier: t={:.4}, p={:.4}", hc.statistic, hc.p_value);
|
|
246
|
+
|
|
247
|
+
let reset = reset_test(&y, &x, &[2, 3], ResetType::Fitted)?;
|
|
248
|
+
println!("RESET: F={:.4}, p={:.4}", reset.f_statistic, reset.p_value);
|
|
249
|
+
|
|
250
|
+
// Influence diagnostics
|
|
251
|
+
let cd = cooks_distance_test(&y, &x)?;
|
|
252
|
+
println!("Cook's Distance: {} influential points", cd.influential_4_over_n.len());
|
|
253
|
+
|
|
254
|
+
let dfbetas = dfbetas_test(&y, &x)?;
|
|
255
|
+
println!("DFBETAS: {} influential observations", dfbetas.influential_observations.len());
|
|
256
|
+
|
|
257
|
+
let dffits = dffits_test(&y, &x)?;
|
|
258
|
+
println!("DFFITS: {} influential observations", dffits.influential_observations.len());
|
|
259
|
+
|
|
260
|
+
// Multicollinearity
|
|
261
|
+
let vif = vif_test(&y, &x)?;
|
|
262
|
+
println!("VIF: {:?}", vif.vif_values);
|
|
263
|
+
|
|
264
|
+
Ok(())
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### WLS Regression (Rust)
|
|
269
|
+
|
|
270
|
+
```rust,no_run
|
|
271
|
+
use linreg_core::weighted_regression::wls_regression;
|
|
272
|
+
|
|
273
|
+
fn main() -> Result<(), linreg_core::Error> {
|
|
274
|
+
let y = vec![2.0, 4.0, 6.0, 8.0, 10.0];
|
|
275
|
+
let x1 = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
276
|
+
|
|
277
|
+
// Equal weights = OLS
|
|
278
|
+
let weights = vec![1.0, 1.0, 1.0, 1.0, 1.0];
|
|
279
|
+
|
|
280
|
+
let fit = wls_regression(&y, &[x1], &weights)?;
|
|
281
|
+
|
|
282
|
+
println!("Intercept: {} (SE: {}, t: {}, p: {})",
|
|
283
|
+
fit.coefficients[0],
|
|
284
|
+
fit.standard_errors[0],
|
|
285
|
+
fit.t_statistics[0],
|
|
286
|
+
fit.p_values[0]
|
|
287
|
+
);
|
|
288
|
+
println!("F-statistic: {} (p: {})", fit.f_statistic, fit.f_p_value);
|
|
289
|
+
println!("R-squared: {:.4}", fit.r_squared);
|
|
290
|
+
|
|
291
|
+
// Access confidence intervals
|
|
292
|
+
for (i, (&coef, &lower, &upper)) in fit.coefficients.iter()
|
|
293
|
+
.zip(fit.conf_int_lower.iter())
|
|
294
|
+
.zip(fit.conf_int_upper.iter())
|
|
295
|
+
.enumerate()
|
|
296
|
+
{
|
|
297
|
+
println!("Coefficient {}: [{}, {}]", i, lower, upper);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Ok(())
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### LOESS Regression (Rust)
|
|
305
|
+
|
|
306
|
+
```rust,no_run
|
|
307
|
+
use linreg_core::loess::{loess_fit, LoessOptions};
|
|
308
|
+
|
|
309
|
+
fn main() -> Result<(), linreg_core::Error> {
|
|
310
|
+
// Single predictor only
|
|
311
|
+
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
|
|
312
|
+
let y = vec![1.0, 3.5, 4.8, 6.2, 8.5, 11.0, 13.2, 14.8, 17.5, 19.0, 22.0];
|
|
313
|
+
|
|
314
|
+
// Default options: span=0.75, degree=2, robust iterations=0
|
|
315
|
+
let options = LoessOptions::default();
|
|
316
|
+
|
|
317
|
+
let result = loess_fit(&y, &[x], &options)?;
|
|
318
|
+
|
|
319
|
+
println!("Fitted values: {:?}", result.fitted_values);
|
|
320
|
+
println!("Residuals: {:?}", result.residuals);
|
|
321
|
+
|
|
226
322
|
Ok(())
|
|
227
323
|
}
|
|
228
324
|
```
|
|
229
325
|
|
|
326
|
+
**Custom LOESS options:**
|
|
327
|
+
|
|
328
|
+
```rust,no_run
|
|
329
|
+
use linreg_core::loess::{loess_fit, LoessOptions, LoessSurface};
|
|
330
|
+
|
|
331
|
+
let options = LoessOptions {
|
|
332
|
+
span: 0.5, // Smoothing parameter (0-1, smaller = less smooth)
|
|
333
|
+
degree: 1, // Polynomial degree (0=constant, 1=linear, 2=quadratic)
|
|
334
|
+
surface: LoessSurface::Direct, // Note: only "direct" is currently supported; "interpolate" is planned
|
|
335
|
+
robust_iterations: 3, // Number of robust fitting iterations (0 = disabled)
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
let result = loess_fit(&y, &[x], &options)?;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### K-Fold Cross Validation (Rust)
|
|
342
|
+
|
|
343
|
+
Cross-validation is used for model evaluation and hyperparameter tuning. The library supports K-Fold CV for all regression types:
|
|
344
|
+
|
|
345
|
+
```rust,no_run
|
|
346
|
+
use linreg_core::cross_validation::{kfold_cv_ols, kfold_cv_ridge, kfold_cv_lasso, kfold_cv_elastic_net, KFoldOptions};
|
|
347
|
+
|
|
348
|
+
fn main() -> Result<(), linreg_core::Error> {
|
|
349
|
+
let y = vec![2.5, 3.7, 4.2, 5.1, 6.3, 7.0, 7.5, 8.1];
|
|
350
|
+
let x1 = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
|
351
|
+
let x2 = vec![2.0, 4.0, 5.0, 4.0, 3.0, 4.5, 5.5, 6.0];
|
|
352
|
+
let names = vec!["Intercept".to_string(), "X1".to_string(), "X2".to_string()];
|
|
353
|
+
|
|
354
|
+
// Configure CV options
|
|
355
|
+
let options = KFoldOptions {
|
|
356
|
+
n_folds: 5,
|
|
357
|
+
shuffle: true,
|
|
358
|
+
seed: Some(42), // For reproducibility
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// OLS cross-validation
|
|
362
|
+
let ols_cv = kfold_cv_ols(&y, &[x1.clone(), x2.clone()], &names, &options)?;
|
|
363
|
+
println!("OLS CV RMSE: {:.4} (±{:.4})", ols_cv.mean_rmse, ols_cv.std_rmse);
|
|
364
|
+
println!("OLS CV R²: {:.4} (±{:.4})", ols_cv.mean_r_squared, ols_cv.std_r_squared);
|
|
365
|
+
|
|
366
|
+
// Ridge cross-validation (for lambda selection)
|
|
367
|
+
let lambda = 1.0;
|
|
368
|
+
let ridge_cv = kfold_cv_ridge(&[x1.clone(), x2.clone()], &y, lambda, true, &options)?;
|
|
369
|
+
println!("Ridge CV RMSE: {:.4}", ridge_cv.mean_rmse);
|
|
370
|
+
|
|
371
|
+
// Lasso cross-validation
|
|
372
|
+
let lasso_cv = kfold_cv_lasso(&[x1.clone(), x2.clone()], &y, 0.1, true, &options)?;
|
|
373
|
+
println!("Lasso CV RMSE: {:.4}", lasso_cv.mean_rmse);
|
|
374
|
+
|
|
375
|
+
// Elastic Net cross-validation
|
|
376
|
+
let enet_cv = kfold_cv_elastic_net(&[x1, x2], &y, 0.1, 0.5, true, &options)?;
|
|
377
|
+
println!("Elastic Net CV RMSE: {:.4}", enet_cv.mean_rmse);
|
|
378
|
+
|
|
379
|
+
// Access per-fold results
|
|
380
|
+
for fold in &ols_cv.fold_results {
|
|
381
|
+
println!("Fold {}: train={}, test={}, R²={:.4}",
|
|
382
|
+
fold.fold_index, fold.train_size, fold.test_size, fold.r_squared);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
Ok(())
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**CV Result fields:**
|
|
390
|
+
- `mean_rmse`, `std_rmse` - Mean and std of RMSE across folds
|
|
391
|
+
- `mean_mae`, `std_mae` - Mean and std of MAE across folds
|
|
392
|
+
- `mean_r_squared`, `std_r_squared` - Mean and std of R² across folds
|
|
393
|
+
- `mean_train_r_squared` - Mean training R² (for overfitting detection)
|
|
394
|
+
- `fold_results` - Per-fold metrics (train/test sizes, MSE, RMSE, MAE, R²)
|
|
395
|
+
- `fold_coefficients` - Coefficients from each fold (for stability analysis)
|
|
396
|
+
|
|
230
397
|
### Lambda Path Generation (Rust)
|
|
231
398
|
|
|
232
399
|
```rust,no_run
|
|
@@ -250,10 +417,34 @@ for &lambda in lambdas.iter() {
|
|
|
250
417
|
}
|
|
251
418
|
```
|
|
252
419
|
|
|
420
|
+
### Model Save/Load (Rust)
|
|
421
|
+
|
|
422
|
+
All trained models can be saved to disk and loaded back later:
|
|
423
|
+
|
|
424
|
+
```rust,no_run
|
|
425
|
+
use linreg_core::{ModelSave, ModelLoad};
|
|
426
|
+
|
|
427
|
+
// Train a model
|
|
428
|
+
let result = ols_regression(&y, &[x1], &names)?;
|
|
429
|
+
|
|
430
|
+
// Save to file
|
|
431
|
+
result.save("my_model.json")?;
|
|
432
|
+
|
|
433
|
+
// Or with a custom name
|
|
434
|
+
result.save_with_name("my_model.json", Some("My Housing Model".to_string()))?;
|
|
435
|
+
|
|
436
|
+
// Load back
|
|
437
|
+
let loaded = linreg_core::core::RegressionOutput::load("my_model.json")?;
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
The same `save()` and `load()` methods work for all model types: `RegressionOutput`, `RidgeFit`, `LassoFit`, `ElasticNetFit`, `WlsFit`, and `LoessFit`.
|
|
441
|
+
|
|
253
442
|
---
|
|
254
443
|
|
|
255
444
|
## WebAssembly Usage
|
|
256
445
|
|
|
446
|
+
**[Live Demo →](https://jesse-anderson.net/linreg-core/)**
|
|
447
|
+
|
|
257
448
|
Build with wasm-pack:
|
|
258
449
|
|
|
259
450
|
```bash
|
|
@@ -360,6 +551,24 @@ console.log("Lambda sequence:", path.lambda_path);
|
|
|
360
551
|
console.log("Lambda max:", path.lambda_max);
|
|
361
552
|
```
|
|
362
553
|
|
|
554
|
+
### WLS Regression (WASM)
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
const result = JSON.parse(wls_regression(
|
|
558
|
+
JSON.stringify([2, 4, 6, 8, 10]),
|
|
559
|
+
JSON.stringify([[1, 2, 3, 4, 5]]),
|
|
560
|
+
JSON.stringify([1, 1, 1, 1, 1]) // weights (equal weights = OLS)
|
|
561
|
+
));
|
|
562
|
+
|
|
563
|
+
console.log("Coefficients:", result.coefficients);
|
|
564
|
+
console.log("Standard errors:", result.standard_errors);
|
|
565
|
+
console.log("P-values:", result.p_values);
|
|
566
|
+
console.log("R-squared:", result.r_squared);
|
|
567
|
+
console.log("F-statistic:", result.f_statistic);
|
|
568
|
+
console.log("Confidence intervals (lower):", result.conf_int_lower);
|
|
569
|
+
console.log("Confidence intervals (upper):", result.conf_int_upper);
|
|
570
|
+
```
|
|
571
|
+
|
|
363
572
|
### LOESS Regression (WASM)
|
|
364
573
|
|
|
365
574
|
```javascript
|
|
@@ -368,7 +577,7 @@ const result = JSON.parse(loess_fit(
|
|
|
368
577
|
JSON.stringify(x[0]), // Single predictor only (flattened array)
|
|
369
578
|
0.5, // span (smoothing parameter: 0-1)
|
|
370
579
|
1, // degree (0=constant, 1=linear, 2=quadratic)
|
|
371
|
-
"direct", // surface method ("direct"
|
|
580
|
+
"direct", // surface method ("direct" only; "interpolate" is planned)
|
|
372
581
|
0 // robust iterations (0=disabled, >0=number of iterations)
|
|
373
582
|
));
|
|
374
583
|
|
|
@@ -376,6 +585,64 @@ console.log("Fitted values:", result.fitted_values);
|
|
|
376
585
|
console.log("Residuals:", result.residuals);
|
|
377
586
|
```
|
|
378
587
|
|
|
588
|
+
### K-Fold Cross Validation (WASM)
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
// OLS cross-validation
|
|
592
|
+
const ols_cv = JSON.parse(kfold_cv_ols(
|
|
593
|
+
JSON.stringify(y),
|
|
594
|
+
JSON.stringify(x),
|
|
595
|
+
JSON.stringify(["Intercept", "X1", "X2"]),
|
|
596
|
+
5, // n_folds
|
|
597
|
+
"true", // shuffle (JSON boolean)
|
|
598
|
+
"42" // seed (JSON string number, or "null" for no seed)
|
|
599
|
+
));
|
|
600
|
+
|
|
601
|
+
console.log("OLS CV RMSE:", ols_cv.mean_rmse, "±", ols_cv.std_rmse);
|
|
602
|
+
console.log("OLS CV R²:", ols_cv.mean_r_squared, "±", ols_cv.std_r_squared);
|
|
603
|
+
|
|
604
|
+
// Ridge cross-validation
|
|
605
|
+
const ridge_cv = JSON.parse(kfold_cv_ridge(
|
|
606
|
+
JSON.stringify(y),
|
|
607
|
+
JSON.stringify(x),
|
|
608
|
+
1.0, // lambda
|
|
609
|
+
true, // standardize
|
|
610
|
+
5, // n_folds
|
|
611
|
+
"true", // shuffle
|
|
612
|
+
"42" // seed
|
|
613
|
+
));
|
|
614
|
+
|
|
615
|
+
// Lasso cross-validation
|
|
616
|
+
const lasso_cv = JSON.parse(kfold_cv_lasso(
|
|
617
|
+
JSON.stringify(y),
|
|
618
|
+
JSON.stringify(x),
|
|
619
|
+
0.1, // lambda
|
|
620
|
+
true, // standardize
|
|
621
|
+
5, // n_folds
|
|
622
|
+
"true", // shuffle
|
|
623
|
+
"42" // seed
|
|
624
|
+
));
|
|
625
|
+
|
|
626
|
+
// Elastic Net cross-validation
|
|
627
|
+
const enet_cv = JSON.parse(kfold_cv_elastic_net(
|
|
628
|
+
JSON.stringify(y),
|
|
629
|
+
JSON.stringify(x),
|
|
630
|
+
0.1, // lambda
|
|
631
|
+
0.5, // alpha (0 = Ridge, 1 = Lasso)
|
|
632
|
+
true, // standardize
|
|
633
|
+
5, // n_folds
|
|
634
|
+
"true", // shuffle
|
|
635
|
+
"42" // seed
|
|
636
|
+
));
|
|
637
|
+
|
|
638
|
+
// Access per-fold results
|
|
639
|
+
ols_cv.fold_results.forEach(fold => {
|
|
640
|
+
console.log(`Fold ${fold.fold_index}: R²=${fold.r_squared.toFixed(4)}`);
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Note:** In WASM, boolean and seed parameters are passed as JSON strings. Use `"true"`/`"false"` for shuffle and `"42"` or `"null"` for seed.
|
|
645
|
+
|
|
379
646
|
### Diagnostic Tests (WASM)
|
|
380
647
|
|
|
381
648
|
```javascript
|
|
@@ -524,6 +791,43 @@ const version = get_version(); // e.g., "0.5.0"
|
|
|
524
791
|
const msg = test(); // "Rust WASM is working!"
|
|
525
792
|
```
|
|
526
793
|
|
|
794
|
+
### Model Serialization (WASM)
|
|
795
|
+
|
|
796
|
+
```javascript
|
|
797
|
+
// Train a model
|
|
798
|
+
const resultJson = ols_regression(
|
|
799
|
+
JSON.stringify(y),
|
|
800
|
+
JSON.stringify(x),
|
|
801
|
+
JSON.stringify(names)
|
|
802
|
+
);
|
|
803
|
+
const result = JSON.parse(resultJson);
|
|
804
|
+
|
|
805
|
+
// Serialize with metadata
|
|
806
|
+
const serialized = serialize_model(
|
|
807
|
+
resultJson, // model JSON
|
|
808
|
+
"OLS", // model type: "OLS", "Ridge", "Lasso", "ElasticNet", "WLS", "LOESS"
|
|
809
|
+
"My Model" // optional name (null to omit)
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
// Get metadata without loading full model
|
|
813
|
+
const metadataJson = get_model_metadata(serialized);
|
|
814
|
+
const metadata = JSON.parse(metadataJson);
|
|
815
|
+
console.log("Model type:", metadata.model_type);
|
|
816
|
+
console.log("Created:", metadata.created_at);
|
|
817
|
+
|
|
818
|
+
// Deserialize to get model data back
|
|
819
|
+
const modelJson = deserialize_model(serialized);
|
|
820
|
+
const model = JSON.parse(modelJson);
|
|
821
|
+
|
|
822
|
+
// Download in browser
|
|
823
|
+
const blob = new Blob([serialized], { type: 'application/json' });
|
|
824
|
+
const url = URL.createObjectURL(blob);
|
|
825
|
+
const a = document.createElement('a');
|
|
826
|
+
a.href = url;
|
|
827
|
+
a.download = 'model.json';
|
|
828
|
+
a.click();
|
|
829
|
+
```
|
|
830
|
+
|
|
527
831
|
### Domain Security (WASM)
|
|
528
832
|
|
|
529
833
|
Optional domain restriction via build-time environment variable:
|
|
@@ -654,6 +958,20 @@ print(f"AIC: {result.aic}")
|
|
|
654
958
|
print(f"BIC: {result.bic}")
|
|
655
959
|
```
|
|
656
960
|
|
|
961
|
+
### LOESS Regression (Python)
|
|
962
|
+
|
|
963
|
+
```python
|
|
964
|
+
result = linreg_core.loess_fit(
|
|
965
|
+
y, # Single predictor only
|
|
966
|
+
[0.5], # span (smoothing parameter: 0-1)
|
|
967
|
+
2, # degree (0=constant, 1=linear, 2=quadratic)
|
|
968
|
+
"direct", # surface ("direct" only; "interpolate" is planned)
|
|
969
|
+
0 # robust iterations (0=disabled, >0=number of iterations)
|
|
970
|
+
)
|
|
971
|
+
print(f"Fitted values: {result.fitted_values}")
|
|
972
|
+
print(f"Residuals: {result.residuals}")
|
|
973
|
+
```
|
|
974
|
+
|
|
657
975
|
### Lambda Path Generation (Python)
|
|
658
976
|
|
|
659
977
|
```python
|
|
@@ -767,6 +1085,23 @@ print(f"Numeric columns: {result.numeric_columns}")
|
|
|
767
1085
|
print(f"Data rows: {result.n_rows}")
|
|
768
1086
|
```
|
|
769
1087
|
|
|
1088
|
+
### Model Save/Load (Python)
|
|
1089
|
+
|
|
1090
|
+
```python
|
|
1091
|
+
# Train a model
|
|
1092
|
+
result = linreg_core.ols_regression(y, x, names)
|
|
1093
|
+
|
|
1094
|
+
# Save to file
|
|
1095
|
+
linreg_core.save_model(result, "my_model.json", name="My Housing Model")
|
|
1096
|
+
|
|
1097
|
+
# Load back
|
|
1098
|
+
loaded = linreg_core.load_model("my_model.json")
|
|
1099
|
+
print(f"R²: {loaded.r_squared}")
|
|
1100
|
+
print(f"Coefficients: {loaded.coefficients}")
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
The `save_model()` and `load_model()` functions work with all result types: `OLSResult`, `RidgeResult`, `LassoResult`, `ElasticNetResult`, `LoessResult`, and `WlsResult`.
|
|
1104
|
+
|
|
770
1105
|
---
|
|
771
1106
|
|
|
772
1107
|
## Feature Flags
|
|
@@ -780,7 +1115,7 @@ print(f"Data rows: {result.n_rows}")
|
|
|
780
1115
|
For native Rust without WASM overhead:
|
|
781
1116
|
|
|
782
1117
|
```toml
|
|
783
|
-
linreg-core = { version = "0.
|
|
1118
|
+
linreg-core = { version = "0.6", default-features = false }
|
|
784
1119
|
```
|
|
785
1120
|
|
|
786
1121
|
For Python bindings (built with maturin):
|
|
@@ -843,6 +1178,181 @@ This library is under active development and has not reached 1.0 stability. Whil
|
|
|
843
1178
|
|
|
844
1179
|
---
|
|
845
1180
|
|
|
1181
|
+
## Benchmarks
|
|
1182
|
+
|
|
1183
|
+
<details>
|
|
1184
|
+
<summary><strong>Click to expand v0.6.0 benchmark results</strong></summary>
|
|
1185
|
+
|
|
1186
|
+
Benchmark results run on Windows with `cargo bench --no-default-features`. Times are median values.
|
|
1187
|
+
|
|
1188
|
+
### Core Regression Benchmarks
|
|
1189
|
+
|
|
1190
|
+
| Benchmark | Size (n × p) | Time | Throughput |
|
|
1191
|
+
|-----------|--------------|------|------------|
|
|
1192
|
+
| OLS Regression | 10 × 2 | 12.46 µs | 802.71 Kelem/s |
|
|
1193
|
+
| OLS Regression | 50 × 3 | 53.72 µs | 930.69 Kelem/s |
|
|
1194
|
+
| OLS Regression | 100 × 5 | 211.09 µs | 473.73 Kelem/s |
|
|
1195
|
+
| OLS Regression | 500 × 10 | 7.46 ms | 67.04 Kelem/s |
|
|
1196
|
+
| OLS Regression | 1000 × 20 | 47.81 ms | 20.91 Kelem/s |
|
|
1197
|
+
| OLS Regression | 5000 × 50 | 2.86 s | 1.75 Kelem/s |
|
|
1198
|
+
| Ridge Regression | 50 × 3 | 9.61 µs | 5.20 Melem/s |
|
|
1199
|
+
| Ridge Regression | 100 × 5 | 70.41 µs | 1.42 Melem/s |
|
|
1200
|
+
| Ridge Regression | 500 × 10 | 842.37 µs | 593.56 Kelem/s |
|
|
1201
|
+
| Ridge Regression | 1000 × 20 | 1.38 ms | 724.71 Kelem/s |
|
|
1202
|
+
| Ridge Regression | 5000 × 50 | 10.25 ms | 487.78 Kelem/s |
|
|
1203
|
+
| Lasso Regression | 50 × 3 | 258.82 µs | 193.18 Kelem/s |
|
|
1204
|
+
| Lasso Regression | 100 × 5 | 247.89 µs | 403.41 Kelem/s |
|
|
1205
|
+
| Lasso Regression | 500 × 10 | 3.58 ms | 139.86 Kelem/s |
|
|
1206
|
+
| Lasso Regression | 1000 × 20 | 1.54 ms | 651.28 Kelem/s |
|
|
1207
|
+
| Lasso Regression | 5000 × 50 | 12.52 ms | 399.50 Kelem/s |
|
|
1208
|
+
| Elastic Net Regression | 50 × 3 | 46.15 µs | 1.08 Melem/s |
|
|
1209
|
+
| Elastic Net Regression | 100 × 5 | 358.07 µs | 279.27 Kelem/s |
|
|
1210
|
+
| Elastic Net Regression | 500 × 10 | 1.61 ms | 310.18 Kelem/s |
|
|
1211
|
+
| Elastic Net Regression | 1000 × 20 | 1.60 ms | 623.66 Kelem/s |
|
|
1212
|
+
| Elastic Net Regression | 5000 × 50 | 12.57 ms | 397.77 Kelem/s |
|
|
1213
|
+
| WLS Regression | 50 × 3 | 32.92 µs | 1.52 Melem/s |
|
|
1214
|
+
| WLS Regression | 100 × 5 | 155.30 µs | 643.93 Kelem/s |
|
|
1215
|
+
| WLS Regression | 500 × 10 | 6.63 ms | 75.37 Kelem/s |
|
|
1216
|
+
| WLS Regression | 1000 × 20 | 42.68 ms | 23.43 Kelem/s |
|
|
1217
|
+
| WLS Regression | 5000 × 50 | 2.64 s | 1.89 Kelem/s |
|
|
1218
|
+
| LOESS Fit | 50 × 1 | 132.83 µs | 376.42 Kelem/s |
|
|
1219
|
+
| LOESS Fit | 100 × 1 | 1.16 ms | 86.00 Kelem/s |
|
|
1220
|
+
| LOESS Fit | 500 × 1 | 28.42 ms | 17.59 Kelem/s |
|
|
1221
|
+
| LOESS Fit | 1000 × 1 | 113.00 ms | 8.85 Kelem/s |
|
|
1222
|
+
| LOESS Fit | 100 × 2 | 7.10 ms | 14.09 Kelem/s |
|
|
1223
|
+
| LOESS Fit | 500 × 2 | 1.05 s | 476.19 elem/s |
|
|
1224
|
+
|
|
1225
|
+
### Lambda Path & Elastic Net Path Benchmarks
|
|
1226
|
+
|
|
1227
|
+
| Benchmark | Size (n × p) | Time | Throughput |
|
|
1228
|
+
|-----------|--------------|------|------------|
|
|
1229
|
+
| Elastic Net Path | 100 × 5 | 198.60 ms | 503.52 elem/s |
|
|
1230
|
+
| Elastic Net Path | 500 × 10 | 69.46 ms | 7.20 Kelem/s |
|
|
1231
|
+
| Elastic Net Path | 1000 × 20 | 39.08 ms | 25.59 Kelem/s |
|
|
1232
|
+
| Make Lambda Path | 100 × 5 | 1.09 µs | 91.58 Melem/s |
|
|
1233
|
+
| Make Lambda Path | 500 × 10 | 8.10 µs | 61.70 Melem/s |
|
|
1234
|
+
| Make Lambda Path | 1000 × 20 | 29.96 µs | 33.37 Melem/s |
|
|
1235
|
+
| Make Lambda Path | 5000 × 50 | 424.18 µs | 11.79 Melem/s |
|
|
1236
|
+
|
|
1237
|
+
### Diagnostic Test Benchmarks
|
|
1238
|
+
|
|
1239
|
+
| Benchmark | Size (n × p) | Time |
|
|
1240
|
+
|-----------|--------------|------|
|
|
1241
|
+
| Rainbow Test | 50 × 3 | 40.34 µs |
|
|
1242
|
+
| Rainbow Test | 100 × 5 | 187.94 µs |
|
|
1243
|
+
| Rainbow Test | 500 × 10 | 8.63 ms |
|
|
1244
|
+
| Rainbow Test | 1000 × 20 | 60.09 ms |
|
|
1245
|
+
| Rainbow Test | 5000 × 50 | 3.45 s |
|
|
1246
|
+
| Harvey-Collier Test | 50 × 1 | 15.26 µs |
|
|
1247
|
+
| Harvey-Collier Test | 100 × 1 | 30.32 µs |
|
|
1248
|
+
| Harvey-Collier Test | 500 × 1 | 138.44 µs |
|
|
1249
|
+
| Harvey-Collier Test | 1000 × 1 | 298.33 µs |
|
|
1250
|
+
| Breusch-Pagan Test | 50 × 3 | 58.07 µs |
|
|
1251
|
+
| Breusch-Pagan Test | 100 × 5 | 296.74 µs |
|
|
1252
|
+
| Breusch-Pagan Test | 500 × 10 | 13.79 ms |
|
|
1253
|
+
| Breusch-Pagan Test | 1000 × 20 | 96.49 ms |
|
|
1254
|
+
| Breusch-Pagan Test | 5000 × 50 | 5.56 s |
|
|
1255
|
+
| White Test | 50 × 3 | 14.31 µs |
|
|
1256
|
+
| White Test | 100 × 5 | 44.25 µs |
|
|
1257
|
+
| White Test | 500 × 10 | 669.40 µs |
|
|
1258
|
+
| White Test | 1000 × 20 | 4.89 ms |
|
|
1259
|
+
| Jarque-Bera Test | 50 × 3 | 30.13 µs |
|
|
1260
|
+
| Jarque-Bera Test | 100 × 5 | 149.29 µs |
|
|
1261
|
+
| Jarque-Bera Test | 500 × 10 | 6.64 ms |
|
|
1262
|
+
| Jarque-Bera Test | 1000 × 20 | 47.89 ms |
|
|
1263
|
+
| Jarque-Bera Test | 5000 × 50 | 2.75 s |
|
|
1264
|
+
| Durbin-Watson Test | 50 × 3 | 31.80 µs |
|
|
1265
|
+
| Durbin-Watson Test | 100 × 5 | 152.56 µs |
|
|
1266
|
+
| Durbin-Watson Test | 500 × 10 | 6.87 ms |
|
|
1267
|
+
| Durbin-Watson Test | 1000 × 20 | 48.65 ms |
|
|
1268
|
+
| Durbin-Watson Test | 5000 × 50 | 2.76 s |
|
|
1269
|
+
| Breusch-Godfrey Test | 50 × 3 | 71.73 µs |
|
|
1270
|
+
| Breusch-Godfrey Test | 100 × 5 | 348.94 µs |
|
|
1271
|
+
| Breusch-Godfrey Test | 500 × 10 | 14.77 ms |
|
|
1272
|
+
| Breusch-Godfrey Test | 1000 × 20 | 100.08 ms |
|
|
1273
|
+
| Breusch-Godfrey Test | 5000 × 50 | 5.64 s |
|
|
1274
|
+
| Shapiro-Wilk Test | 10 × 2 | 2.04 µs |
|
|
1275
|
+
| Shapiro-Wilk Test | 50 × 3 | 4.87 µs |
|
|
1276
|
+
| Shapiro-Wilk Test | 100 × 5 | 10.67 µs |
|
|
1277
|
+
| Shapiro-Wilk Test | 500 × 10 | 110.02 µs |
|
|
1278
|
+
| Shapiro-Wilk Test | 1000 × 20 | 635.13 µs |
|
|
1279
|
+
| Shapiro-Wilk Test | 5000 × 50 | 17.53 ms |
|
|
1280
|
+
| Anderson-Darling Test | 50 × 3 | 34.02 µs |
|
|
1281
|
+
| Anderson-Darling Test | 100 × 5 | 162.28 µs |
|
|
1282
|
+
| Anderson-Darling Test | 500 × 10 | 6.95 ms |
|
|
1283
|
+
| Anderson-Darling Test | 1000 × 20 | 48.15 ms |
|
|
1284
|
+
| Anderson-Darling Test | 5000 × 50 | 2.78 s |
|
|
1285
|
+
| Cook's Distance Test | 50 × 3 | 64.52 µs |
|
|
1286
|
+
| Cook's Distance Test | 100 × 5 | 297.69 µs |
|
|
1287
|
+
| Cook's Distance Test | 500 × 10 | 12.73 ms |
|
|
1288
|
+
| Cook's Distance Test | 1000 × 20 | 94.02 ms |
|
|
1289
|
+
| Cook's Distance Test | 5000 × 50 | 5.31 s |
|
|
1290
|
+
| DFBETAS Test | 50 × 3 | 46.34 µs |
|
|
1291
|
+
| DFBETAS Test | 100 × 5 | 185.52 µs |
|
|
1292
|
+
| DFBETAS Test | 500 × 10 | 7.04 ms |
|
|
1293
|
+
| DFBETAS Test | 1000 × 20 | 49.68 ms |
|
|
1294
|
+
| DFFITS Test | 50 × 3 | 33.56 µs |
|
|
1295
|
+
| DFFITS Test | 100 × 5 | 157.62 µs |
|
|
1296
|
+
| DFFITS Test | 500 × 10 | 6.82 ms |
|
|
1297
|
+
| DFFITS Test | 1000 × 20 | 48.35 ms |
|
|
1298
|
+
| VIF Test | 50 × 3 | 5.36 µs |
|
|
1299
|
+
| VIF Test | 100 × 5 | 12.68 µs |
|
|
1300
|
+
| VIF Test | 500 × 10 | 128.04 µs |
|
|
1301
|
+
| VIF Test | 1000 × 20 | 807.30 µs |
|
|
1302
|
+
| VIF Test | 5000 × 50 | 26.33 ms |
|
|
1303
|
+
| RESET Test | 50 × 3 | 77.85 µs |
|
|
1304
|
+
| RESET Test | 100 × 5 | 359.12 µs |
|
|
1305
|
+
| RESET Test | 500 × 10 | 14.40 ms |
|
|
1306
|
+
| RESET Test | 1000 × 20 | 100.52 ms |
|
|
1307
|
+
| RESET Test | 5000 × 50 | 5.67 s |
|
|
1308
|
+
| Full Diagnostics | 100 × 5 | 2.75 ms |
|
|
1309
|
+
| Full Diagnostics | 500 × 10 | 104.01 ms |
|
|
1310
|
+
| Full Diagnostics | 1000 × 20 | 740.52 ms |
|
|
1311
|
+
|
|
1312
|
+
### Linear Algebra Benchmarks
|
|
1313
|
+
|
|
1314
|
+
| Benchmark | Size | Time |
|
|
1315
|
+
|-----------|------|------|
|
|
1316
|
+
| Matrix Transpose | 10 × 10 | 209.50 ns |
|
|
1317
|
+
| Matrix Transpose | 50 × 50 | 3.67 µs |
|
|
1318
|
+
| Matrix Transpose | 100 × 100 | 14.92 µs |
|
|
1319
|
+
| Matrix Transpose | 500 × 500 | 924.23 µs |
|
|
1320
|
+
| Matrix Transpose | 1000 × 1000 | 5.56 ms |
|
|
1321
|
+
| Matrix Multiply (matmul) | 10 × 10 × 10 | 1.54 µs |
|
|
1322
|
+
| Matrix Multiply (matmul) | 50 × 50 × 50 | 144.15 µs |
|
|
1323
|
+
| Matrix Multiply (matmul) | 100 × 100 × 100 | 1.39 ms |
|
|
1324
|
+
| Matrix Multiply (matmul) | 200 × 200 × 200 | 11.90 ms |
|
|
1325
|
+
| Matrix Multiply (matmul) | 1000 × 100 × 100 | 13.94 ms |
|
|
1326
|
+
| QR Decomposition | 10 × 5 | 1.41 µs |
|
|
1327
|
+
| QR Decomposition | 50 × 10 | 14.81 µs |
|
|
1328
|
+
| QR Decomposition | 100 × 20 | 57.61 µs |
|
|
1329
|
+
| QR Decomposition | 500 × 50 | 2.19 ms |
|
|
1330
|
+
| QR Decomposition | 1000 × 100 | 19.20 ms |
|
|
1331
|
+
| QR Decomposition | 5000 × 100 | 1.48 s |
|
|
1332
|
+
| QR Decomposition | 10000 × 100 | 8.09 s |
|
|
1333
|
+
| QR Decomposition | 1000 × 500 | 84.48 ms |
|
|
1334
|
+
| SVD | 10 × 5 | 150.36 µs |
|
|
1335
|
+
| SVD | 50 × 10 | 505.41 µs |
|
|
1336
|
+
| SVD | 100 × 20 | 2.80 ms |
|
|
1337
|
+
| SVD | 500 × 50 | 60.00 ms |
|
|
1338
|
+
| SVD | 1000 × 100 | 513.35 ms |
|
|
1339
|
+
| Matrix Invert | 5 × 5 | 877.32 ns |
|
|
1340
|
+
| Matrix Invert | 10 × 10 | 2.48 µs |
|
|
1341
|
+
| Matrix Invert | 20 × 20 | 5.46 µs |
|
|
1342
|
+
| Matrix Invert | 50 × 50 | 31.94 µs |
|
|
1343
|
+
| Matrix Invert | 100 × 100 | 141.38 µs |
|
|
1344
|
+
| Matrix Invert | 200 × 200 | 647.03 µs |
|
|
1345
|
+
|
|
1346
|
+
### Pressure Benchmarks (Large Datasets)
|
|
1347
|
+
|
|
1348
|
+
| Benchmark | Size (n) | Time |
|
|
1349
|
+
|-----------|----------|------|
|
|
1350
|
+
| Pressure (OLS + all diagnostics) | 10000 | 11.28 s |
|
|
1351
|
+
|
|
1352
|
+
</details>
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
846
1356
|
## License
|
|
847
1357
|
|
|
848
1358
|
Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE).
|