Aggiunta la libreria base per la statistica

This commit is contained in:
2026-09-27 21:51:48 +02:00
commit 4a34321800
6 changed files with 963 additions and 0 deletions
+295
View File
@@ -0,0 +1,295 @@
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()