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()