quantized-lab 0.8.0__py3-none-any.whl
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.
- quantized/__init__.py +11 -0
- quantized/__main__.py +14 -0
- quantized/api.py +220 -0
- quantized/app.py +206 -0
- quantized/calc/__init__.py +8 -0
- quantized/calc/_clipfit.py +76 -0
- quantized/calc/_natural_neighbor.py +219 -0
- quantized/calc/aggregate.py +159 -0
- quantized/calc/backgrounds.py +353 -0
- quantized/calc/baseline.py +349 -0
- quantized/calc/batch_fit.py +148 -0
- quantized/calc/constants.py +27 -0
- quantized/calc/corrections.py +192 -0
- quantized/calc/crystallography.py +400 -0
- quantized/calc/diffusion.py +120 -0
- quantized/calc/electrical.py +248 -0
- quantized/calc/electrochemistry.py +176 -0
- quantized/calc/element_data.json +1 -0
- quantized/calc/element_data.py +59 -0
- quantized/calc/errors.py +246 -0
- quantized/calc/figure.py +417 -0
- quantized/calc/figure_break.py +152 -0
- quantized/calc/figure_categorical.py +156 -0
- quantized/calc/figure_corner.py +229 -0
- quantized/calc/figure_facets.py +127 -0
- quantized/calc/figure_field.py +137 -0
- quantized/calc/figure_hitmap.py +116 -0
- quantized/calc/figure_labels.py +62 -0
- quantized/calc/figure_map.py +287 -0
- quantized/calc/figure_overrides.py +159 -0
- quantized/calc/figure_page.py +266 -0
- quantized/calc/figure_scale.py +125 -0
- quantized/calc/figure_statplots.py +167 -0
- quantized/calc/figure_styles.py +131 -0
- quantized/calc/figure_ternary.py +239 -0
- quantized/calc/figure_ticks.py +217 -0
- quantized/calc/fit_autoguess.py +156 -0
- quantized/calc/fit_bootstrap.py +163 -0
- quantized/calc/fit_bumps.py +258 -0
- quantized/calc/fit_constraints.py +114 -0
- quantized/calc/fit_equation.py +264 -0
- quantized/calc/fit_findxy.py +80 -0
- quantized/calc/fit_models.py +195 -0
- quantized/calc/fit_models_special.py +189 -0
- quantized/calc/fit_odr.py +99 -0
- quantized/calc/fit_scan.py +243 -0
- quantized/calc/fit_stats.py +215 -0
- quantized/calc/fitting.py +199 -0
- quantized/calc/formula.py +90 -0
- quantized/calc/global_curve_fit.py +305 -0
- quantized/calc/global_fit.py +181 -0
- quantized/calc/interp2d.py +260 -0
- quantized/calc/linecut.py +269 -0
- quantized/calc/magnetic.py +414 -0
- quantized/calc/magnetometry.py +464 -0
- quantized/calc/map.py +228 -0
- quantized/calc/mcmc.py +177 -0
- quantized/calc/optics.py +228 -0
- quantized/calc/pawley.py +251 -0
- quantized/calc/peak_batch.py +128 -0
- quantized/calc/peak_fit.py +259 -0
- quantized/calc/peak_integrate.py +104 -0
- quantized/calc/peak_multifit.py +260 -0
- quantized/calc/peak_track.py +134 -0
- quantized/calc/peaks.py +298 -0
- quantized/calc/peakshapes.py +85 -0
- quantized/calc/plotting.py +147 -0
- quantized/calc/processing.py +232 -0
- quantized/calc/qspace.py +48 -0
- quantized/calc/reductions.py +155 -0
- quantized/calc/reductions_fft.py +383 -0
- quantized/calc/refl_sld_presets.json +1 -0
- quantized/calc/reflectivity.py +80 -0
- quantized/calc/registry.py +303 -0
- quantized/calc/relaxation.py +119 -0
- quantized/calc/report.py +253 -0
- quantized/calc/report_emit.py +227 -0
- quantized/calc/resample.py +142 -0
- quantized/calc/rsm.py +91 -0
- quantized/calc/rsm_analyze.py +245 -0
- quantized/calc/semiconductor.py +488 -0
- quantized/calc/sld.py +131 -0
- quantized/calc/sld_formula.py +138 -0
- quantized/calc/spectral.py +357 -0
- quantized/calc/statplots.py +214 -0
- quantized/calc/stats.py +399 -0
- quantized/calc/stats_anova2.py +202 -0
- quantized/calc/stats_anova_ext.py +338 -0
- quantized/calc/stats_dist.py +196 -0
- quantized/calc/stats_glm.py +245 -0
- quantized/calc/stats_multivar.py +289 -0
- quantized/calc/stats_roc.py +157 -0
- quantized/calc/stats_survival.py +261 -0
- quantized/calc/stats_tests.py +380 -0
- quantized/calc/substrates.py +181 -0
- quantized/calc/superconductor.py +359 -0
- quantized/calc/surface_fit.py +290 -0
- quantized/calc/surface_models.py +156 -0
- quantized/calc/thermal.py +119 -0
- quantized/calc/thin_film.py +425 -0
- quantized/calc/unit_convert.py +259 -0
- quantized/calc/units.py +80 -0
- quantized/calc/vacuum.py +290 -0
- quantized/calc/xray.py +169 -0
- quantized/cli.py +214 -0
- quantized/datastruct.py +153 -0
- quantized/io/__init__.py +11 -0
- quantized/io/_hdf5_layout.py +308 -0
- quantized/io/_jcamp_asdf.py +135 -0
- quantized/io/_xrdml_scan.py +291 -0
- quantized/io/base.py +82 -0
- quantized/io/bruker_brml.py +177 -0
- quantized/io/bruker_raw.py +158 -0
- quantized/io/cif.py +266 -0
- quantized/io/consolidated.py +122 -0
- quantized/io/delimited.py +222 -0
- quantized/io/excel.py +135 -0
- quantized/io/hdf5.py +192 -0
- quantized/io/import_filters.py +178 -0
- quantized/io/import_preview.py +262 -0
- quantized/io/jcamp.py +179 -0
- quantized/io/lakeshore.py +163 -0
- quantized/io/ncnr.py +278 -0
- quantized/io/netcdf.py +195 -0
- quantized/io/opus.py +231 -0
- quantized/io/origin.py +346 -0
- quantized/io/origin_com.py +194 -0
- quantized/io/origin_project/__init__.py +221 -0
- quantized/io/origin_project/annotation_marks.py +288 -0
- quantized/io/origin_project/container.py +262 -0
- quantized/io/origin_project/curve_style_color.py +359 -0
- quantized/io/origin_project/figure_geometry.py +108 -0
- quantized/io/origin_project/figure_layers.py +333 -0
- quantized/io/origin_project/figure_text.py +258 -0
- quantized/io/origin_project/figures.py +210 -0
- quantized/io/origin_project/figures_opju.py +440 -0
- quantized/io/origin_project/notes.py +302 -0
- quantized/io/origin_project/opj.py +459 -0
- quantized/io/origin_project/opj_curves.py +297 -0
- quantized/io/origin_project/opj_shapes.py +148 -0
- quantized/io/origin_project/opju.py +146 -0
- quantized/io/origin_project/opju_axis_real_form.py +418 -0
- quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
- quantized/io/origin_project/opju_codec.py +370 -0
- quantized/io/origin_project/opju_curves.py +497 -0
- quantized/io/origin_project/opju_curves_allcols.py +258 -0
- quantized/io/origin_project/opju_figure_curves.py +302 -0
- quantized/io/origin_project/opju_figure_text.py +245 -0
- quantized/io/origin_project/opju_reports.py +129 -0
- quantized/io/origin_project/origin_richtext.py +145 -0
- quantized/io/origin_project/preview.py +132 -0
- quantized/io/origin_project/templates.py +314 -0
- quantized/io/origin_project/tree.py +379 -0
- quantized/io/origin_project/tree_opju.py +228 -0
- quantized/io/origin_project/windows.py +238 -0
- quantized/io/origin_project/windows_opju.py +393 -0
- quantized/io/origin_project/writer.py +156 -0
- quantized/io/origin_project/writer_blocks.py +282 -0
- quantized/io/qd.py +380 -0
- quantized/io/refl1d.py +132 -0
- quantized/io/registry.py +210 -0
- quantized/io/report_export.py +347 -0
- quantized/io/rigaku.py +100 -0
- quantized/io/sims.py +398 -0
- quantized/io/spc.py +311 -0
- quantized/io/xrd_csv.py +308 -0
- quantized/io/xrdml.py +394 -0
- quantized/jobs.py +173 -0
- quantized/plugins/__init__.py +50 -0
- quantized/plugins/contract.py +111 -0
- quantized/plugins/loader.py +394 -0
- quantized/plugins/steps.py +90 -0
- quantized/routes/__init__.py +7 -0
- quantized/routes/_bookcache.py +62 -0
- quantized/routes/_export_common.py +27 -0
- quantized/routes/_payload.py +58 -0
- quantized/routes/_uploadcache.py +59 -0
- quantized/routes/aggregate.py +46 -0
- quantized/routes/baseline.py +210 -0
- quantized/routes/books.py +117 -0
- quantized/routes/calc.py +56 -0
- quantized/routes/corrections.py +78 -0
- quantized/routes/crystallography.py +80 -0
- quantized/routes/diffusion.py +58 -0
- quantized/routes/electrical.py +101 -0
- quantized/routes/electrochemistry.py +83 -0
- quantized/routes/export.py +280 -0
- quantized/routes/export_facets.py +83 -0
- quantized/routes/export_figures.py +471 -0
- quantized/routes/export_page.py +125 -0
- quantized/routes/fitting.py +379 -0
- quantized/routes/fitting_bumps.py +97 -0
- quantized/routes/import_template.py +97 -0
- quantized/routes/import_wizard.py +150 -0
- quantized/routes/jobs_api.py +59 -0
- quantized/routes/magnetic.py +135 -0
- quantized/routes/magnetometry.py +133 -0
- quantized/routes/optics.py +98 -0
- quantized/routes/parsers.py +281 -0
- quantized/routes/peaks.py +184 -0
- quantized/routes/plot.py +103 -0
- quantized/routes/reductions.py +121 -0
- quantized/routes/reference.py +58 -0
- quantized/routes/reflectivity.py +91 -0
- quantized/routes/report_export.py +119 -0
- quantized/routes/rsm.py +136 -0
- quantized/routes/samples.py +32 -0
- quantized/routes/semiconductor.py +207 -0
- quantized/routes/sld.py +42 -0
- quantized/routes/spectral.py +54 -0
- quantized/routes/statplots.py +99 -0
- quantized/routes/stats.py +418 -0
- quantized/routes/stats_design.py +321 -0
- quantized/routes/substrates.py +46 -0
- quantized/routes/superconductor.py +139 -0
- quantized/routes/thermal.py +57 -0
- quantized/routes/thin_film.py +153 -0
- quantized/routes/vacuum.py +113 -0
- quantized/routes/xray.py +32 -0
- quantized/samples/demo_vsm.csv +42 -0
- quantized/server_launch.py +251 -0
- quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- quantized/web/assets/index-BHmmCL-x.js +27 -0
- quantized/web/assets/index-BiZzN7J6.css +1 -0
- quantized/web/index.html +13 -0
- quantized/web/loading.html +69 -0
- quantized_lab-0.8.0.dist-info/METADATA +122 -0
- quantized_lab-0.8.0.dist-info/RECORD +233 -0
- quantized_lab-0.8.0.dist-info/WHEEL +4 -0
- quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
- quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
- quantized_lab-0.8.0.dist-info/licenses/NOTICE +11 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Thin FFT spectral route. Wraps ``calc.spectral.fft_spectral`` for the ROI
|
|
2
|
+
gadget family's FFT mode (gap #34): a single-record magnitude/PSD/phase
|
|
3
|
+
spectrum of one region's rows. The "complex" output type never crosses the
|
|
4
|
+
wire (numpy complex isn't JSON-serializable — see CLAUDE.md's jsonencode
|
|
5
|
+
notes); only magnitude/psd/phase are exposed here. Validate -> call ->
|
|
6
|
+
serialize; no business logic here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from fastapi import APIRouter, HTTPException
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from quantized.calc.spectral import fft_spectral
|
|
18
|
+
from quantized.routes._payload import to_jsonable
|
|
19
|
+
|
|
20
|
+
router = APIRouter(prefix="/api/spectral", tags=["spectral"])
|
|
21
|
+
|
|
22
|
+
# "complex" is deliberately excluded (see module docstring).
|
|
23
|
+
_OUTPUT_TYPES = ("psd", "magnitude", "phase")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FftRequest(BaseModel):
|
|
27
|
+
x: list[float]
|
|
28
|
+
y: list[float]
|
|
29
|
+
window: str = "hanning"
|
|
30
|
+
output_type: str = "magnitude"
|
|
31
|
+
sided: str = "one"
|
|
32
|
+
detrend: str = "mean"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post("/fft")
|
|
36
|
+
def fft(req: FftRequest) -> dict[str, Any]:
|
|
37
|
+
"""Single-record FFT spectrum (magnitude/psd/phase) of (x, y)."""
|
|
38
|
+
if req.output_type not in _OUTPUT_TYPES:
|
|
39
|
+
raise HTTPException(status_code=422, detail=f"unsupported output_type: {req.output_type}")
|
|
40
|
+
try:
|
|
41
|
+
result = fft_spectral(
|
|
42
|
+
np.asarray(req.x, dtype=float),
|
|
43
|
+
np.asarray(req.y, dtype=float),
|
|
44
|
+
window=req.window,
|
|
45
|
+
output_type=req.output_type,
|
|
46
|
+
sided=req.sided,
|
|
47
|
+
detrend=req.detrend,
|
|
48
|
+
)
|
|
49
|
+
except ValueError as exc:
|
|
50
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
51
|
+
# Drop the window-function array: same length as the input, internal detail
|
|
52
|
+
# the caller never plots (keeps the response small).
|
|
53
|
+
out = {k: v for k, v in result.items() if k != "window"}
|
|
54
|
+
return to_jsonable(out) # type: ignore[no-any-return]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Thin routes for statistical-plot primitives (ORIGIN_GAP_PLAN #16).
|
|
2
|
+
|
|
3
|
+
All math lives in ``quantized.calc.statplots``; these adapters only validate,
|
|
4
|
+
call, and serialize. The same payloads feed the interactive stage and the
|
|
5
|
+
matplotlib export path.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from fastapi import APIRouter, HTTPException
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
from quantized.calc.statplots import (
|
|
17
|
+
grouped_box_stats,
|
|
18
|
+
histogram,
|
|
19
|
+
qq_plot,
|
|
20
|
+
violin_kde,
|
|
21
|
+
)
|
|
22
|
+
from quantized.routes._payload import to_jsonable
|
|
23
|
+
|
|
24
|
+
router = APIRouter(prefix="/api/statplots", tags=["statplots"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _wrap(result: dict[str, Any]) -> dict[str, Any]:
|
|
28
|
+
return to_jsonable(result) # type: ignore[no-any-return]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BoxRequest(BaseModel):
|
|
32
|
+
groups: list[list[float]]
|
|
33
|
+
labels: list[str] | None = None
|
|
34
|
+
whis: float | str = 1.5
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.post("/box")
|
|
38
|
+
def box_route(req: BoxRequest) -> dict[str, Any]:
|
|
39
|
+
"""Box/whisker stats for one or more groups (matplotlib-compatible)."""
|
|
40
|
+
try:
|
|
41
|
+
return _wrap(grouped_box_stats(req.groups, labels=req.labels, whis=req.whis))
|
|
42
|
+
except (ValueError, IndexError) as exc:
|
|
43
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ViolinRequest(BaseModel):
|
|
47
|
+
data: list[float]
|
|
48
|
+
bw_method: str | float = "scott"
|
|
49
|
+
n_points: int = 128
|
|
50
|
+
cut: float = 2.0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.post("/violin")
|
|
54
|
+
def violin_route(req: ViolinRequest) -> dict[str, Any]:
|
|
55
|
+
"""Gaussian-KDE density for a violin plot."""
|
|
56
|
+
try:
|
|
57
|
+
return _wrap(
|
|
58
|
+
violin_kde(
|
|
59
|
+
np.asarray(req.data, dtype=float),
|
|
60
|
+
bw_method=req.bw_method, n_points=req.n_points, cut=req.cut,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
except (ValueError, IndexError) as exc:
|
|
64
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class QQRequest(BaseModel):
|
|
68
|
+
data: list[float]
|
|
69
|
+
dist: str = "norm"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.post("/qq")
|
|
73
|
+
def qq_route(req: QQRequest) -> dict[str, Any]:
|
|
74
|
+
"""Quantile-quantile / probability-plot coordinates against a distribution."""
|
|
75
|
+
try:
|
|
76
|
+
return _wrap(qq_plot(np.asarray(req.data, dtype=float), dist=req.dist))
|
|
77
|
+
except (ValueError, IndexError) as exc:
|
|
78
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class HistogramRequest(BaseModel):
|
|
82
|
+
data: list[float]
|
|
83
|
+
bins: str | int = "fd"
|
|
84
|
+
density: bool = False
|
|
85
|
+
fit: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post("/histogram")
|
|
89
|
+
def histogram_route(req: HistogramRequest) -> dict[str, Any]:
|
|
90
|
+
"""Histogram with a data-driven bin rule and an optional fit overlay."""
|
|
91
|
+
try:
|
|
92
|
+
return _wrap(
|
|
93
|
+
histogram(
|
|
94
|
+
np.asarray(req.data, dtype=float),
|
|
95
|
+
bins=req.bins, density=req.density, fit=req.fit,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
except (ValueError, IndexError) as exc:
|
|
99
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Thin statistics routes. Wraps ``calc.stats`` (no toolbox, golden vs MATLAB).
|
|
2
|
+
|
|
3
|
+
Each handler validates, converts to ndarray, calls the pure function, and
|
|
4
|
+
serializes the (array-bearing) result dict via ``to_jsonable``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from fastapi import APIRouter, HTTPException
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from quantized.calc.stats import (
|
|
16
|
+
anova1,
|
|
17
|
+
descriptive_stats,
|
|
18
|
+
lin_regress,
|
|
19
|
+
pca_analysis,
|
|
20
|
+
t_test,
|
|
21
|
+
)
|
|
22
|
+
from quantized.calc.stats_dist import (
|
|
23
|
+
fit_distribution,
|
|
24
|
+
fit_distributions,
|
|
25
|
+
required_n,
|
|
26
|
+
t_test_power,
|
|
27
|
+
)
|
|
28
|
+
from quantized.calc.stats_multivar import (
|
|
29
|
+
correlation_matrix,
|
|
30
|
+
multiple_regression,
|
|
31
|
+
partial_correlation,
|
|
32
|
+
stepwise_regression,
|
|
33
|
+
)
|
|
34
|
+
from quantized.calc.stats_tests import (
|
|
35
|
+
anderson_darling,
|
|
36
|
+
friedman,
|
|
37
|
+
kruskal_wallis,
|
|
38
|
+
ks_normal,
|
|
39
|
+
ks_two_sample,
|
|
40
|
+
levene,
|
|
41
|
+
mann_whitney,
|
|
42
|
+
shapiro_wilk,
|
|
43
|
+
sign_test,
|
|
44
|
+
wilcoxon_signed_rank,
|
|
45
|
+
)
|
|
46
|
+
from quantized.routes._payload import to_jsonable
|
|
47
|
+
|
|
48
|
+
router = APIRouter(prefix="/api/stats", tags=["stats"])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DescriptiveRequest(BaseModel):
|
|
52
|
+
x: list[float]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RegressionRequest(BaseModel):
|
|
56
|
+
x: list[float]
|
|
57
|
+
y: list[float]
|
|
58
|
+
order: int = 1
|
|
59
|
+
alpha: float = 0.05
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TTestRequest(BaseModel):
|
|
63
|
+
x: list[float]
|
|
64
|
+
y: list[float] | None = None
|
|
65
|
+
mu: float = 0.0
|
|
66
|
+
paired: bool = False
|
|
67
|
+
alpha: float = 0.05
|
|
68
|
+
tail: str = "both"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AnovaRequest(BaseModel):
|
|
72
|
+
groups: list[list[float]]
|
|
73
|
+
alpha: float = 0.05
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PCARequest(BaseModel):
|
|
77
|
+
data: list[list[float]]
|
|
78
|
+
center: bool = True
|
|
79
|
+
scale: bool = False
|
|
80
|
+
num_components: int = 0
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TwoSampleRequest(BaseModel):
|
|
84
|
+
x: list[float]
|
|
85
|
+
y: list[float]
|
|
86
|
+
alternative: str = "two-sided"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class PairedOrOneSampleRequest(BaseModel):
|
|
90
|
+
x: list[float]
|
|
91
|
+
y: list[float] | None = None
|
|
92
|
+
mu: float = 0.0
|
|
93
|
+
alternative: str = "two-sided"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class GroupsRequest(BaseModel):
|
|
97
|
+
groups: list[list[float]]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class LeveneRequest(BaseModel):
|
|
101
|
+
groups: list[list[float]]
|
|
102
|
+
center: str = "median"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class OneSampleRequest(BaseModel):
|
|
106
|
+
x: list[float]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class KSNormalRequest(BaseModel):
|
|
110
|
+
x: list[float]
|
|
111
|
+
loc: float | None = None
|
|
112
|
+
scale: float | None = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MultiRegressionRequest(BaseModel):
|
|
116
|
+
predictors: list[list[float]] # k same-length columns
|
|
117
|
+
y: list[float]
|
|
118
|
+
alpha: float = 0.05
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class CorrelationRequest(BaseModel):
|
|
122
|
+
columns: list[list[float]]
|
|
123
|
+
method: str = "pearson"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class PartialCorrelationRequest(BaseModel):
|
|
127
|
+
columns: list[list[float]]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _wrap(result: dict[str, Any]) -> dict[str, Any]:
|
|
131
|
+
return to_jsonable(result) # type: ignore[no-any-return]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@router.post("/descriptive")
|
|
135
|
+
def descriptive(req: DescriptiveRequest) -> dict[str, Any]:
|
|
136
|
+
"""Descriptive statistics of a 1-D array (NaNs dropped)."""
|
|
137
|
+
try:
|
|
138
|
+
return _wrap(descriptive_stats(np.asarray(req.x, dtype=float)))
|
|
139
|
+
except (ValueError, IndexError) as exc:
|
|
140
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@router.post("/regression")
|
|
144
|
+
def regression(req: RegressionRequest) -> dict[str, Any]:
|
|
145
|
+
"""Polynomial least-squares regression with inference."""
|
|
146
|
+
try:
|
|
147
|
+
return _wrap(
|
|
148
|
+
lin_regress(
|
|
149
|
+
np.asarray(req.x, dtype=float),
|
|
150
|
+
np.asarray(req.y, dtype=float),
|
|
151
|
+
order=req.order,
|
|
152
|
+
alpha=req.alpha,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
except (ValueError, IndexError) as exc:
|
|
156
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@router.post("/ttest")
|
|
160
|
+
def ttest(req: TTestRequest) -> dict[str, Any]:
|
|
161
|
+
"""Student's t-test (one-sample / paired / Welch two-sample)."""
|
|
162
|
+
try:
|
|
163
|
+
y = np.asarray(req.y, dtype=float) if req.y is not None else None
|
|
164
|
+
return _wrap(
|
|
165
|
+
t_test(
|
|
166
|
+
np.asarray(req.x, dtype=float),
|
|
167
|
+
y,
|
|
168
|
+
mu=req.mu,
|
|
169
|
+
paired=req.paired,
|
|
170
|
+
alpha=req.alpha,
|
|
171
|
+
tail=req.tail,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
except (ValueError, IndexError) as exc:
|
|
175
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@router.post("/anova")
|
|
179
|
+
def anova(req: AnovaRequest) -> dict[str, Any]:
|
|
180
|
+
"""One-way ANOVA on a list of group vectors."""
|
|
181
|
+
try:
|
|
182
|
+
groups = [np.asarray(g, dtype=float) for g in req.groups]
|
|
183
|
+
return _wrap(anova1(groups, alpha=req.alpha))
|
|
184
|
+
except (ValueError, IndexError) as exc:
|
|
185
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@router.post("/pca")
|
|
189
|
+
def pca(req: PCARequest) -> dict[str, Any]:
|
|
190
|
+
"""Principal component analysis via SVD."""
|
|
191
|
+
try:
|
|
192
|
+
return _wrap(
|
|
193
|
+
pca_analysis(
|
|
194
|
+
np.asarray(req.data, dtype=float),
|
|
195
|
+
center=req.center,
|
|
196
|
+
scale=req.scale,
|
|
197
|
+
num_components=req.num_components,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
except (ValueError, IndexError) as exc:
|
|
201
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@router.post("/mann-whitney")
|
|
205
|
+
def mann_whitney_route(req: TwoSampleRequest) -> dict[str, Any]:
|
|
206
|
+
"""Mann-Whitney U test (independent two-sample rank test)."""
|
|
207
|
+
try:
|
|
208
|
+
return _wrap(
|
|
209
|
+
mann_whitney(
|
|
210
|
+
np.asarray(req.x, dtype=float),
|
|
211
|
+
np.asarray(req.y, dtype=float),
|
|
212
|
+
alternative=req.alternative,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
except (ValueError, IndexError) as exc:
|
|
216
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@router.post("/wilcoxon")
|
|
220
|
+
def wilcoxon_route(req: PairedOrOneSampleRequest) -> dict[str, Any]:
|
|
221
|
+
"""Wilcoxon signed-rank test (paired, or one-sample vs mu)."""
|
|
222
|
+
try:
|
|
223
|
+
y = np.asarray(req.y, dtype=float) if req.y is not None else None
|
|
224
|
+
return _wrap(
|
|
225
|
+
wilcoxon_signed_rank(
|
|
226
|
+
np.asarray(req.x, dtype=float), y, mu=req.mu, alternative=req.alternative
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
except (ValueError, IndexError) as exc:
|
|
230
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@router.post("/kruskal")
|
|
234
|
+
def kruskal_route(req: GroupsRequest) -> dict[str, Any]:
|
|
235
|
+
"""Kruskal-Wallis H test on a list of group vectors."""
|
|
236
|
+
try:
|
|
237
|
+
return _wrap(kruskal_wallis([np.asarray(g, dtype=float) for g in req.groups]))
|
|
238
|
+
except (ValueError, IndexError) as exc:
|
|
239
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@router.post("/friedman")
|
|
243
|
+
def friedman_route(req: GroupsRequest) -> dict[str, Any]:
|
|
244
|
+
"""Friedman test (k treatments x n blocks, equal lengths)."""
|
|
245
|
+
try:
|
|
246
|
+
return _wrap(friedman([np.asarray(g, dtype=float) for g in req.groups]))
|
|
247
|
+
except (ValueError, IndexError) as exc:
|
|
248
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@router.post("/sign-test")
|
|
252
|
+
def sign_test_route(req: PairedOrOneSampleRequest) -> dict[str, Any]:
|
|
253
|
+
"""Sign test (paired, or one-sample vs mu) via exact binomial."""
|
|
254
|
+
try:
|
|
255
|
+
y = np.asarray(req.y, dtype=float) if req.y is not None else None
|
|
256
|
+
return _wrap(
|
|
257
|
+
sign_test(np.asarray(req.x, dtype=float), y, mu=req.mu, alternative=req.alternative)
|
|
258
|
+
)
|
|
259
|
+
except (ValueError, IndexError) as exc:
|
|
260
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@router.post("/shapiro")
|
|
264
|
+
def shapiro_route(req: OneSampleRequest) -> dict[str, Any]:
|
|
265
|
+
"""Shapiro-Wilk normality test."""
|
|
266
|
+
try:
|
|
267
|
+
return _wrap(shapiro_wilk(np.asarray(req.x, dtype=float)))
|
|
268
|
+
except (ValueError, IndexError) as exc:
|
|
269
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@router.post("/anderson")
|
|
273
|
+
def anderson_route(req: OneSampleRequest) -> dict[str, Any]:
|
|
274
|
+
"""Anderson-Darling normality test (critical-value table, no p)."""
|
|
275
|
+
try:
|
|
276
|
+
return _wrap(anderson_darling(np.asarray(req.x, dtype=float)))
|
|
277
|
+
except (ValueError, IndexError) as exc:
|
|
278
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@router.post("/levene")
|
|
282
|
+
def levene_route(req: LeveneRequest) -> dict[str, Any]:
|
|
283
|
+
"""Levene / Brown-Forsythe equal-variance test."""
|
|
284
|
+
try:
|
|
285
|
+
return _wrap(
|
|
286
|
+
levene([np.asarray(g, dtype=float) for g in req.groups], center=req.center)
|
|
287
|
+
)
|
|
288
|
+
except (ValueError, IndexError) as exc:
|
|
289
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@router.post("/ks-normal")
|
|
293
|
+
def ks_normal_route(req: KSNormalRequest) -> dict[str, Any]:
|
|
294
|
+
"""One-sample KS test vs a normal distribution."""
|
|
295
|
+
try:
|
|
296
|
+
return _wrap(ks_normal(np.asarray(req.x, dtype=float), loc=req.loc, scale=req.scale))
|
|
297
|
+
except (ValueError, IndexError) as exc:
|
|
298
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@router.post("/regression-multi")
|
|
302
|
+
def regression_multi(req: MultiRegressionRequest) -> dict[str, Any]:
|
|
303
|
+
"""Multiple linear regression (intercept + k predictors) with inference."""
|
|
304
|
+
try:
|
|
305
|
+
return _wrap(
|
|
306
|
+
multiple_regression(
|
|
307
|
+
[np.asarray(c, dtype=float) for c in req.predictors],
|
|
308
|
+
np.asarray(req.y, dtype=float),
|
|
309
|
+
alpha=req.alpha,
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
except (ValueError, IndexError) as exc:
|
|
313
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@router.post("/correlation")
|
|
317
|
+
def correlation(req: CorrelationRequest) -> dict[str, Any]:
|
|
318
|
+
"""Pairwise Pearson/Spearman correlation matrix with p-values."""
|
|
319
|
+
try:
|
|
320
|
+
return _wrap(
|
|
321
|
+
correlation_matrix(
|
|
322
|
+
[np.asarray(c, dtype=float) for c in req.columns], method=req.method
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
except (ValueError, IndexError) as exc:
|
|
326
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@router.post("/partial-correlation")
|
|
330
|
+
def partial_correlation_route(req: PartialCorrelationRequest) -> dict[str, Any]:
|
|
331
|
+
"""Partial correlation of every pair controlling for all other columns."""
|
|
332
|
+
try:
|
|
333
|
+
return _wrap(partial_correlation([np.asarray(c, dtype=float) for c in req.columns]))
|
|
334
|
+
except (ValueError, IndexError) as exc:
|
|
335
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@router.post("/ks-two-sample")
|
|
339
|
+
def ks_two_sample_route(req: TwoSampleRequest) -> dict[str, Any]:
|
|
340
|
+
"""Two-sample KS test."""
|
|
341
|
+
try:
|
|
342
|
+
return _wrap(
|
|
343
|
+
ks_two_sample(
|
|
344
|
+
np.asarray(req.x, dtype=float),
|
|
345
|
+
np.asarray(req.y, dtype=float),
|
|
346
|
+
alternative=req.alternative,
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
except (ValueError, IndexError) as exc:
|
|
350
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class FitDistRequest(BaseModel):
|
|
354
|
+
x: list[float]
|
|
355
|
+
dist: str | None = None # None = fit all families, rank by AIC
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@router.post("/fit-distribution")
|
|
359
|
+
def fit_distribution_route(req: FitDistRequest) -> dict[str, Any]:
|
|
360
|
+
"""MLE distribution fit(s) with KS GOF; dist=None ranks all families."""
|
|
361
|
+
try:
|
|
362
|
+
x = np.asarray(req.x, dtype=float)
|
|
363
|
+
if req.dist is None:
|
|
364
|
+
return _wrap(fit_distributions(x))
|
|
365
|
+
return _wrap(fit_distribution(x, req.dist))
|
|
366
|
+
except (ValueError, IndexError) as exc:
|
|
367
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class PowerRequest(BaseModel):
|
|
371
|
+
effect_size: float
|
|
372
|
+
n: int | None = None # given -> power; omitted -> solve for n
|
|
373
|
+
power: float = 0.8
|
|
374
|
+
kind: str = "two-sample"
|
|
375
|
+
alpha: float = 0.05
|
|
376
|
+
tails: int = 2
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@router.post("/power")
|
|
380
|
+
def power_route(req: PowerRequest) -> dict[str, Any]:
|
|
381
|
+
"""t-test power at n, or the required n for a target power (exact nct)."""
|
|
382
|
+
try:
|
|
383
|
+
if req.n is not None:
|
|
384
|
+
return _wrap(
|
|
385
|
+
t_test_power(
|
|
386
|
+
req.effect_size, req.n, kind=req.kind, alpha=req.alpha, tails=req.tails
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
return _wrap(
|
|
390
|
+
required_n(
|
|
391
|
+
req.effect_size, req.power, kind=req.kind, alpha=req.alpha, tails=req.tails
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
except (ValueError, IndexError) as exc:
|
|
395
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class StepwiseRequest(BaseModel):
|
|
399
|
+
predictors: list[list[float]]
|
|
400
|
+
y: list[float]
|
|
401
|
+
criterion: str = "aic"
|
|
402
|
+
direction: str = "forward"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@router.post("/stepwise")
|
|
406
|
+
def stepwise_route(req: StepwiseRequest) -> dict[str, Any]:
|
|
407
|
+
"""Stepwise predictor selection (AIC/BIC) over multiple regression."""
|
|
408
|
+
try:
|
|
409
|
+
return _wrap(
|
|
410
|
+
stepwise_regression(
|
|
411
|
+
[np.asarray(c, dtype=float) for c in req.predictors],
|
|
412
|
+
np.asarray(req.y, dtype=float),
|
|
413
|
+
criterion=req.criterion,
|
|
414
|
+
direction=req.direction,
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
except (ValueError, IndexError) as exc:
|
|
418
|
+
raise HTTPException(status_code=422, detail=str(exc)) from exc
|