296 lines
8.2 KiB
Python
296 lines
8.2 KiB
Python
import marimo
|
||
|
||
__generated_with = "0.25.0"
|
||
app = marimo.App(width="medium")
|
||
|
||
|
||
@app.cell
|
||
def _():
|
||
import marimo as mo
|
||
|
||
return (mo,)
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
# Test del modulo `statistica.py`
|
||
|
||
Questo notebook importa `statistica.py` (notebook marimo) e testa **ogni
|
||
funzione** definita al suo interno, verificando che i risultati siano
|
||
corretti e segnalando eventuali errori.
|
||
|
||
Poiché in `statistica.py` le funzioni sono definite dentro alle celle
|
||
`@app.cell` (non con `@app.function`), per poterle riutilizzare qui
|
||
eseguiamo l'intero notebook con `app.run()`, che restituisce un
|
||
dizionario con tutte le definizioni (`defs`). Solo la classe `Data`
|
||
(definita con `@app.class_definition`) sarebbe importabile direttamente.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Import e caricamento del modulo da testare
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _():
|
||
import matplotlib
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
import sympy as sp
|
||
|
||
import statistica
|
||
|
||
# Esegue tutte le celle del notebook importato e recupera le definizioni
|
||
_outputs, defs = statistica.app.run()
|
||
|
||
Data = defs["Data"]
|
||
compat = defs["compat"]
|
||
stat_chauv = defs["stat_chauv"]
|
||
calc_var = defs["calc_var"]
|
||
reg_lin = defs["reg_lin"]
|
||
plot_gauss = defs["plot_gauss"]
|
||
trova_outlier = defs["trova_outlier"]
|
||
rimuovi_outlier = defs["rimuovi_outlier"]
|
||
return Data, compat, np, pd, plot_gauss, sp
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Helper di test
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(mo):
|
||
risultati_test = []
|
||
|
||
def esegui_test(nome, fn):
|
||
"""Esegue fn(), registra esito e ritorna un mo.md con il risultato."""
|
||
try:
|
||
dettaglio = fn()
|
||
risultati_test.append((nome, True, ""))
|
||
corpo = f"✅ **{nome}**: OK"
|
||
if dettaglio:
|
||
corpo += f"\n\n{dettaglio}"
|
||
return mo.md(corpo)
|
||
except Exception as e:
|
||
import traceback
|
||
tb = traceback.format_exc()
|
||
risultati_test.append((nome, False, str(e)))
|
||
return mo.md(f"❌ **{nome}**: ERRORE — `{e}`\n\n```\n{tb}\n```")
|
||
|
||
return esegui_test, risultati_test
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Test 1 — `compat(x1, x2, u1, u2)`
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(compat, esegui_test, np):
|
||
def _test_compat():
|
||
k, diff = compat(10.0, 10.5, 0.2, 0.3)
|
||
assert np.isclose(diff, -0.5), f"diff errato: {diff}"
|
||
atteso_k = 0.5 / np.sqrt(0.2**2 + 0.3**2)
|
||
assert np.isclose(k, atteso_k), f"k errato: {k} atteso {atteso_k}"
|
||
return f"k = {k:.4f}, diff = {diff:.4f} (attesi: k={atteso_k:.4f}, diff=-0.5)"
|
||
|
||
esegui_test("compat", _test_compat)
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Test 2 — `Data.stat_chauv(prefissi, err_strumentale)`
|
||
|
||
Costruiamo un campione con 4 punti sperimentali, ciascuno con 8 misure
|
||
ripetute della grandezza `T`. Nel primo punto inseriamo un valore
|
||
anomalo (~10σ) che il criterio di Chauvenet dovrebbe individuare e
|
||
rimuovere.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(Data, esegui_test, np, pd):
|
||
np.random.seed(0)
|
||
_righe = [np.random.normal(10, 0.05, size=8) for _ in range(4)]
|
||
_righe[0][0] = 10.5 # outlier moderato ma rilevabile
|
||
|
||
campione_chauv = pd.DataFrame(_righe, columns=[f"T{i+1}" for i in range(8)])
|
||
dato_chauv = Data(campione_chauv)
|
||
|
||
def _test_stat_chauv():
|
||
dato_chauv.stat_chauv("T", 0.02)
|
||
colonne_attese = {"T", "uT_strum", "uT_stat", "uT", "nT", "outT"}
|
||
mancanti = colonne_attese - set(dato_chauv.analisi_stat.columns)
|
||
assert not mancanti, f"colonne mancanti in analisi_stat: {mancanti}"
|
||
assert len(dato_chauv.analisi_stat.at[0, "outT"]) >= 1, (
|
||
"l'outlier atteso non è stato rimosso"
|
||
)
|
||
return dato_chauv.analisi_stat.to_markdown()
|
||
|
||
esegui_test("Data.stat_chauv", _test_stat_chauv)
|
||
return campione_chauv, dato_chauv
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Test 3 — `Data.calc_var(funz, nome_risultato)`
|
||
|
||
Aggiungiamo una seconda grandezza `P` allo stesso `Data` e calcoliamo
|
||
`Z = T * P` con propagazione simbolica dell'incertezza.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(campione_chauv, dato_chauv, esegui_test, np, pd, sp):
|
||
def _test_calc_var():
|
||
np.random.seed(1)
|
||
campione_p = pd.DataFrame(
|
||
np.random.normal(2, 0.02, size=(4, 8)),
|
||
columns=[f"P{i+1}" for i in range(8)],
|
||
)
|
||
dato_chauv.campione = pd.concat([campione_chauv, campione_p], axis=1)
|
||
dato_chauv.stat_chauv(["T", "P"], [0.02, 0.01])
|
||
|
||
T, P = sp.symbols("T P", positive=True)
|
||
dato_chauv.calc_var(T * P, "Z")
|
||
|
||
for col in ["Z", "uZ", "%uZ_T", "%uZ_P"]:
|
||
assert col in dato_chauv.analisi_stat.columns, f"colonna mancante: {col}"
|
||
|
||
T_val = dato_chauv.analisi_stat["T"].astype(float)
|
||
P_val = dato_chauv.analisi_stat["P"].astype(float)
|
||
Z_val = dato_chauv.analisi_stat["Z"].astype(float)
|
||
assert np.allclose(Z_val, T_val * P_val), "Z != T*P: propagazione errata"
|
||
|
||
return dato_chauv.analisi_stat[["T", "uT", "P", "uP", "Z", "uZ", "%uZ_T", "%uZ_P"]].to_markdown()
|
||
|
||
esegui_test("Data.calc_var", _test_calc_var)
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Test 4 — `Data.reg_lin(...)` (incl. `residui`, `plot_reg`, `plot_residui`)
|
||
|
||
Generiamo dati sintetici y = 2x + 1 con rumore gaussiano e verifichiamo
|
||
che la regressione pesata recuperi correttamente A e B, e che vengano
|
||
prodotti anche i grafici e i residui.
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(Data, esegui_test, np, pd):
|
||
def _test_reg_lin():
|
||
np.random.seed(2)
|
||
n = 12
|
||
A_vero, B_vero = 2.0, 1.0
|
||
x = np.linspace(0, 10, n)
|
||
ux = np.full(n, 0.1)
|
||
uy = np.full(n, 0.2)
|
||
y = A_vero * x + B_vero + np.random.normal(0, 0.2, n)
|
||
|
||
dato_reg = Data(pd.DataFrame({"x": x, "y": y, "ux": ux, "uy": uy}))
|
||
dato_reg.analisi_stat = dato_reg.campione.copy()
|
||
|
||
dato_reg.reg_lin(
|
||
stampa_param=True,
|
||
plot_regressione=True,
|
||
calc_residui=True,
|
||
x_label="x",
|
||
y_label="y",
|
||
r_label="residuo",
|
||
titolo_reg="Test regressione",
|
||
titolo_residui="Test residui",
|
||
)
|
||
|
||
assert dato_reg.param_reg is not None, "param_reg non impostato"
|
||
for chiave in ["A", "B", "uA", "uB", "covAB", "x2", "P"]:
|
||
assert chiave in dato_reg.param_reg.index, f"manca {chiave} in param_reg"
|
||
|
||
errore_A = abs(dato_reg.param_reg["A"] - A_vero)
|
||
assert errore_A < 0.3, f"A stimato troppo lontano dal vero: {dato_reg.param_reg['A']}"
|
||
assert "r" in dato_reg.analisi_stat.columns
|
||
assert "ur" in dato_reg.analisi_stat.columns
|
||
|
||
p = dato_reg.param_reg
|
||
return (
|
||
f"A = {p['A']:.4f} ± {p['uA']:.4f} (vero: {A_vero}) \n"
|
||
f"B = {p['B']:.4f} ± {p['uB']:.4f} (vero: {B_vero}) \n"
|
||
f"chi² = {p['x2']:.3f}, P = {p['P']:.3f}"
|
||
)
|
||
|
||
esegui_test("Data.reg_lin", _test_reg_lin)
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Test 5 — `plot_gauss(gaussiane)`
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(esegui_test, plot_gauss):
|
||
def _test_plot_gauss():
|
||
gaussiane = [
|
||
(0, 1, 0, "N(0,1)"),
|
||
(2, 0.5, 1, "N(2, 0.5)"),
|
||
]
|
||
plot_gauss(gaussiane)
|
||
return "Grafico generato senza errori per 2 gaussiane."
|
||
|
||
esegui_test("plot_gauss", _test_plot_gauss)
|
||
return
|
||
|
||
|
||
@app.cell(hide_code=True)
|
||
def _(mo):
|
||
mo.md(r"""
|
||
## Riepilogo finale
|
||
""")
|
||
return
|
||
|
||
|
||
@app.cell
|
||
def _(mo, risultati_test):
|
||
_n_ok = sum(1 for _, ok, _ in risultati_test if ok)
|
||
_n_tot = len(risultati_test)
|
||
_righe = [f"| {nome} | {'✅ OK' if ok else '❌ ERRORE: ' + err} |" for nome, ok, err in risultati_test]
|
||
_tabella = "| Funzione | Esito |\n|---|---|\n" + "\n".join(_righe)
|
||
|
||
_titolo = (
|
||
f"### ✅ Tutti i test sono passati ({_n_ok}/{_n_tot})"
|
||
if _n_ok == _n_tot
|
||
else f"### ⚠️ {_n_tot - _n_ok} test falliti su {_n_tot}"
|
||
)
|
||
|
||
mo.md(f"{_titolo}\n\n{_tabella}")
|
||
return
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app.run()
|