mcp-calculator/tests/test_stats.py
2026-09-15 20:38:16 +02:00

112 lines
4.3 KiB
Python

import math
from mcp_calculator.ops.context import MathError
from mcp_calculator.ops.stats import hypothesis_test, probability, regression, statistics
from tests.support import MathTestCase
class StatisticsTest(MathTestCase):
def test_basic(self):
r = statistics([2, 4, 4, 4, 5, 5, 7, 9])
self.assertEqual(r["mean"], 5)
self.assertEqual(r["median"], 4.5)
self.assertEqual(r["mode"], [4.0])
self.assertApprox(r["std_dev"], 2.13808993529939)
def test_population(self):
r = statistics([2, 4, 4, 4, 5, 5, 7, 9], sample=False)
self.assertEqual(r["std_dev"], 2)
self.assertTrue(r["kind"].startswith("population"))
def test_percentiles(self):
self.assertApprox(statistics(list(range(1, 101)), percentiles=[90])["percentiles"]["90"], 90.1)
def test_paired(self):
self.assertApprox(statistics([1, 2, 3, 4, 5], y=[2, 4, 6, 8, 10])["paired"]["pearson_r"], 1)
def test_single_value(self):
self.assertEqual(statistics([3])["mean"], 3)
def test_rejects_empty(self):
with self.assertRaises(MathError):
statistics([])
class RegressionTest(MathTestCase):
def test_linear(self):
r = regression([1, 2, 3, 4], [3, 5, 7, 9])
self.assertApprox(r["coefficients"]["intercept"], 1)
self.assertApprox(r["coefficients"]["slope"], 2)
self.assertApprox(r["r_squared"], 1)
def test_prediction(self):
self.assertApprox(regression([1, 2, 3], [2, 4, 6], predict=[10])["predictions"][0]["y"], 20)
def test_polynomial(self):
self.assertApprox(regression([0, 1, 2, 3], [1, 2, 5, 10], model="polynomial", degree=2)["coefficients"]["x^2"], 1)
def test_exponential(self):
xs = [0, 1, 2, 3]
r = regression(xs, [2 * math.exp(0.5 * x) for x in xs], model="exponential")
self.assertApprox(r["coefficients"]["a"], 2)
self.assertApprox(r["coefficients"]["b"], 0.5)
def test_length_mismatch(self):
with self.assertRaisesRegex(MathError, "same length"):
regression([1, 2], [1, 2, 3])
class ProbabilityTest(MathTestCase):
def test_normal_between(self):
r = probability("normal", "between", {"mean": 100, "std": 15}, lower=85, upper=115)
self.assertApprox(r["result"], 0.682689492137)
def test_binomial_discrete_semantics(self):
r = probability("binomial", "sf", {"n": 10, "p": 0.5}, x=7)
self.assertApprox(r["result"], 56 / 1024)
self.assertIn("P(X >= 8)", r["meaning"])
def test_pmf(self):
self.assertApprox(probability("poisson", "pdf", {"lambda": 2}, x=0)["result"], math.exp(-2))
def test_quantile(self):
self.assertApprox(probability("normal", "quantile", q=0.975)["result"], 1.959963985)
def test_summary(self):
self.assertApprox(probability("exponential", "summary", {"rate": 2})["result"]["mean"], 0.5)
def test_missing_parameter(self):
with self.assertRaisesRegex(MathError, "needs parameter"):
probability("binomial", "cdf", {"n": 10}, x=3)
def test_unknown_parameter(self):
with self.assertRaisesRegex(MathError, "unknown parameter"):
probability("normal", "cdf", {"mu": 0}, x=0)
class HypothesisTestTest(MathTestCase):
def test_one_sample_t(self):
r = hypothesis_test("one_sample_t", sample=[5.1, 4.9, 5.3, 5.5, 5.0], mu=5)
self.assertApprox(r["p_value"], 0.2115789946)
self.assertIs(r["reject_null"], False)
def test_two_sample_welch(self):
r = hypothesis_test("two_sample_t", sample=[1, 2, 3, 4, 5], sample2=[6, 7, 8, 9, 10])
self.assertIs(r["reject_null"], True)
self.assertEqual(r["variant"], "Welch")
def test_chi2_independence(self):
r = hypothesis_test("chi2_independence", table=[[10, 20], [30, 40]])
self.assertEqual(r["df"], 1)
self.assertIn("cramers_v", r)
def test_goodness_of_fit_accepts_probabilities(self):
self.assertApprox(hypothesis_test("chi2_goodness_of_fit", sample=[50, 50], expected=[0.5, 0.5])["p_value"], 1)
def test_binomial(self):
r = hypothesis_test("binomial", sample=[9, 10], p=0.5, alternative="greater")
self.assertApprox(r["p_value"], 11 / 1024)
def test_missing_input(self):
with self.assertRaisesRegex(MathError, "needs 'sample2'"):
hypothesis_test("paired_t", sample=[1, 2, 3])