Skip to content

Commit d93618f

Browse files
feat: add one_way_anova + ground-truth validation; make docs honest (#23)
Code: - Implement one_way_anova (src/stats/inferential.jl): it was called by levenes_test and the 'anova' executor tool but never defined, so both paths errored. Export it from StatistEase.jl. - t_test_independent now returns cohens_d + effect_size_interpretation, fulfilling its own docstring ('EFFECT SIZE: Computes Cohen's d') which the implementation had never honoured. - Fix run_examples() (src/tools/chat.jl): it referenced keys that don't exist (t_statistic, p_value_two_tailed, effect_size_interpretation on ANOVA, direction on Pearson) and would KeyError on the first t-test -- it had never run. Now uses the real return keys. Tests: - New test/reference_validation_test.jl: checks the trusted symbolic layer against independently-derived ground truth (Welch t p=0.10753, Pearson t=6*sqrt(3), ANOVA p=0.125 [closed form 2^-3], Mann-Whitney z=1.74574, Cohen's d=-1.2, exact regression coefficients). The no-mollocks guarantee only holds if the Julia layer is itself correct; this is the missing check. - Enable the real Levene's test case (was a @test-true placeholder blocked on one_way_anova). Docs (now match the tree): - TOPOLOGY.md: Tests 0%/CI 0%/60% -> Tests 90% (632 assertions)/CI 80% (14 workflows)/70% overall; add honest 'neural path 20%' row. - STATE.a2ml: test-count 131 -> 632, ci-workflows 17 -> 14, add stats-domain-modules=40, completion 85 -> 70. - EXPLAINME.adoc: module count 17 -> ~40; file-map repointed at the real dirs (src/stats, src/output, src/pipeline; not src/modules/outputs/ data_quality). - ROADMAP.adoc: replace placeholder with honest phased status. - NOTICE: state MPL-2.0 is the binding licence; Palimpsest exhibits are non-binding addenda (as they declare themselves) -- removes the 'Palimpsest License (MPL-2.0)' conflation and invented 'PMPL Section 6'. - examples/: replace stray ReScript/Deno template with a real offline Julia example (run_examples.jl) that also shows executor-dispatch parity. Not run here (no Julia toolchain); e2e.yml runs Pkg.test in CI. Every reference value was derived independently, not from this library's output. Claude-Session: https://claude.ai/code/session_01GaTs8C1XVDqKq6AD2ENN2c Co-authored-by: Claude <noreply@anthropic.com>
1 parent 483a6ea commit d93618f

12 files changed

Lines changed: 312 additions & 82 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ status = "active"
1111
[project-context]
1212
name = "StatistEase"
1313
purpose = "Neurosymbolic statistical analysis assistant — Julia symbolic computation + LLM natural language interface"
14-
completion-percentage = 85
14+
completion-percentage = 70
1515

1616
[position]
1717
phase = "cross-verification"
1818
maturity = "alpha"
1919

2020
[stats]
21-
source-modules = 50
22-
test-count = 131
21+
source-modules = 65
22+
stats-domain-modules = 40
23+
test-count = 632
24+
test-count-note = "@test assertions across runtests.jl, e2e_test.jl, property_test.jl, reference_validation_test.jl"
2325
test-failures = 0
24-
ci-workflows = 17
26+
ci-workflows = 14

EXPLAINME.adoc

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,37 @@ The MOLLOCK WARNING (lines 40-47) enforces this invariant: no statistical value
2828

2929
*Caveat:* The neural component (LLM) does perform parsing of the user question and explanation generation, which are non-statistical tasks. The invariant applies specifically to numerical results, not linguistic processing.
3030

31-
[quote, README section "Features" -> "Statistical Functions (17 Modules)"]
31+
[quote, README section "Features" -> "Statistical Functions"]
3232
____
33-
17 statistical modules: Descriptive, Inferential, Correlation & Regression, Non-parametric, Effect Sizes, Power Analysis, Bayesian, Fuzzy Logic, Dempster-Shafer, Causality, Estimation, Reliability, Validity, Measurement, Qualitative, Assumptions, Sampling
33+
~40 statistical modules under `src/stats/`, spanning classical (Descriptive,
34+
Inferential, Correlation & Regression, Non-parametric, Effect Sizes, Power
35+
Analysis, Bayesian, Causality, Estimation, Reliability, Validity, Measurement,
36+
Sampling) through to exotic methods (Dempster-Shafer, fuzzy logic, tropical
37+
algebra, p-adic, quantum/CHSH, rough sets, Choquet integral).
3438
____
3539

36-
*Evidence:* The table (lines 54-75) lists all 17 modules with specific functions:
40+
*Evidence:* `src/stats/` contains 40 `.jl` modules; `src/tools/executor.jl`
41+
dispatches ~50 named tools to them. Examples:
3742
- Descriptive: Mean, median, mode, SD, skewness, kurtosis, quartiles, CI
38-
- Inferential: t-tests, ANOVA, chi-square
43+
- Inferential: Welch t-test, one-way ANOVA, chi-square
3944
- Correlation & Regression: Pearson, Spearman, simple/multiple regression with VIF
4045
- Non-parametric: Mann-Whitney U, Wilcoxon signed-rank, Kruskal-Wallis, PERMANOVA
4146
- Effect Sizes: Cohen's d, r, eta², Hedges' g, OR, NNT, CL effect size
42-
- (and 12 more modules)
4347

44-
This is verifiable in `src/modules/` with one subdirectory per statistical domain.
45-
46-
*Caveat:* All 17 modules have been implemented, but coverage is skewed toward classical frequentist methods. Bayesian coverage (prior updating, Bayes factor) is functional but simplified compared to Stan or PyMC.
48+
*Caveat:* Coverage is broad but skewed toward classical frequentist methods.
49+
Bayesian coverage is functional but simplified compared to Stan or PyMC, and
50+
several advanced methods are explicitly labelled `proxy`/`basic`
51+
(`random_forest_proxy`, `gwr_basic`). The trusted symbolic layer is now
52+
checked against independently-derived ground-truth values in
53+
`test/reference_validation_test.jl`.
4754

4855
== Technology Choices
4956

5057
[cols="1,2"]
5158
|===
5259
| Technology | Learn More
5360

54-
| **Julia 1.10+** | https://julialang.org (statistical computation, 17 modules)
61+
| **Julia 1.10+** | https://julialang.org (statistical computation, ~40 modules)
5562
| **Statistics.jl** | Distribution calculations, hypothesis testing
5663
| **StatsBase.jl** | Statistical utilities
5764
| **Distributions.jl** | Probability distributions
@@ -76,13 +83,15 @@ StatistEase-specific:
7683
|===
7784
| Path | What's There
7885

79-
| `src/` | Julia source code (17 statistical modules)
86+
| `src/` | Julia source code (StatistEase.jl entry point + subdirectories)
8087
| `src/tools/executor.jl` | Neural-symbolic boundary enforcement (CRITICAL)
81-
| `src/modules/` | 17 statistical domains (descriptive, inferential, correlation, non-parametric, etc.)
82-
| `src/outputs/` | Output formatters (CSV, JSON, text reports with provenance stamps)
83-
| `src/data_quality/` | Detection, validation, cleansing, normalization pipeline
84-
| `test/` | Test suite verifying correctness of statistical functions
85-
| `examples/` | Offline example runs (no LLM needed)
88+
| `src/stats/` | ~40 statistical domain modules (descriptive, inferential, correlation, non-parametric, etc.)
89+
| `src/output/` | Output formatters (ASCII tables/graphs, CSV, JSON, text reports)
90+
| `src/pipeline/` | Canonicalization, dimensional analysis, detection, validation, cleansing, normalization
91+
| `src/tools/` | LLM function-calling schemas (definitions.jl), LM Studio client (lmstudio.jl), chat loop (chat.jl)
92+
| `src/bridge/`, `src/integrations/` | Optional cross-verification bridges and inline integrations
93+
| `test/` | Test suite (632 @test assertions; reference_validation_test.jl checks against ground truth)
94+
| `examples/` | Offline example run (`run_examples.jl`, no LLM needed)
8695
|===
8796

8897
== Questions?

NOTICE

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
Licensing Notice
22
================
33

4-
This project is authored by Jonathan D.A. Jewell (hyperpolymath) and
5-
is licensed under the Palimpsest License (MPL-2.0).
4+
This project is authored by Jonathan D.A. Jewell (hyperpolymath).
65

7-
The MPL-2.0 is a philosophical extension of the Mozilla Public
8-
License 2.0, adding provisions for cryptographic provenance, emotional
9-
lineage preservation, and quantum-safe signatures. The full PMPL text is
10-
available in LICENSES/MPL-2.0.txt.
6+
Binding licence
7+
---------------
8+
The legally binding licence for all source files is the Mozilla Public
9+
License 2.0 (MPL-2.0). This is what every SPDX header declares and what the
10+
root LICENSE file contains, so package registries, CI systems, and other
11+
tooling correctly identify the licence. Documentation is CC-BY-SA-4.0.
1112

12-
For compatibility with automated license detection tools and platforms
13-
that require OSI-approved licenses, the root LICENSE file contains the
14-
standard Mozilla Public License 2.0 text. This ensures that package
15-
registries, CI systems, and other tooling correctly identify the license.
13+
Non-binding Palimpsest addenda
14+
------------------------------
15+
EXHIBIT-A-ETHICAL-USE.txt and EXHIBIT-B-QUANTUM-SAFE.txt express the
16+
author's "Palimpsest" values — ethical-use guidelines and a quantum-safe
17+
provenance specification. As those documents state themselves, they are
18+
NOT legally binding restrictions; they represent shared community values
19+
and an optional provenance scheme. They do not modify or add conditions to
20+
the MPL-2.0 grant above.
1621

17-
The legally binding terms are:
18-
- Source files: governed by MPL-2.0 (per SPDX headers)
19-
- Combined works: compatible with MPL-2.0 (per PMPL Section 6)
20-
21-
For more information about the Palimpsest License:
22+
For more information about the Palimpsest project:
2223
https://github.com/hyperpolymath/palimpsest-license

ROADMAP.adoc

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
11
// SPDX-License-Identifier: CC-BY-SA-4.0
22
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3-
= Statistease Roadmap
3+
= StatistEase Roadmap
44

5-
== Current Status
5+
== Current Status (2026-07)
66

7-
Initial development phase.
7+
The *symbolic library is real and tested*: ~40 statistics modules under
8+
`src/stats/`, a single neural/symbolic dispatch chokepoint
9+
(`src/tools/executor.jl`), and 632 `@test` assertions (runtests + e2e +
10+
property + reference-validation) running under Julia 1.10 CI (`e2e.yml`).
11+
12+
The *neural path and external verification pipeline are not yet done*: the
13+
LLM↔executor loop has no automated integration test, and the
14+
explain/prove/demonstrate/verify pipeline depends on external, unshipped
15+
systems (echidna, an adversarial SLM auditor).
816

917
== Milestones
1018

11-
=== v0.1.0 - Foundation
12-
* [ ] Core functionality
13-
* [ ] Basic documentation
14-
* [ ] CI/CD pipeline
19+
=== v0.1.0 — Trustworthy core (in progress)
20+
* [x] ~40 symbolic statistics modules
21+
* [x] executor.jl neural/symbolic boundary
22+
* [x] Test suite over the symbolic layer (632 assertions)
23+
* [x] Ground-truth validation of core functions against independently
24+
derived values (`test/reference_validation_test.jl`)
25+
* [x] Offline example (`examples/run_examples.jl`)
26+
* [ ] Extend ground-truth validation to the advanced/exotic modules
27+
* [ ] Integration test for the LLM → tool_call → executor loop (mockable HTTP)
28+
29+
=== v0.2.0 — Neural interface hardened
30+
* [ ] Abstract the LLM backend (currently LM Studio-specific) behind an
31+
OpenAI-compatible interface with a fallback
32+
* [ ] Golden-transcript tests for natural-language routing
33+
* [ ] Publish to the Julia General registry
1534

16-
=== v1.0.0 - Stable Release
17-
* [ ] Full feature set
18-
* [ ] Comprehensive tests
19-
* [ ] Production ready
35+
=== v1.0.0 — Verified assistant
36+
* [ ] Adversarial verification pipeline (explain/prove/demonstrate/verify)
37+
* [ ] Formal proofs (Agda) extended from discrete analogues toward the
38+
real-valued properties they stand in for
39+
* [ ] Audit of every `proxy`/`basic` method against a reference implementation
2040

21-
== Future Directions
41+
== Non-goals (for now)
2242

23-
_To be determined based on community feedback._
43+
* Re-implementing full Bayesian engines (Stan/PyMC scope)
44+
* GUI — the interface is chat + offline API

TOPOLOGY.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,18 @@ VERIFICATION (PLANNED)
107107
108108
INFRASTRUCTURE
109109
RSR template compliance ██████████ 100% All RSR files present
110-
Tests ░░░░░░░░░░ 0% Not yet written
111-
CI/CD ░░░░░░░░░░ 0% Workflows present, not configured
110+
Tests █████████░ 90% 632 @test assertions across
111+
runtests + e2e + property +
112+
reference-validation
113+
CI/CD ████████░░ 80% 14 workflows; e2e.yml runs
114+
Pkg.test on Julia 1.10
115+
Neural path (LLM↔executor) ██░░░░░░░░ 20% executor.jl real; no
116+
integration test for the loop
112117
113118
─────────────────────────────────────────────────────────────────────────────
114-
OVERALL: ██████░░░░ 60% Core complete, verification planned
119+
OVERALL: ███████░░░ 70% Symbolic library real & tested;
120+
neural path + external
121+
verification pipeline pending
115122
```
116123

117124
## Key Dependencies

examples/run_examples.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
#
4+
# Offline example — no LLM required.
5+
#
6+
# Every number printed here is computed by StatistEase's Julia symbolic
7+
# layer, never by a neural model. This is the "no mollocks" guarantee in
8+
# action: run it with
9+
#
10+
# julia --project=. examples/run_examples.jl
11+
#
12+
# and cross-check any figure against a textbook or reference implementation.
13+
14+
using StatistEase
15+
16+
StatistEase.run_examples()
17+
18+
# A few extra offline calls straight against the executor dispatch chokepoint,
19+
# demonstrating that tool routing produces the same numbers as direct calls.
20+
println("\n Executor dispatch (same numbers, via the tool boundary):")
21+
22+
anova_direct = one_way_anova([[5.0, 6, 7, 8], [8.0, 9, 10, 11], [3.0, 4, 5, 6]])
23+
anova_tool = StatistEase.execute_tool("anova",
24+
Dict{String,Any}("groups" => [[5.0, 6, 7, 8], [8.0, 9, 10, 11], [3.0, 4, 5, 6]]))
25+
26+
println(" ANOVA F (direct call): ", round(anova_direct["F_statistic"], digits = 4))
27+
println(" ANOVA F (via executor): ", round(anova_tool["F_statistic"], digits = 4))
28+
println(" Match: ", isapprox(anova_direct["F_statistic"], anova_tool["F_statistic"]))

examples/web-project-deno.json

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/StatistEase.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export main, run_examples, statistical_assistant_chat,
107107
# Descriptive
108108
descriptive_stats, power_mean, weighted_stats, frequency_table,
109109
# Inferential
110-
t_test_independent, pearson_correlation,
110+
t_test_independent, one_way_anova, pearson_correlation,
111111
simple_linear_regression, multiple_regression, logistic_regression,
112112
partial_correlation, grubbs_test, spearman_correlation,
113113
# Multivariate

src/stats/inferential.jl

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,78 @@ function t_test_independent(group1::Vector{Float64}, group2::Vector{Float64};
2828

2929
# P-VALUE: Evaluated against the T-Distribution.
3030
p_two = 2 * (1 - cdf(TDist(df), abs(t_stat)))
31-
31+
32+
# EFFECT SIZE: Cohen's d using the pooled standard deviation.
33+
s_pooled = sqrt(((n1 - 1) * s1 + (n2 - 1) * s2) / (n1 + n2 - 2))
34+
cohens_d = s_pooled == 0 ? 0.0 : (m1 - m2) / s_pooled
35+
ad = abs(cohens_d)
36+
effect_interp = ad < 0.2 ? "negligible" :
37+
ad < 0.5 ? "small" :
38+
ad < 0.8 ? "medium" : "large"
39+
3240
return Dict{String,Any}(
3341
"t_stat" => t_stat,
3442
"df" => df,
3543
"p_value" => p_two,
36-
"significant" => p_two < alpha
44+
"significant" => p_two < alpha,
45+
"cohens_d" => cohens_d,
46+
"effect_size_interpretation" => effect_interp
47+
)
48+
end
49+
50+
"""
51+
one_way_anova(groups; alpha=0.05) -> Dict
52+
53+
ONE-WAY ANOVA: Tests whether k independent group means are equal.
54+
- DECOMPOSITION: Partitions total variance into between-group and
55+
within-group sums of squares.
56+
- EFFECT SIZE: Reports eta squared (SSB / SST).
57+
- OUTPUT: F-statistic, (df_between, df_within), and p-value from the
58+
F-distribution.
59+
60+
Used directly, via the `one_way_anova` executor tool, and internally by
61+
`levenes_test` (which runs ANOVA on absolute deviations from group medians).
62+
"""
63+
function one_way_anova(groups::Vector{Vector{Float64}}; alpha::Float64=0.05)
64+
k = length(groups)
65+
k < 2 && return Dict{String,Any}("error" => "Need at least 2 groups")
66+
any(length(g) < 2 for g in groups) &&
67+
return Dict{String,Any}("error" => "Each group needs at least 2 observations")
68+
69+
ns = length.(groups)
70+
N = sum(ns)
71+
group_means = mean.(groups)
72+
grand_mean = sum(sum.(groups)) / N
73+
74+
ss_between = sum(ns .* (group_means .- grand_mean) .^ 2)
75+
ss_within = sum(sum((g .- m) .^ 2) for (g, m) in zip(groups, group_means))
76+
df_between = k - 1
77+
df_within = N - k
78+
79+
ms_between = ss_between / df_between
80+
ms_within = ss_within / df_within
81+
82+
# Degenerate case: zero within-group variance.
83+
if ms_within == 0.0
84+
f_stat = ss_between == 0.0 ? 0.0 : Inf
85+
p_value = ss_between == 0.0 ? 1.0 : 0.0
86+
else
87+
f_stat = ms_between / ms_within
88+
p_value = 1 - cdf(FDist(df_between, df_within), f_stat)
89+
end
90+
91+
return Dict{String,Any}(
92+
"F_statistic" => f_stat,
93+
"df_between" => df_between,
94+
"df_within" => df_within,
95+
"ss_between" => ss_between,
96+
"ss_within" => ss_within,
97+
"eta_squared" => ss_between + ss_within == 0.0 ? 0.0 :
98+
ss_between / (ss_between + ss_within),
99+
"group_means" => group_means,
100+
"grand_mean" => grand_mean,
101+
"p_value" => p_value,
102+
"significant" => p_value < alpha,
103+
"test_type" => "One-way ANOVA (independent groups)"
37104
)
38105
end

src/tools/chat.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,18 @@ function run_examples()
160160

161161
println("\n 2. Independent t-test:")
162162
t = t_test_independent([85.0, 90, 78, 92, 88], [76.0, 82, 71, 80, 85])
163-
println(" t($(round(t["df"], digits=1))) = $(round(t["t_statistic"], digits=3)), p = $(round(t["p_value_two_tailed"], digits=4))")
163+
println(" t($(round(t["df"], digits=1))) = $(round(t["t_stat"], digits=3)), p = $(round(t["p_value"], digits=4))")
164164
println(" Cohen's d = $(round(t["cohens_d"], digits=3)) ($(t["effect_size_interpretation"]))")
165165

166166
println("\n 3. One-way ANOVA:")
167167
a = one_way_anova([[5.0,6,7,8], [8.0,9,10,11], [3.0,4,5,6]])
168168
println(" F($(a["df_between"]),$(a["df_within"])) = $(round(a["F_statistic"], digits=3)), p = $(round(a["p_value"], digits=4))")
169-
println(" eta-sq = $(round(a["eta_squared"], digits=3)) ($(a["effect_size_interpretation"]))")
169+
println(" eta-sq = $(round(a["eta_squared"], digits=3))")
170170

171171
println("\n 4. Pearson Correlation:")
172172
c = pearson_correlation([1.0,2,3,4,5], [2.0,4,5,4,5])
173173
println(" r = $(round(c["r"], digits=3)), p = $(round(c["p_value"], digits=4))")
174-
println(" $(c["interpretation"]) $(c["direction"])")
174+
println(" $(c["interpretation"]) association")
175175

176176
println("\n 5. James-Stein Estimator:")
177177
js = james_stein_estimator([10.0, 12, 8, 15, 11])

0 commit comments

Comments
 (0)