1#![cfg(feature = "wasm")]
9
10use wasm_bindgen::prelude::*;
11use std::collections::HashSet;
12use serde::Serialize;
13
14use crate::core;
16use crate::diagnostics;
17use crate::distributions::{normal_inverse_cdf, student_t_cdf};
18use crate::error::{error_json, error_to_json, Error, Result};
19use crate::linalg;
20use crate::loess;
21use crate::regularized;
22use crate::stats;
23
24#[derive(Serialize)]
29struct ParsedCsv {
30 headers: Vec<String>,
31 data: Vec<serde_json::Map<String, serde_json::Value>>,
32 numeric_columns: Vec<String>,
33}
34
35#[wasm_bindgen]
36pub fn parse_csv(content: &str) -> String {
60 if let Err(e) = check_domain() {
61 return error_to_json(&e);
62 }
63
64 let mut reader = csv::ReaderBuilder::new()
65 .has_headers(true)
66 .flexible(true)
67 .from_reader(content.as_bytes());
68
69 let headers: Vec<String> = match reader.headers() {
71 Ok(h) => h.iter().map(|s| s.to_string()).collect(),
72 Err(e) => return error_json(&format!("Failed to read headers: {}", e)),
73 };
74
75 let mut data = Vec::new();
76 let mut numeric_col_set = HashSet::new();
77
78 for result in reader.records() {
79 let record = match result {
80 Ok(r) => r,
81 Err(e) => return error_json(&format!("Failed to parse CSV record: {}", e)),
82 };
83
84 if record.len() != headers.len() {
85 continue;
86 }
87
88 let mut row_map = serde_json::Map::new();
89
90 for (i, field) in record.iter().enumerate() {
91 if i >= headers.len() {
92 continue;
93 }
94
95 let header = &headers[i];
96 let val_trimmed = field.trim();
97
98 if let Ok(num) = val_trimmed.parse::<f64>() {
100 if num.is_finite() {
101 row_map.insert(
102 header.clone(),
103 serde_json::Value::Number(serde_json::Number::from_f64(num).unwrap()),
104 );
105 numeric_col_set.insert(header.clone());
106 continue;
107 }
108 }
109
110 row_map.insert(
112 header.clone(),
113 serde_json::Value::String(val_trimmed.to_string()),
114 );
115 }
116 data.push(row_map);
117 }
118
119 let mut numeric_columns: Vec<String> = numeric_col_set.into_iter().collect();
120 numeric_columns.sort();
121
122 let output = ParsedCsv {
123 headers,
124 data,
125 numeric_columns,
126 };
127
128 serde_json::to_string(&output).unwrap_or_else(|_| error_json("Failed to serialize CSV output"))
129}
130
131#[wasm_bindgen]
136pub fn ols_regression(y_json: &str, x_vars_json: &str, variable_names: &str) -> String {
161 if let Err(e) = check_domain() {
162 return error_to_json(&e);
163 }
164
165 let y: Vec<f64> = match serde_json::from_str(y_json) {
167 Ok(v) => v,
168 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
169 };
170
171 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
172 Ok(v) => v,
173 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
174 };
175
176 let names: Vec<String> = match serde_json::from_str(variable_names) {
177 Ok(v) => v,
178 Err(_) => vec!["Intercept".to_string()],
179 };
180
181 match core::ols_regression(&y, &x_vars, &names) {
183 Ok(output) => serde_json::to_string(&output)
184 .unwrap_or_else(|_| error_json("Failed to serialize output")),
185 Err(e) => error_json(&e.to_string()),
186 }
187}
188
189#[wasm_bindgen]
213pub fn rainbow_test(y_json: &str, x_vars_json: &str, fraction: f64, method: &str) -> String {
214 if let Err(e) = check_domain() {
215 return error_to_json(&e);
216 }
217
218 let y: Vec<f64> = match serde_json::from_str(y_json) {
219 Ok(v) => v,
220 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
221 };
222
223 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
224 Ok(v) => v,
225 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
226 };
227
228 let method = match method.to_lowercase().as_str() {
230 "python" => diagnostics::RainbowMethod::Python,
231 "both" => diagnostics::RainbowMethod::Both,
232 _ => diagnostics::RainbowMethod::R, };
234
235 match diagnostics::rainbow_test(&y, &x_vars, fraction, method) {
236 Ok(output) => serde_json::to_string(&output)
237 .unwrap_or_else(|_| error_json("Failed to serialize Rainbow test result")),
238 Err(e) => error_json(&e.to_string()),
239 }
240}
241
242#[wasm_bindgen]
261pub fn harvey_collier_test(y_json: &str, x_vars_json: &str) -> String {
262 if let Err(e) = check_domain() {
263 return error_to_json(&e);
264 }
265
266 let y: Vec<f64> = match serde_json::from_str(y_json) {
267 Ok(v) => v,
268 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
269 };
270
271 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
272 Ok(v) => v,
273 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
274 };
275
276 match diagnostics::harvey_collier_test(&y, &x_vars, diagnostics::HarveyCollierMethod::R) {
277 Ok(output) => serde_json::to_string(&output)
278 .unwrap_or_else(|_| error_json("Failed to serialize Harvey-Collier test result")),
279 Err(e) => error_json(&e.to_string()),
280 }
281}
282
283#[wasm_bindgen]
302pub fn breusch_pagan_test(y_json: &str, x_vars_json: &str) -> String {
303 if let Err(e) = check_domain() {
304 return error_to_json(&e);
305 }
306
307 let y: Vec<f64> = match serde_json::from_str(y_json) {
308 Ok(v) => v,
309 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
310 };
311
312 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
313 Ok(v) => v,
314 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
315 };
316
317 match diagnostics::breusch_pagan_test(&y, &x_vars) {
318 Ok(output) => serde_json::to_string(&output)
319 .unwrap_or_else(|_| error_json("Failed to serialize Breusch-Pagan test result")),
320 Err(e) => error_json(&e.to_string()),
321 }
322}
323
324#[wasm_bindgen]
344pub fn white_test(y_json: &str, x_vars_json: &str, method: &str) -> String {
345 if let Err(e) = check_domain() {
346 return error_to_json(&e);
347 }
348
349 let y: Vec<f64> = match serde_json::from_str(y_json) {
350 Ok(v) => v,
351 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
352 };
353
354 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
355 Ok(v) => v,
356 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
357 };
358
359 let method = match method.to_lowercase().as_str() {
361 "python" => diagnostics::WhiteMethod::Python,
362 "both" => diagnostics::WhiteMethod::Both,
363 _ => diagnostics::WhiteMethod::R, };
365
366 match diagnostics::white_test(&y, &x_vars, method) {
367 Ok(output) => serde_json::to_string(&output)
368 .unwrap_or_else(|_| error_json("Failed to serialize White test result")),
369 Err(e) => error_json(&e.to_string()),
370 }
371}
372
373#[wasm_bindgen]
392pub fn r_white_test(y_json: &str, x_vars_json: &str) -> String {
393 if let Err(e) = check_domain() {
394 return error_to_json(&e);
395 }
396
397 let y: Vec<f64> = match serde_json::from_str(y_json) {
398 Ok(v) => v,
399 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
400 };
401
402 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
403 Ok(v) => v,
404 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
405 };
406
407 match diagnostics::r_white_method(&y, &x_vars) {
408 Ok(output) => serde_json::to_string(&output)
409 .unwrap_or_else(|_| error_json("Failed to serialize R White test result")),
410 Err(e) => error_json(&e.to_string()),
411 }
412}
413
414#[wasm_bindgen]
433pub fn python_white_test(y_json: &str, x_vars_json: &str) -> String {
434 if let Err(e) = check_domain() {
435 return error_to_json(&e);
436 }
437
438 let y: Vec<f64> = match serde_json::from_str(y_json) {
439 Ok(v) => v,
440 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
441 };
442
443 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
444 Ok(v) => v,
445 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
446 };
447
448 match diagnostics::python_white_method(&y, &x_vars) {
449 Ok(output) => serde_json::to_string(&output)
450 .unwrap_or_else(|_| error_json("Failed to serialize Python White test result")),
451 Err(e) => error_json(&e.to_string()),
452 }
453}
454
455#[wasm_bindgen]
474pub fn jarque_bera_test(y_json: &str, x_vars_json: &str) -> String {
475 if let Err(e) = check_domain() {
476 return error_to_json(&e);
477 }
478
479 let y: Vec<f64> = match serde_json::from_str(y_json) {
480 Ok(v) => v,
481 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
482 };
483
484 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
485 Ok(v) => v,
486 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
487 };
488
489 match diagnostics::jarque_bera_test(&y, &x_vars) {
490 Ok(output) => serde_json::to_string(&output)
491 .unwrap_or_else(|_| error_json("Failed to serialize Jarque-Bera test result")),
492 Err(e) => error_json(&e.to_string()),
493 }
494}
495
496#[wasm_bindgen]
519pub fn durbin_watson_test(y_json: &str, x_vars_json: &str) -> String {
520 if let Err(e) = check_domain() {
521 return error_to_json(&e);
522 }
523
524 let y: Vec<f64> = match serde_json::from_str(y_json) {
525 Ok(v) => v,
526 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
527 };
528
529 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
530 Ok(v) => v,
531 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
532 };
533
534 match diagnostics::durbin_watson_test(&y, &x_vars) {
535 Ok(output) => serde_json::to_string(&output)
536 .unwrap_or_else(|_| error_json("Failed to serialize Durbin-Watson test result")),
537 Err(e) => error_json(&e.to_string()),
538 }
539}
540
541#[wasm_bindgen]
565pub fn shapiro_wilk_test(y_json: &str, x_vars_json: &str) -> String {
566 if let Err(e) = check_domain() {
567 return error_to_json(&e);
568 }
569
570 let y: Vec<f64> = match serde_json::from_str(y_json) {
571 Ok(v) => v,
572 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
573 };
574
575 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
576 Ok(v) => v,
577 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
578 };
579
580 match diagnostics::shapiro_wilk_test(&y, &x_vars) {
581 Ok(output) => serde_json::to_string(&output)
582 .unwrap_or_else(|_| error_json("Failed to serialize Shapiro-Wilk test result")),
583 Err(e) => error_json(&e.to_string()),
584 }
585}
586
587#[wasm_bindgen]
607pub fn anderson_darling_test(y_json: &str, x_vars_json: &str) -> String {
608 if let Err(e) = check_domain() {
609 return error_to_json(&e);
610 }
611
612 let y: Vec<f64> = match serde_json::from_str(y_json) {
613 Ok(v) => v,
614 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
615 };
616
617 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
618 Ok(v) => v,
619 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
620 };
621
622 match diagnostics::anderson_darling_test(&y, &x_vars) {
623 Ok(output) => serde_json::to_string(&output)
624 .unwrap_or_else(|_| error_json("Failed to serialize Anderson-Darling test result")),
625 Err(e) => error_json(&e.to_string()),
626 }
627}
628
629#[wasm_bindgen]
656pub fn cooks_distance_test(y_json: &str, x_vars_json: &str) -> String {
657 if let Err(e) = check_domain() {
658 return error_to_json(&e);
659 }
660
661 let y: Vec<f64> = match serde_json::from_str(y_json) {
662 Ok(v) => v,
663 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
664 };
665
666 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
667 Ok(v) => v,
668 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
669 };
670
671 match diagnostics::cooks_distance_test(&y, &x_vars) {
672 Ok(output) => serde_json::to_string(&output)
673 .unwrap_or_else(|_| error_json("Failed to serialize Cook's distance result")),
674 Err(e) => error_json(&e.to_string()),
675 }
676}
677
678#[wasm_bindgen]
697pub fn dfbetas_test(y_json: &str, x_vars_json: &str) -> String {
698 if let Err(e) = check_domain() {
699 return error_to_json(&e);
700 }
701
702 let y: Vec<f64> = match serde_json::from_str(y_json) {
703 Ok(v) => v,
704 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
705 };
706
707 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
708 Ok(v) => v,
709 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
710 };
711
712 match diagnostics::dfbetas_test(&y, &x_vars) {
713 Ok(output) => serde_json::to_string(&output)
714 .unwrap_or_else(|_| error_json("Failed to serialize DFBETAS result")),
715 Err(e) => error_json(&e.to_string()),
716 }
717}
718
719#[wasm_bindgen]
738pub fn dffits_test(y_json: &str, x_vars_json: &str) -> String {
739 if let Err(e) = check_domain() {
740 return error_to_json(&e);
741 }
742
743 let y: Vec<f64> = match serde_json::from_str(y_json) {
744 Ok(v) => v,
745 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
746 };
747
748 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
749 Ok(v) => v,
750 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
751 };
752
753 match diagnostics::dffits_test(&y, &x_vars) {
754 Ok(output) => serde_json::to_string(&output)
755 .unwrap_or_else(|_| error_json("Failed to serialize DFFITS result")),
756 Err(e) => error_json(&e.to_string()),
757 }
758}
759
760#[wasm_bindgen]
786pub fn vif_test(y_json: &str, x_vars_json: &str) -> String {
787 if let Err(e) = check_domain() {
788 return error_to_json(&e);
789 }
790
791 let y: Vec<f64> = match serde_json::from_str(y_json) {
792 Ok(v) => v,
793 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
794 };
795
796 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
797 Ok(v) => v,
798 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
799 };
800
801 match diagnostics::vif_test(&y, &x_vars) {
802 Ok(output) => serde_json::to_string(&output)
803 .unwrap_or_else(|_| error_json("Failed to serialize VIF result")),
804 Err(e) => error_json(&e.to_string()),
805 }
806}
807
808#[wasm_bindgen]
829pub fn reset_test(y_json: &str, x_vars_json: &str, powers_json: &str, type_: &str) -> String {
830 if let Err(e) = check_domain() {
831 return error_to_json(&e);
832 }
833
834 let y: Vec<f64> = match serde_json::from_str(y_json) {
835 Ok(v) => v,
836 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
837 };
838
839 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
840 Ok(v) => v,
841 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
842 };
843
844 let powers: Vec<usize> = match serde_json::from_str(powers_json) {
845 Ok(v) => v,
846 Err(e) => return error_json(&format!("Failed to parse powers: {}", e)),
847 };
848
849 let reset_type = match type_.to_lowercase().as_str() {
851 "regressor" => diagnostics::ResetType::Regressor,
852 "princomp" => diagnostics::ResetType::PrincipalComponent,
853 _ => diagnostics::ResetType::Fitted,
854 };
855
856 match diagnostics::reset_test(&y, &x_vars, &powers, reset_type) {
857 Ok(output) => serde_json::to_string(&output)
858 .unwrap_or_else(|_| error_json("Failed to serialize RESET test result")),
859 Err(e) => error_json(&e.to_string()),
860 }
861}
862
863#[wasm_bindgen]
883pub fn breusch_godfrey_test(y_json: &str, x_vars_json: &str, order: usize, test_type: &str) -> String {
884 if let Err(e) = check_domain() {
885 return error_to_json(&e);
886 }
887
888 let y: Vec<f64> = match serde_json::from_str(y_json) {
889 Ok(v) => v,
890 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
891 };
892
893 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
894 Ok(v) => v,
895 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
896 };
897
898 let bg_test_type = match test_type.to_lowercase().as_str() {
900 "f" => diagnostics::BGTestType::F,
901 _ => diagnostics::BGTestType::Chisq,
902 };
903
904 match diagnostics::breusch_godfrey_test(&y, &x_vars, order, bg_test_type) {
905 Ok(output) => serde_json::to_string(&output)
906 .unwrap_or_else(|_| error_json("Failed to serialize Breusch-Godfrey test result")),
907 Err(e) => error_json(&e.to_string()),
908 }
909}
910
911#[wasm_bindgen]
943pub fn loess_fit(
944 y_json: &str,
945 x_vars_json: &str,
946 span: f64,
947 degree: usize,
948 robust_iterations: usize,
949 surface: &str,
950) -> String {
951 if let Err(e) = check_domain() {
952 return error_to_json(&e);
953 }
954
955 let y: Vec<f64> = match serde_json::from_str(y_json) {
956 Ok(v) => v,
957 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
958 };
959
960 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
961 Ok(v) => v,
962 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
963 };
964
965 let n_predictors = x_vars.len();
966
967 let surface = match surface.to_lowercase().as_str() {
969 "interpolate" => loess::LoessSurface::Interpolate,
970 _ => loess::LoessSurface::Direct,
971 };
972
973 let options = loess::LoessOptions {
974 span,
975 degree,
976 robust_iterations,
977 n_predictors,
978 surface,
979 };
980
981 match loess::loess_fit(&y, &x_vars, &options) {
982 Ok(output) => serde_json::to_string(&output)
983 .unwrap_or_else(|_| error_json("Failed to serialize LOESS result")),
984 Err(e) => error_json(&e.to_string()),
985 }
986}
987
988#[wasm_bindgen]
1013pub fn loess_predict(
1014 new_x_json: &str,
1015 original_x_json: &str,
1016 original_y_json: &str,
1017 span: f64,
1018 degree: usize,
1019 robust_iterations: usize,
1020 surface: &str,
1021) -> String {
1022 if let Err(e) = check_domain() {
1023 return error_to_json(&e);
1024 }
1025
1026 let new_x: Vec<Vec<f64>> = match serde_json::from_str(new_x_json) {
1027 Ok(v) => v,
1028 Err(e) => return error_json(&format!("Failed to parse new_x: {}", e)),
1029 };
1030
1031 let original_x: Vec<Vec<f64>> = match serde_json::from_str(original_x_json) {
1032 Ok(v) => v,
1033 Err(e) => return error_json(&format!("Failed to parse original_x: {}", e)),
1034 };
1035
1036 let original_y: Vec<f64> = match serde_json::from_str(original_y_json) {
1037 Ok(v) => v,
1038 Err(e) => return error_json(&format!("Failed to parse original_y: {}", e)),
1039 };
1040
1041 let n_predictors = original_x.len();
1042
1043 let surface = match surface.to_lowercase().as_str() {
1046 "interpolate" => loess::LoessSurface::Interpolate,
1047 _ => loess::LoessSurface::Direct,
1048 };
1049
1050 let fit = loess::LoessFit {
1051 fitted: vec![0.0; original_y.len()], predictions: None,
1053 span,
1054 degree,
1055 robust_iterations,
1056 surface,
1057 };
1058
1059 let options = loess::LoessOptions {
1060 span,
1061 degree,
1062 robust_iterations,
1063 n_predictors,
1064 surface,
1065 };
1066
1067 match fit.predict(&new_x, &original_x, &original_y, &options) {
1068 Ok(predictions) => {
1069 let result = serde_json::json!({
1070 "predictions": predictions
1071 });
1072 result.to_string()
1073 },
1074 Err(e) => error_json(&e.to_string()),
1075 }
1076}
1077
1078#[wasm_bindgen]
1083pub fn ridge_regression(
1111 y_json: &str,
1112 x_vars_json: &str,
1113 _variable_names: &str,
1114 lambda: f64,
1115 standardize: bool,
1116) -> String {
1117 if let Err(e) = check_domain() {
1118 return error_to_json(&e);
1119 }
1120
1121 let y: Vec<f64> = match serde_json::from_str(y_json) {
1123 Ok(v) => v,
1124 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
1125 };
1126
1127 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
1128 Ok(v) => v,
1129 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
1130 };
1131
1132 let n = y.len();
1134 let p = x_vars.len();
1135
1136 if n <= p + 1 {
1137 return error_json(&format!(
1138 "Insufficient data: need at least {} observations for {} predictors",
1139 p + 2,
1140 p
1141 ));
1142 }
1143
1144 let mut x_data = vec![1.0; n * (p + 1)]; for (j, x_var) in x_vars.iter().enumerate() {
1146 if x_var.len() != n {
1147 return error_json(&format!(
1148 "x_vars[{}] has {} elements, expected {}",
1149 j,
1150 x_var.len(),
1151 n
1152 ));
1153 }
1154 for (i, &val) in x_var.iter().enumerate() {
1155 x_data[i * (p + 1) + j + 1] = val;
1156 }
1157 }
1158
1159 let x = linalg::Matrix::new(n, p + 1, x_data);
1160
1161 let options = regularized::ridge::RidgeFitOptions {
1163 lambda,
1164 intercept: true,
1165 standardize,
1166 max_iter: 100000,
1167 tol: 1e-7,
1168 warm_start: None,
1169 weights: None,
1170 };
1171
1172 match regularized::ridge::ridge_fit(&x, &y, &options) {
1173 Ok(output) => serde_json::to_string(&output)
1174 .unwrap_or_else(|_| error_json("Failed to serialize ridge regression result")),
1175 Err(e) => error_json(&e.to_string()),
1176 }
1177}
1178
1179#[wasm_bindgen]
1180pub fn lasso_regression(
1213 y_json: &str,
1214 x_vars_json: &str,
1215 _variable_names: &str,
1216 lambda: f64,
1217 standardize: bool,
1218 max_iter: usize,
1219 tol: f64,
1220) -> String {
1221 if let Err(e) = check_domain() {
1222 return error_to_json(&e);
1223 }
1224
1225 let y: Vec<f64> = match serde_json::from_str(y_json) {
1227 Ok(v) => v,
1228 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
1229 };
1230
1231 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
1232 Ok(v) => v,
1233 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
1234 };
1235
1236 let n = y.len();
1238 let p = x_vars.len();
1239
1240 if n <= p + 1 {
1241 return error_json(&format!(
1242 "Insufficient data: need at least {} observations for {} predictors",
1243 p + 2,
1244 p
1245 ));
1246 }
1247
1248 let mut x_data = vec![1.0; n * (p + 1)]; for (j, x_var) in x_vars.iter().enumerate() {
1250 if x_var.len() != n {
1251 return error_json(&format!(
1252 "x_vars[{}] has {} elements, expected {}",
1253 j,
1254 x_var.len(),
1255 n
1256 ));
1257 }
1258 for (i, &val) in x_var.iter().enumerate() {
1259 x_data[i * (p + 1) + j + 1] = val;
1260 }
1261 }
1262
1263 let x = linalg::Matrix::new(n, p + 1, x_data);
1264
1265 let options = regularized::lasso::LassoFitOptions {
1267 lambda,
1268 intercept: true,
1269 standardize,
1270 max_iter,
1271 tol,
1272 ..Default::default()
1273 };
1274
1275 match regularized::lasso::lasso_fit(&x, &y, &options) {
1276 Ok(output) => serde_json::to_string(&output)
1277 .unwrap_or_else(|_| error_json("Failed to serialize lasso regression result")),
1278 Err(e) => error_json(&e.to_string()),
1279 }
1280}
1281
1282#[wasm_bindgen]
1283#[allow(clippy::too_many_arguments)]
1284pub fn elastic_net_regression(
1308 y_json: &str,
1309 x_vars_json: &str,
1310 _variable_names: &str,
1311 lambda: f64,
1312 alpha: f64,
1313 standardize: bool,
1314 max_iter: usize,
1315 tol: f64,
1316) -> String {
1317 if let Err(e) = check_domain() {
1318 return error_to_json(&e);
1319 }
1320
1321 let y: Vec<f64> = match serde_json::from_str(y_json) {
1323 Ok(v) => v,
1324 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
1325 };
1326
1327 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
1328 Ok(v) => v,
1329 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
1330 };
1331
1332 let n = y.len();
1334 let p = x_vars.len();
1335
1336 if n <= p + 1 {
1337 return error_json(&format!(
1338 "Insufficient data: need at least {} observations for {} predictors",
1339 p + 2,
1340 p
1341 ));
1342 }
1343
1344 let mut x_data = vec![1.0; n * (p + 1)]; for (j, x_var) in x_vars.iter().enumerate() {
1346 if x_var.len() != n {
1347 return error_json(&format!(
1348 "x_vars[{}] has {} elements, expected {}",
1349 j,
1350 x_var.len(),
1351 n
1352 ));
1353 }
1354 for (i, &val) in x_var.iter().enumerate() {
1355 x_data[i * (p + 1) + j + 1] = val;
1356 }
1357 }
1358
1359 let x = linalg::Matrix::new(n, p + 1, x_data);
1360
1361 let options = regularized::elastic_net::ElasticNetOptions {
1363 lambda,
1364 alpha,
1365 intercept: true,
1366 standardize,
1367 max_iter,
1368 tol,
1369 ..Default::default()
1370 };
1371
1372 match regularized::elastic_net::elastic_net_fit(&x, &y, &options) {
1373 Ok(output) => serde_json::to_string(&output)
1374 .unwrap_or_else(|_| error_json("Failed to serialize elastic net regression result")),
1375 Err(e) => error_json(&e.to_string()),
1376 }
1377}
1378
1379#[wasm_bindgen]
1380pub fn make_lambda_path(
1406 y_json: &str,
1407 x_vars_json: &str,
1408 n_lambda: usize,
1409 lambda_min_ratio: f64,
1410) -> String {
1411 if let Err(e) = check_domain() {
1412 return error_to_json(&e);
1413 }
1414
1415 let y: Vec<f64> = match serde_json::from_str(y_json) {
1417 Ok(v) => v,
1418 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
1419 };
1420
1421 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
1422 Ok(v) => v,
1423 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
1424 };
1425
1426 let n = y.len();
1428 let p = x_vars.len();
1429
1430 let mut x_data = vec![1.0; n * (p + 1)]; for (j, x_var) in x_vars.iter().enumerate() {
1432 if x_var.len() != n {
1433 return error_json(&format!(
1434 "x_vars[{}] has {} elements, expected {}",
1435 j,
1436 x_var.len(),
1437 n
1438 ));
1439 }
1440 for (i, &val) in x_var.iter().enumerate() {
1441 x_data[i * (p + 1) + j + 1] = val;
1442 }
1443 }
1444
1445 let x = linalg::Matrix::new(n, p + 1, x_data);
1446
1447 let x_mean: Vec<f64> = (0..x.cols)
1449 .map(|j| {
1450 if j == 0 {
1451 1.0 } else {
1453 (0..n).map(|i| x.get(i, j)).sum::<f64>() / n as f64
1454 }
1455 })
1456 .collect();
1457
1458 let x_standardized: Vec<f64> = (0..x.cols)
1459 .map(|j| {
1460 if j == 0 {
1461 0.0 } else {
1463 let mean = x_mean[j];
1464 let variance =
1465 (0..n).map(|i| (x.get(i, j) - mean).powi(2)).sum::<f64>() / (n - 1) as f64;
1466 variance.sqrt()
1467 }
1468 })
1469 .collect();
1470
1471 let mut x_standardized_data = vec![1.0; n * (p + 1)];
1473 for j in 0..x.cols {
1474 for i in 0..n {
1475 if j == 0 {
1476 x_standardized_data[i * (p + 1)] = 1.0; } else {
1478 let std = x_standardized[j];
1479 if std > 1e-10 {
1480 x_standardized_data[i * (p + 1) + j] = (x.get(i, j) - x_mean[j]) / std;
1481 } else {
1482 x_standardized_data[i * (p + 1) + j] = 0.0;
1483 }
1484 }
1485 }
1486 }
1487 let x_standardized = linalg::Matrix::new(n, p + 1, x_standardized_data);
1488
1489 let y_mean: f64 = y.iter().sum::<f64>() / n as f64;
1491 let y_centered: Vec<f64> = y.iter().map(|&yi| yi - y_mean).collect();
1492
1493 let options = regularized::path::LambdaPathOptions {
1495 nlambda: n_lambda.max(1),
1496 lambda_min_ratio: if lambda_min_ratio > 0.0 {
1497 Some(lambda_min_ratio)
1498 } else {
1499 None
1500 },
1501 alpha: 1.0, ..Default::default()
1503 };
1504
1505 let lambda_path =
1506 regularized::path::make_lambda_path(&x_standardized, &y_centered, &options, None, Some(0));
1507
1508 let lambda_max = lambda_path.first().copied().unwrap_or(0.0);
1509 let lambda_min = lambda_path.last().copied().unwrap_or(0.0);
1510
1511 let result = serde_json::json!({
1513 "lambda_path": lambda_path,
1514 "lambda_max": lambda_max,
1515 "lambda_min": lambda_min,
1516 "n_lambda": lambda_path.len()
1517 });
1518
1519 result.to_string()
1520}
1521
1522#[wasm_bindgen]
1527pub fn get_t_cdf(t: f64, df: f64) -> f64 {
1540 if check_domain().is_err() {
1541 return f64::NAN;
1542 }
1543
1544 student_t_cdf(t, df)
1545}
1546
1547#[wasm_bindgen]
1548pub fn get_t_critical(alpha: f64, df: f64) -> f64 {
1562 if check_domain().is_err() {
1563 return f64::NAN;
1564 }
1565
1566 core::t_critical_quantile(df, alpha)
1567}
1568
1569#[wasm_bindgen]
1570pub fn get_normal_inverse(p: f64) -> f64 {
1582 if check_domain().is_err() {
1583 return f64::NAN;
1584 }
1585
1586 normal_inverse_cdf(p)
1587}
1588
1589#[wasm_bindgen]
1594pub fn stats_mean(data_json: String) -> String {
1604 if check_domain().is_err() {
1605 return "null".to_string();
1606 }
1607
1608 let data: Vec<f64> = match serde_json::from_str(&data_json) {
1609 Ok(d) => d,
1610 Err(_) => return "null".to_string(),
1611 };
1612
1613 serde_json::to_string(&stats::mean(&data)).unwrap_or("null".to_string())
1614}
1615
1616#[wasm_bindgen]
1617pub fn stats_stddev(data_json: String) -> String {
1629 if check_domain().is_err() {
1630 return "null".to_string();
1631 }
1632
1633 let data: Vec<f64> = match serde_json::from_str(&data_json) {
1634 Ok(d) => d,
1635 Err(_) => return "null".to_string(),
1636 };
1637
1638 serde_json::to_string(&stats::stddev(&data)).unwrap_or("null".to_string())
1639}
1640
1641#[wasm_bindgen]
1642pub fn stats_variance(data_json: String) -> String {
1654 if check_domain().is_err() {
1655 return "null".to_string();
1656 }
1657
1658 let data: Vec<f64> = match serde_json::from_str(&data_json) {
1659 Ok(d) => d,
1660 Err(_) => return "null".to_string(),
1661 };
1662
1663 serde_json::to_string(&stats::variance(&data)).unwrap_or("null".to_string())
1664}
1665
1666#[wasm_bindgen]
1667pub fn stats_median(data_json: String) -> String {
1677 if check_domain().is_err() {
1678 return "null".to_string();
1679 }
1680
1681 let data: Vec<f64> = match serde_json::from_str(&data_json) {
1682 Ok(d) => d,
1683 Err(_) => return "null".to_string(),
1684 };
1685
1686 serde_json::to_string(&stats::median(&data)).unwrap_or("null".to_string())
1687}
1688
1689#[wasm_bindgen]
1690pub fn stats_quantile(data_json: String, q: f64) -> String {
1701 if check_domain().is_err() {
1702 return "null".to_string();
1703 }
1704
1705 let data: Vec<f64> = match serde_json::from_str(&data_json) {
1706 Ok(d) => d,
1707 Err(_) => return "null".to_string(),
1708 };
1709
1710 serde_json::to_string(&stats::quantile(&data, q)).unwrap_or("null".to_string())
1711}
1712
1713#[wasm_bindgen]
1714pub fn stats_correlation(x_json: String, y_json: String) -> String {
1725 if check_domain().is_err() {
1726 return "null".to_string();
1727 }
1728
1729 let x: Vec<f64> = match serde_json::from_str(&x_json) {
1730 Ok(d) => d,
1731 Err(_) => return "null".to_string(),
1732 };
1733
1734 let y: Vec<f64> = match serde_json::from_str(&y_json) {
1735 Ok(d) => d,
1736 Err(_) => return "null".to_string(),
1737 };
1738
1739 serde_json::to_string(&stats::correlation(&x, &y)).unwrap_or("null".to_string())
1740}
1741
1742fn check_domain() -> Result<()> {
1758 let allowed_domains = option_env!("LINREG_DOMAIN_RESTRICT");
1760
1761 match allowed_domains {
1762 Some(domains) if !domains.is_empty() => {
1763 let window =
1765 web_sys::window().ok_or(Error::DomainCheck("No window found".to_string()))?;
1766 let location = window.location();
1767 let hostname = location
1768 .hostname()
1769 .map_err(|_| Error::DomainCheck("No hostname found".to_string()))?;
1770
1771 let domain_list: Vec<&str> = domains.split(',').map(|s| s.trim()).collect();
1772
1773 if domain_list.contains(&hostname.as_str()) {
1774 Ok(())
1775 } else {
1776 Err(Error::DomainCheck(format!(
1777 "Unauthorized domain: {}. Allowed: {}",
1778 hostname, domains
1779 )))
1780 }
1781 },
1782 _ => {
1783 Ok(())
1785 },
1786 }
1787}
1788
1789#[wasm_bindgen]
1794pub fn test() -> String {
1802 if let Err(e) = check_domain() {
1803 return error_to_json(&e);
1804 }
1805 "Rust WASM is working!".to_string()
1806}
1807
1808#[wasm_bindgen]
1809pub fn get_version() -> String {
1817 if let Err(e) = check_domain() {
1818 return error_to_json(&e);
1819 }
1820 env!("CARGO_PKG_VERSION").to_string()
1821}
1822
1823#[wasm_bindgen]
1824pub fn test_t_critical(df: f64, alpha: f64) -> String {
1832 if let Err(e) = check_domain() {
1833 return error_to_json(&e);
1834 }
1835 let t_crit = core::t_critical_quantile(df, alpha);
1836 format!(
1837 r#"{{"df": {}, "alpha": {}, "t_critical": {}}}"#,
1838 df, alpha, t_crit
1839 )
1840}
1841
1842#[wasm_bindgen]
1843pub fn test_ci(coef: f64, se: f64, df: f64, alpha: f64) -> String {
1851 if let Err(e) = check_domain() {
1852 return error_to_json(&e);
1853 }
1854 let t_crit = core::t_critical_quantile(df, alpha);
1855 format!(
1856 r#"{{"lower": {}, "upper": {}}}"#,
1857 coef - t_crit * se,
1858 coef + t_crit * se
1859 )
1860}
1861
1862#[wasm_bindgen]
1863pub fn test_r_accuracy() -> String {
1871 if let Err(e) = check_domain() {
1872 return error_to_json(&e);
1873 }
1874 format!(
1875 r#"{{"two_tail_p": {}, "qt_975": {}}}"#,
1876 core::two_tailed_p_value(1.6717, 21.0),
1877 core::t_critical_quantile(21.0, 0.05)
1878 )
1879}
1880
1881#[wasm_bindgen]
1882pub fn test_housing_regression() -> String {
1891 if let Err(e) = check_domain() {
1892 return error_to_json(&e);
1893 }
1894
1895 match test_housing_regression_native() {
1896 Ok(result) => result,
1897 Err(e) => serde_json::json!({ "status": "ERROR", "error": e.to_string() }).to_string(),
1898 }
1899}
1900
1901#[cfg(any(test, feature = "wasm"))]
1903fn test_housing_regression_native() -> Result<String> {
1904 let y = vec![
1905 245.5, 312.8, 198.4, 425.6, 278.9, 356.2, 189.5, 512.3, 234.7, 298.1, 445.8, 167.9, 367.4,
1906 289.6, 198.2, 478.5, 256.3, 334.7, 178.5, 398.9, 223.4, 312.5, 156.8, 423.7, 267.9,
1907 ];
1908
1909 let square_feet = vec![
1910 1200.0, 1800.0, 950.0, 2400.0, 1450.0, 2000.0, 1100.0, 2800.0, 1350.0, 1650.0, 2200.0,
1911 900.0, 1950.0, 1500.0, 1050.0, 2600.0, 1300.0, 1850.0, 1000.0, 2100.0, 1250.0, 1700.0,
1912 850.0, 2350.0, 1400.0,
1913 ];
1914 let bedrooms = vec![
1915 3.0, 4.0, 2.0, 4.0, 3.0, 4.0, 2.0, 5.0, 3.0, 3.0, 4.0, 2.0, 4.0, 3.0, 2.0, 5.0, 3.0, 4.0,
1916 2.0, 4.0, 3.0, 3.0, 2.0, 4.0, 3.0,
1917 ];
1918 let age = vec![
1919 15.0, 10.0, 25.0, 5.0, 8.0, 12.0, 20.0, 2.0, 18.0, 7.0, 3.0, 30.0, 6.0, 14.0, 22.0, 1.0,
1920 16.0, 9.0, 28.0, 4.0, 19.0, 11.0, 35.0, 3.0, 13.0,
1921 ];
1922
1923 let x_vars = vec![square_feet, bedrooms, age];
1924 let names = vec![
1925 "Intercept".to_string(),
1926 "Square_Feet".to_string(),
1927 "Bedrooms".to_string(),
1928 "Age".to_string(),
1929 ];
1930
1931 let result = core::ols_regression(&y, &x_vars, &names)?;
1932
1933 let expected_coeffs = [52.1271333, 0.1613877, 0.9545492, -1.1811815];
1935 let expected_std_errs = [31.18201809, 0.01875072, 10.44400198, 0.73219949];
1936
1937 let tolerance = 1e-4;
1938 let mut mismatches = vec![];
1939
1940 for i in 0..4 {
1941 if (result.coefficients[i] - expected_coeffs[i]).abs() > tolerance {
1942 mismatches.push(format!(
1943 "coeff[{}] differs: got {}, expected {}",
1944 i, result.coefficients[i], expected_coeffs[i]
1945 ));
1946 }
1947 if (result.std_errors[i] - expected_std_errs[i]).abs() > tolerance {
1948 mismatches.push(format!(
1949 "std_err[{}] differs: got {}, expected {}",
1950 i, result.std_errors[i], expected_std_errs[i]
1951 ));
1952 }
1953 }
1954
1955 if mismatches.is_empty() {
1956 Ok(serde_json::json!({ "status": "PASS" }).to_string())
1957 } else {
1958 Ok(serde_json::json!({ "status": "FAIL", "mismatches": mismatches }).to_string())
1959 }
1960}
1961
1962#[cfg(test)]
1967mod tests {
1968 use super::*;
1969
1970 #[test]
1971 fn verify_housing_regression_integrity() {
1972 let result = test_housing_regression_native();
1973 if let Err(e) = result {
1974 panic!("Regression test failed: {}", e);
1975 }
1976 }
1977
1978 #[test]
1980 fn test_housing_regression_json_output() {
1981 let result = test_housing_regression_native().unwrap();
1982 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
1984 assert!(parsed.get("status").is_some());
1986 assert_eq!(parsed["status"], "PASS");
1988 }
1989
1990 #[test]
1992 fn test_housing_regression_coefficients() {
1993 let y = vec![
1994 245.5, 312.8, 198.4, 425.6, 278.9, 356.2, 189.5, 512.3, 234.7, 298.1, 445.8, 167.9,
1995 367.4, 289.6, 198.2, 478.5, 256.3, 334.7, 178.5, 398.9, 223.4, 312.5, 156.8, 423.7,
1996 267.9,
1997 ];
1998
1999 let square_feet = vec![
2000 1200.0, 1800.0, 950.0, 2400.0, 1450.0, 2000.0, 1100.0, 2800.0, 1350.0, 1650.0,
2001 2200.0, 900.0, 1950.0, 1500.0, 1050.0, 2600.0, 1300.0, 1850.0, 1000.0, 2100.0,
2002 1250.0, 1700.0, 850.0, 2350.0, 1400.0,
2003 ];
2004 let bedrooms = vec![
2005 3.0, 4.0, 2.0, 4.0, 3.0, 4.0, 2.0, 5.0, 3.0, 3.0, 4.0, 2.0, 4.0, 3.0, 2.0, 5.0,
2006 3.0, 4.0, 2.0, 4.0, 3.0, 3.0, 2.0, 4.0, 3.0,
2007 ];
2008 let age = vec![
2009 15.0, 10.0, 25.0, 5.0, 8.0, 12.0, 20.0, 2.0, 18.0, 7.0, 3.0, 30.0, 6.0, 14.0,
2010 22.0, 1.0, 16.0, 9.0, 28.0, 4.0, 19.0, 11.0, 35.0, 3.0, 13.0,
2011 ];
2012
2013 let x_vars = vec![square_feet, bedrooms, age];
2014 let names = vec![
2015 "Intercept".to_string(),
2016 "Square_Feet".to_string(),
2017 "Bedrooms".to_string(),
2018 "Age".to_string(),
2019 ];
2020
2021 let result = core::ols_regression(&y, &x_vars, &names).unwrap();
2022
2023 let expected_coeffs = [52.1271333, 0.1613877, 0.9545492, -1.1811815];
2025 let expected_std_errs = [31.18201809, 0.01875072, 10.44400198, 0.73219949];
2026
2027 let tolerance = 1e-4;
2028 for i in 0..4 {
2029 assert!(
2030 (result.coefficients[i] - expected_coeffs[i]).abs() < tolerance,
2031 "coeff[{}] differs: got {}, expected {}",
2032 i,
2033 result.coefficients[i],
2034 expected_coeffs[i]
2035 );
2036 assert!(
2037 (result.std_errors[i] - expected_std_errs[i]).abs() < tolerance,
2038 "std_err[{}] differs: got {}, expected {}",
2039 i,
2040 result.std_errors[i],
2041 expected_std_errs[i]
2042 );
2043 }
2044 }
2045
2046 #[test]
2048 fn test_housing_regression_r_squared() {
2049 let result = test_housing_regression_native().unwrap();
2050 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
2051
2052 assert_eq!(parsed["status"], "PASS");
2054 }
2055
2056 #[test]
2058 fn test_housing_regression_comprehensive() {
2059 let y = vec![
2060 245.5, 312.8, 198.4, 425.6, 278.9, 356.2, 189.5, 512.3, 234.7, 298.1,
2061 ];
2062 let x1 = vec![1200.0, 1800.0, 950.0, 2400.0, 1450.0, 2000.0, 1100.0, 2800.0, 1350.0, 1650.0];
2063 let x2 = vec![3.0, 4.0, 2.0, 4.0, 3.0, 4.0, 2.0, 5.0, 3.0, 3.0];
2064
2065 let result = core::ols_regression(&y, &[x1, x2], &["Intercept".into(), "X1".into(), "X2".into()])
2066 .unwrap();
2067
2068 assert!(!result.coefficients.is_empty());
2070 assert!(!result.std_errors.is_empty());
2071 assert!(!result.t_stats.is_empty());
2072 assert!(!result.p_values.is_empty());
2073 assert!(result.r_squared >= 0.0 && result.r_squared <= 1.0);
2074 assert!(result.residuals.len() == y.len());
2075 }
2076
2077 #[test]
2079 fn test_housing_regression_insufficient_data() {
2080 let y = vec![245.5, 312.8]; let x1 = vec![1200.0, 1800.0];
2082 let x2 = vec![3.0, 4.0];
2083
2084 let result = core::ols_regression(&y, &[x1, x2], &["Intercept".into(), "X1".into(), "X2".into()]);
2085 assert!(result.is_err());
2086 }
2087
2088 #[test]
2090 fn test_housing_regression_tolerance_check() {
2091 let y = vec![
2092 245.5, 312.8, 198.4, 425.6, 278.9, 356.2, 189.5, 512.3, 234.7, 298.1, 445.8, 167.9,
2093 367.4, 289.6, 198.2, 478.5, 256.3, 334.7, 178.5, 398.9, 223.4, 312.5, 156.8, 423.7,
2094 267.9,
2095 ];
2096
2097 let square_feet = vec![
2098 1200.0, 1800.0, 950.0, 2400.0, 1450.0, 2000.0, 1100.0, 2800.0, 1350.0, 1650.0,
2099 2200.0, 900.0, 1950.0, 1500.0, 1050.0, 2600.0, 1300.0, 1850.0, 1000.0, 2100.0,
2100 1250.0, 1700.0, 850.0, 2350.0, 1400.0,
2101 ];
2102 let bedrooms = vec![
2103 3.0, 4.0, 2.0, 4.0, 3.0, 4.0, 2.0, 5.0, 3.0, 3.0, 4.0, 2.0, 4.0, 3.0, 2.0, 5.0,
2104 3.0, 4.0, 2.0, 4.0, 3.0, 3.0, 2.0, 4.0, 3.0,
2105 ];
2106 let age = vec![
2107 15.0, 10.0, 25.0, 5.0, 8.0, 12.0, 20.0, 2.0, 18.0, 7.0, 3.0, 30.0, 6.0, 14.0,
2108 22.0, 1.0, 16.0, 9.0, 28.0, 4.0, 19.0, 11.0, 35.0, 3.0, 13.0,
2109 ];
2110
2111 let x_vars = vec![square_feet, bedrooms, age];
2112 let names = vec![
2113 "Intercept".to_string(),
2114 "Square_Feet".to_string(),
2115 "Bedrooms".to_string(),
2116 "Age".to_string(),
2117 ];
2118
2119 let result = core::ols_regression(&y, &x_vars, &names).unwrap();
2120
2121 for coef in &result.coefficients {
2123 assert!(coef.is_finite(), "Coefficient should be finite");
2124 }
2125 for se in &result.std_errors {
2127 assert!(se.is_finite(), "Standard error should be finite");
2128 if *se <= 0.0 {
2129 panic!("Standard error should be positive, got {}", se);
2130 }
2131 }
2132 }
2133}