From ed4b8f2f335c2a44e22d642ef1f08822d447bd48 Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 1/6] Add shape statistics helpers --- src/luasf.lua | 1 + src/luasf/shape.lua | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/luasf/shape.lua diff --git a/src/luasf.lua b/src/luasf.lua index 7be91bf..9fc63b5 100644 --- a/src/luasf.lua +++ b/src/luasf.lua @@ -34,6 +34,7 @@ local M = {} merge(M, require(prefix .. "rng")) merge(M, require(prefix .. "core")) merge(M, require(prefix .. "descriptive")) +merge(M, require(prefix .. "shape")) merge(M, require(prefix .. "sampling")) merge(M, require(prefix .. "distributions")) merge(M, require(prefix .. "bivariate")) diff --git a/src/luasf/shape.lua b/src/luasf/shape.lua new file mode 100644 index 0000000..3d120c3 --- /dev/null +++ b/src/luasf/shape.lua @@ -0,0 +1,52 @@ +local module_name = ... or "luasf.shape" +local prefix = module_name:match("^src%.") and "src.luasf." or "luasf." + +local validation = require(prefix .. "validation") +local descriptive = require(prefix .. "descriptive") + +local M = {} + +local function central_moment(array, order) + validation.assert_min_length(array, 2) + validation.assert_numeric_array(array) + validation.assert_number(order, "order") + assert(order >= 1, "order must be greater than or equal to 1") + + local mean = descriptive.mean(array) + local total = 0 + + for i = 1, #array do + total = total + (array[i] - mean) ^ order + end + + return total / #array +end + +local function skewness(array) + local m2 = central_moment(array, 2) + assert(m2 > 0, "array variance must be greater than 0") + + local m3 = central_moment(array, 3) + + return m3 / (m2 ^ 1.5) +end + +local function kurtosis(array) + local m2 = central_moment(array, 2) + assert(m2 > 0, "array variance must be greater than 0") + + local m4 = central_moment(array, 4) + + return m4 / (m2 * m2) +end + +local function excess_kurtosis(array) + return kurtosis(array) - 3 +end + +M.central_moment = central_moment +M.skewness = skewness +M.kurtosis = kurtosis +M.excess_kurtosis = excess_kurtosis + +return M From e51c18fed0fb98eca3eaa8b67291154575269f1e Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 2/6] Add shape statistics tests --- spec/test_shape.lua | 54 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 spec/test_shape.lua diff --git a/spec/test_shape.lua b/spec/test_shape.lua new file mode 100644 index 0000000..d39170e --- /dev/null +++ b/spec/test_shape.lua @@ -0,0 +1,54 @@ +local luaunit = require("luaunit") +local stats = require("luasf") + +TestShape = {} + +function TestShape:test_skewness_symmetric_data() + local values = {1, 2, 3, 4, 5} + + luaunit.assertAlmostEquals(stats.skewness(values), 0, 0.000001) +end + +function TestShape:test_skewness_positive_data() + local values = {1, 1, 2, 2, 10} + + luaunit.assertTrue(stats.skewness(values) > 0) +end + +function TestShape:test_skewness_negative_data() + local values = {1, 9, 10, 10, 10} + + luaunit.assertTrue(stats.skewness(values) < 0) +end + +function TestShape:test_kurtosis_for_simple_sequence() + local values = {1, 2, 3, 4, 5} + + luaunit.assertAlmostEquals(stats.kurtosis(values), 1.7, 0.000001) +end + +function TestShape:test_excess_kurtosis_for_simple_sequence() + local values = {1, 2, 3, 4, 5} + + luaunit.assertAlmostEquals(stats.excess_kurtosis(values), -1.3, 0.000001) +end + +function TestShape:test_central_moment_second_order() + local values = {1, 2, 3, 4, 5} + + luaunit.assertAlmostEquals(stats.central_moment(values, 2), 2, 0.000001) +end + +function TestShape:test_skewness_requires_non_constant_array() + luaunit.assertError(function() + stats.skewness({1, 1, 1}) + end) +end + +function TestShape:test_kurtosis_requires_non_constant_array() + luaunit.assertError(function() + stats.kurtosis({1, 1, 1}) + end) +end + +os.exit(luaunit.LuaUnit.run()) From 4f3a0960e4583ae847e951b6c0a264dae044db52 Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 3/6] Add skewness and kurtosis example --- examples/skewness_kurtosis.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/skewness_kurtosis.lua diff --git a/examples/skewness_kurtosis.lua b/examples/skewness_kurtosis.lua new file mode 100644 index 0000000..750f5a8 --- /dev/null +++ b/examples/skewness_kurtosis.lua @@ -0,0 +1,15 @@ +local stats = require("luasf") + +local symmetric_values = {1, 2, 3, 4, 5} +local right_skewed_values = {1, 1, 2, 2, 10} + +local function print_shape_summary(label, values) + print(label) + print("Skewness:", stats.skewness(values)) + print("Kurtosis:", stats.kurtosis(values)) + print("Excess kurtosis:", stats.excess_kurtosis(values)) + print("") +end + +print_shape_summary("Symmetric values", symmetric_values) +print_shape_summary("Right-skewed values", right_skewed_values) From cb3239297c8ca5c1d76a20feb4be6edfed3a5779 Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 4/6] Add LuaRocks rockspec for v0.6.0 --- rockspec/luasf-0.6.0-1.rockspec | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 rockspec/luasf-0.6.0-1.rockspec diff --git a/rockspec/luasf-0.6.0-1.rockspec b/rockspec/luasf-0.6.0-1.rockspec new file mode 100644 index 0000000..87b30af --- /dev/null +++ b/rockspec/luasf-0.6.0-1.rockspec @@ -0,0 +1,41 @@ +package = "luasf" +version = "0.6.0-1" + +source = { + url = "git://github.com/HubertRonald/LuaSF.git", + tag = "v0.6.0" +} + +description = { + summary = "Lua Statistics Functions", + detailed = [[ +LuaSF is a lightweight, pure-Lua library for descriptive statistics, +shape statistics, bivariate statistics, sampling utilities, simulation +examples, and random variable generation. + ]], + homepage = "https://github.com/HubertRonald/LuaSF", + license = "MIT", + maintainer = "Hubert Ronald" +} + +dependencies = { + "lua >= 5.1" +} + +build = { + type = "builtin", + modules = { + luasf = "src/luasf.lua", + ["luasf.core"] = "src/luasf/core.lua", + ["luasf.validation"] = "src/luasf/validation.lua", + ["luasf.rng"] = "src/luasf/rng.lua", + ["luasf.descriptive"] = "src/luasf/descriptive.lua", + ["luasf.shape"] = "src/luasf/shape.lua", + ["luasf.sampling"] = "src/luasf/sampling.lua", + ["luasf.distributions"] = "src/luasf/distributions.lua", + ["luasf.bivariate"] = "src/luasf/bivariate.lua", + ["luasf.probability"] = "src/luasf/probability.lua", + LuaSF = "LuaSF.lua", + LuaStat = "LuaStat.lua" + } +} From 07cfd0db29d29bfe2a1608b07332a3e40f56ceba Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 5/6] Add shape statistics to workflows --- .github/workflows/ci.yml | 4 +++- .github/workflows/publish-luarocks.yml | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58d023d..dcd16fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,8 @@ jobs: lua spec/test_stats.lua lua spec/test_distributions.lua lua spec/test_sampling.lua + lua spec/test_bivariate.lua + lua spec/test_shape.lua - name: Run examples run: | @@ -67,5 +69,5 @@ jobs: lua examples/poisson_arrivals.lua lua examples/binomial_coin_flips.lua lua examples/bootstrap_mean.lua - lua spec/test_bivariate.lua lua examples/covariance_correlation.lua + lua examples/skewness_kurtosis.lua \ No newline at end of file diff --git a/.github/workflows/publish-luarocks.yml b/.github/workflows/publish-luarocks.yml index c4ea3ce..fa7cc25 100644 --- a/.github/workflows/publish-luarocks.yml +++ b/.github/workflows/publish-luarocks.yml @@ -6,7 +6,7 @@ on: rockspec: description: "Rockspec file to validate or publish" required: true - default: "rockspec/luasf-0.5.0-1.rockspec" + default: "rockspec/luasf-0.6.0-1.rockspec" type: string publish: description: "Publish to LuaRocks after validation" @@ -47,6 +47,9 @@ jobs: - name: Test LuaRocks package entry point run: lua -e 'local stats = require("luasf"); print(stats.sum({1,2,3}))' + - name: Test shape statistics entry point + run: lua -e 'local stats = require("luasf"); print(stats.skewness({1,2,3,4,5})); print(stats.kurtosis({1,2,3,4,5}))' + - name: Test LuaSF compatibility entry point run: lua -e 'local stats = require("LuaSF"); print(stats.sumF({1,2,3}))' From bfb95436b3a112fb0e1e3b4c239b619d87b0c950 Mon Sep 17 00:00:00 2001 From: HubertRonald Date: Mon, 8 Jun 2026 18:44:02 -0500 Subject: [PATCH 6/6] Document shape statistics helpers --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++------- CONTRIBUTING.md | 43 +++++++++++++++++++++++++++++++-- docs/api.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d6b8c4..e6d414f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,48 @@ This project follows a lightweight changelog format inspired by [Keep a Changelo ## [Unreleased] +### Planned + +* Continue improving examples and documentation. +* Explore a lightweight cross-reference with LuaHMF as a related pure-Lua math helper project. +* Evaluate future probability helpers such as `factorial`, `combinations`, and `permutations`. +* Evaluate optional formula-based simple regression summaries while keeping ML workflows outside the current scope. +* Add more distribution examples and simulation-oriented examples. + +--- + +## [0.6.0] - 2026-06-07 + +### Added + +* Added `central_moment(array, order)`. +* Added `skewness(array)`. +* Added `kurtosis(array)`. +* Added `excess_kurtosis(array)`. +* Added `src/luasf/shape.lua` for shape statistics helpers. +* Added `spec/test_shape.lua`. +* Added `examples/skewness_kurtosis.lua`. +* Added `rockspec/luasf-0.6.0-1.rockspec`. + +### Changed + +* Updated the public facade `src/luasf.lua` to expose shape statistics helpers. +* Updated CI to run shape statistics tests and the new shape statistics example. +* Updated LuaRocks publishing workflow default rockspec path for `v0.6.0`. +* Updated README, API documentation, changelog, and contribution notes for shape statistics. + +### Notes + +Shape statistics are implemented as moment-based descriptive statistics: + +* `skewness(array)` returns the standardized third central moment. +* `kurtosis(array)` returns Pearson kurtosis. +* `excess_kurtosis(array)` returns `kurtosis(array) - 3`. + +--- + +## [0.5.0] - 2026-06-07 + ### Added * Added modular internal source layout under `src/luasf/`. @@ -34,14 +76,6 @@ This project follows a lightweight changelog format inspired by [Keep a Changelo * Updated documentation to describe the modular layout and bivariate statistics helpers. * Updated CI expectations to include bivariate tests and the covariance/correlation example. -### Planned - -* Add shape statistics helpers such as `skewness(array)` and `kurtosis(array)`. -* Explore future probability helpers such as `factorial(n)`, `combinations(n, r)`, and `permutations(n, r)`. -* Explore a lightweight cross-reference with LuaHMF as a related pure-Lua math helper project. -* Consider simple formula-based regression summaries later, without turning LuaSF into a machine learning framework. -* Add more distribution examples and simulation-oriented examples. - --- ## [0.4.0] - 2026-06-04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9d166d..0fbff42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,13 +11,44 @@ LuaSF is a small, pure-Lua statistics library. The project values simplicity, co LuaSF aims to provide: * Basic descriptive statistics +* Summary statistics helpers +* Shape statistics helpers such as skewness and kurtosis +* Bivariate statistics helpers such as covariance and correlation * Pseudo-random variable generation +* Sampling utilities * A small and readable Lua codebase * Compatibility with the existing public API * Useful examples for simulations, teaching, small scripts, and game/mod scripting --- +## Scope boundaries + +LuaSF should remain lightweight and dependency-free. + +Good fits for LuaSF: + +* Descriptive statistics +* Shape statistics +* Bivariate statistics +* Probability helpers +* Random variable generation +* Sampling and simulation utilities +* Formula-based statistical summaries + +Currently out of scope: + +* Machine learning pipelines +* Optimization-based model training +* Non-linear regression fitting +* Deep learning +* Native dependencies +* Large framework-style APIs + +Formula-based simple regression summaries may be considered in the future, but ML-style model training is intentionally outside the current scope. + +--- + ## Compatibility first Please avoid breaking the existing public API. @@ -71,6 +102,7 @@ src/ distributions.lua bivariate.lua probability.lua + shape.lua validation.lua rng.lua ``` @@ -85,6 +117,7 @@ Recommended module ownership: * `distributions.lua`: random variable generators * `probability.lua`: future probability/combinatorics helpers * `validation.lua`: reusable input validation helpers +* `shape.lua`: skewness and kurtosis helpers * `rng.lua`: random generator and seed helpers * `core.lua`: small reusable internal utilities @@ -106,6 +139,12 @@ luarocks install --local luaunit eval "$(luarocks path --local)" ``` +If needed for local development, configure Lua to load the local `src/` module path: + +```bash +export LUA_PATH="./src/?.lua;./?.lua;$LUA_PATH" +``` + Run tests: ```bash @@ -113,6 +152,7 @@ lua spec/test_stats.lua lua spec/test_distributions.lua lua spec/test_sampling.lua lua spec/test_bivariate.lua +lua spec/test_shape.lua ``` Run examples: @@ -127,6 +167,7 @@ lua examples/poisson_arrivals.lua lua examples/binomial_coin_flips.lua lua examples/bootstrap_mean.lua lua examples/covariance_correlation.lua +lua examples/skewness_kurtosis.lua ``` --- @@ -233,8 +274,6 @@ Avoid: Potential future additions include: -* `skewness(array)` -* `kurtosis(array)` * `factorial(n)` * `combinations(n, r)` * `permutations(n, r)` diff --git a/docs/api.md b/docs/api.md index 77cb917..44e6c40 100644 --- a/docs/api.md +++ b/docs/api.md @@ -317,6 +317,69 @@ print(result.variance) -- 2.5 ``` +--- + +## Shape statistics + +Shape statistics describe the asymmetry and tail behavior of a numeric array. + +LuaSF implements moment-based shape helpers. + +### `central_moment(array, order)` + +Returns the central moment of a numeric array using denominator `n`. + +The central moment of order `k` is computed as the average of `(x_i - mean)^k`. + +```lua +local stats = require("luasf") + +print(stats.central_moment({1, 2, 3, 4, 5}, 2)) -- 2 +``` + +--- + +### `skewness(array)` + +Returns the standardized third central moment. + +Values near `0` indicate approximate symmetry. Positive values suggest right skew, while negative values suggest left skew. + +```lua +local stats = require("luasf") + +print(stats.skewness({1, 2, 3, 4, 5})) -- approximately 0 +print(stats.skewness({1, 1, 2, 2, 10})) -- positive +``` + +--- + +### `kurtosis(array)` + +Returns Pearson kurtosis. + +```lua +local stats = require("luasf") + +print(stats.kurtosis({1, 2, 3, 4, 5})) -- 1.7 +``` + +--- + +### `excess_kurtosis(array)` + +Returns Fisher-style excess kurtosis: + +```text +kurtosis(array) - 3 +``` + +```lua +local stats = require("luasf") + +print(stats.excess_kurtosis({1, 2, 3, 4, 5})) -- -1.3 +``` + --- ## Bivariate statistics