模組:PythonScript/Librarys/Statistics
外观
-- Module:PythonScript/Librarys/Statistics
-- 純 Lua 實作的 Python 3.10 statistics 標準庫 (延遲載入版,支援 float/int/BigInt/Decimal 與 NormalDist)
local StatisticsMod = {}
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local S = {
initialized = false,
cache = {},
linreg_cls = nil
}
-- ==========================================
-- 1. 內部型別轉換與高精度四則運算輔助
-- ==========================================
function S.is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
function S.is_dec(v)
return type(v) == "table" and v.__type == "instance" and v.__dict and v.__dict._is_decimal == true
end
function S.raise_err(msg)
S.ensure_init()
Runtime.raise(S.cache.StatisticsError, msg)
end
function S.to_float(x)
if type(x) == "number" then return x end
if type(x) == "boolean" then return x and 1.0 or 0.0 end
if type(x) == "table" then
if x.__type == "int" then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return x.value or tonumber(MathCore.BigInt.to_string(x.sign, x.limbs))
elseif S.is_dec(x) then
return require("Module:PythonScript/Librarys/Decimal").to_number(x)
elseif x.__type == "instance" then
local ok, flt_m = pcall(PyClass.get_attr, x, "__float__")
if ok and flt_m and flt_m.__type == "bound_method" then
return require("Module:PythonScript/Evaluator").invoke(flt_m.__func, {}, {}, flt_m.__self)
end
if x.__builtin_value ~= nil then return S.to_float(x.__builtin_value) end
end
end
Runtime.raise("TypeError", "can't convert type to float")
end
function S.extract_list(iterable)
if iterable == nil then Runtime.raise("TypeError", "missing required iterable argument") end
if type(iterable) == "table" and (iterable.__type == "list" or iterable.__type == "tuple") then
local res = {}
for i = 1, #iterable.elements do res[i] = iterable.elements[i] end
return res
end
local lst = Runtime.builtins.list.__call({iterable})
return lst.elements
end
function S.sort_list(arr)
local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
local copy = {}
for i = 1, #arr do copy[i] = arr[i] end
table.sort(copy, function(a, b) return EvalUtils.py_cmp(a, b) < 0 end)
return copy
end
-- 支援保留 Decimal 型別之通用算術
function S.is_exact(v)
return type(v) == "table" and v.__type == "instance" and v.__dict and (v.__dict._is_decimal or v.__dict._is_fraction)
end
function S.v_add(a, b)
if S.is_exact(a) or S.is_exact(b) then
local Evaluator = require("Module:PythonScript/Evaluator")
local B = require("Module:PythonScript/Runtime").builtins
if S.is_exact(a) then
local r = Evaluator.invoke(PyClass.get_attr(a, "__add__"), {b}, {})
if r ~= B.NotImplemented then return r end
end
if S.is_exact(b) then
local r = Evaluator.invoke(PyClass.get_attr(b, "__radd__"), {a}, {})
if r ~= B.NotImplemented then return r end
end
end
return S.to_float(a) + S.to_float(b)
end
function S.v_sub(a, b)
if S.is_exact(a) or S.is_exact(b) then
local Evaluator = require("Module:PythonScript/Evaluator")
local B = require("Module:PythonScript/Runtime").builtins
if S.is_exact(a) then
local r = Evaluator.invoke(PyClass.get_attr(a, "__sub__"), {b}, {})
if r ~= B.NotImplemented then return r end
end
if S.is_exact(b) then
local r = Evaluator.invoke(PyClass.get_attr(b, "__rsub__"), {a}, {})
if r ~= B.NotImplemented then return r end
end
end
return S.to_float(a) - S.to_float(b)
end
function S.v_mul(a, b)
if S.is_exact(a) or S.is_exact(b) then
local Evaluator = require("Module:PythonScript/Evaluator")
local B = require("Module:PythonScript/Runtime").builtins
if S.is_exact(a) then
local r = Evaluator.invoke(PyClass.get_attr(a, "__mul__"), {b}, {})
if r ~= B.NotImplemented then return r end
end
if S.is_exact(b) then
local r = Evaluator.invoke(PyClass.get_attr(b, "__rmul__"), {a}, {})
if r ~= B.NotImplemented then return r end
end
end
return S.to_float(a) * S.to_float(b)
end
function S.v_div(a, b)
if S.is_exact(a) or S.is_exact(b) then
local Evaluator = require("Module:PythonScript/Evaluator")
local B = require("Module:PythonScript/Runtime").builtins
if S.is_exact(a) then
local r = Evaluator.invoke(PyClass.get_attr(a, "__truediv__"), {b}, {})
if r ~= B.NotImplemented then return r end
end
if S.is_exact(b) then
local r = Evaluator.invoke(PyClass.get_attr(b, "__rtruediv__"), {a}, {})
if r ~= B.NotImplemented then return r end
end
end
return S.to_float(a) / S.to_float(b)
end
function S.v_sqrt(a)
if S.is_dec(a) then
return require("Module:PythonScript/Evaluator").invoke(PyClass.get_attr(a, "sqrt"), {}, {})
end
return math.sqrt(S.to_float(a))
end
-- ==========================================
-- 2. 误差函數與常態分佈反函數 (erf & inv_cdf)
-- ==========================================
function S.erf(x)
local sign = (x < 0) and -1 or 1
x = math.abs(x)
local t = 1.0 / (1.0 + 0.3275911 * x)
local y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * math.exp(-x * x)
return sign * y
end
-- Acklam 有理逼近法計算標準常態分佈反函數 \Phi^{-1}(p)
function S.std_inv_cdf(p)
if p <= 0.0 or p >= 1.0 then
S.raise_err("inv_cdf() requires 0.0 < p < 1.0")
end
local a1, a2, a3, a4, a5, a6 = -39.69683028665376, 220.9460984245205, -275.9285104469687, 138.3577518672690, -30.66479806614716, 2.506628277459239
local b1, b2, b3, b4, b5 = -54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572
local c1, c2, c3, c4, c5, c6 = -0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783
local d1, d2, d3, d4 = 0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416
local p_low = 0.02425
local p_high = 1.0 - p_low
local q, r
if p < p_low then
q = math.sqrt(-2.0 * math.log(p))
return (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1.0)
elseif p <= p_high then
q = p - 0.5
r = q * q
return (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1.0)
else
q = math.sqrt(-2.0 * math.log(1.0 - p))
return -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1.0)
end
end
-- ==========================================
-- 3. 統計核心演算法實作
-- ==========================================
function S.mean_impl(data)
local elts = S.extract_list(data)
local n = #elts
if n < 1 then S.raise_err("mean requires at least one data point") end
local total = elts[1]
for i = 2, n do total = S.v_add(total, elts[i]) end
return S.v_div(total, n)
end
function S.fmean_impl(data, weights)
local elts = S.extract_list(data)
local n = #elts
if n < 1 then S.raise_err("fmean requires at least one data point") end
if S.is_none(weights) then
local sum = 0.0
for i = 1, n do sum = sum + S.to_float(elts[i]) end
return sum / n
else
local w_elts = S.extract_list(weights)
if #w_elts ~= n then S.raise_err("allbar weights must be the same length as data") end
local num, den = 0.0, 0.0
for i = 1, n do
local w = S.to_float(w_elts[i])
num = num + S.to_float(elts[i]) * w
den = den + w
end
if den <= 0.0 then S.raise_err("sum of weights must be greater than zero") end
return num / den
end
end
function S.geometric_mean_impl(data)
local elts = S.extract_list(data)
local n = #elts
if n < 1 then S.raise_err("geometric mean requires a non-empty dataset") end
local log_sum = 0.0
for i = 1, n do
local v = S.to_float(elts[i])
if v ~= v then return 0/0 end
if v <= 0.0 then S.raise_err("geometric mean requires positive inputs") end
log_sum = log_sum + math.log(v)
end
return math.exp(log_sum / n)
end
function S.harmonic_mean_impl(data, weights)
local elts = S.extract_list(data)
local n = #elts
if n < 1 then S.raise_err("harmonic_mean requires at least one data point") end
local w_elts = nil
if not S.is_none(weights) then
w_elts = S.extract_list(weights)
if #w_elts ~= n then S.raise_err("Number of weights does not match data size") end
end
local sum_w, sum_inv = 0.0, 0.0
local has_zero = false
for i = 1, n do
local x = S.to_float(elts[i])
if x < 0.0 then S.raise_err("harmonic mean does not support negative values") end
local w = w_elts and S.to_float(w_elts[i]) or 1.0
if w < 0.0 then S.raise_err("harmonic mean weights cannot be negative") end
if w > 0.0 then
sum_w = sum_w + w
if x == 0.0 then has_zero = true
elseif not has_zero then sum_inv = sum_inv + (w / x) end
end
end
if sum_w <= 0.0 then S.raise_err("sum of weights must be greater than zero") end
if has_zero then return 0 end
return sum_w / sum_inv
end
function S.median_impl(data)
local sorted = S.sort_list(S.extract_list(data))
local n = #sorted
if n == 0 then S.raise_err("no median for empty data") end
local mid = math.floor(n / 2)
if n % 2 == 1 then return sorted[mid + 1] end
return S.v_div(S.v_add(sorted[mid], sorted[mid + 1]), 2)
end
function S.median_low_impl(data)
local sorted = S.sort_list(S.extract_list(data))
local n = #sorted
if n == 0 then S.raise_err("no median for empty data") end
return sorted[math.floor((n + 1) / 2)]
end
function S.median_high_impl(data)
local sorted = S.sort_list(S.extract_list(data))
local n = #sorted
if n == 0 then S.raise_err("no median for empty data") end
return sorted[math.floor(n / 2) + 1]
end
function S.median_grouped_impl(data, interval)
local sorted = S.sort_list(S.extract_list(data))
local n = #sorted
if n == 0 then S.raise_err("no median for empty data") end
local intv = S.is_none(interval) and 1.0 or S.to_float(interval)
if n == 1 then return S.to_float(sorted[1]) end
local x = S.to_float(sorted[math.floor(n / 2) + 1])
local cf, f = 0, 0
for i = 1, n do
local v = S.to_float(sorted[i])
if v < x then cf = cf + 1
elseif v == x then f = f + 1 end
end
local L = x - intv / 2.0
return L + intv * (n / 2.0 - cf) / f
end
function S.multimode_impl(data)
local elts = S.extract_list(data)
if #elts == 0 then return { __type = "list", elements = {} } end
local EvalUtils = require("Module:PythonScript/Evaluator/Utils")
local counts, unique_vals = {}, {}
local max_c = 0
for i = 1, #elts do
local v = elts[i]
local found_idx = nil
for j = 1, #unique_vals do
if EvalUtils.py_eq(unique_vals[j], v) then found_idx = j; break end
end
if not found_idx then
table.insert(unique_vals, v)
found_idx = #unique_vals
counts[found_idx] = 0
end
local c = counts[found_idx] + 1
counts[found_idx] = c
if c > max_c then max_c = c end
end
local res = {}
for j = 1, #unique_vals do
if counts[j] == max_c then table.insert(res, unique_vals[j]) end
end
return { __type = "list", elements = res }
end
function S.mode_impl(data)
local mm = S.multimode_impl(data)
if #mm.elements == 0 then S.raise_err("no mode for empty data") end
return mm.elements[1]
end
function S.quantiles_impl(data, n, method)
n = S.is_none(n) and 4 or S.to_float(n)
method = S.is_none(method) and "exclusive" or method
if n < 1 or math.floor(n) ~= n then S.raise_err("n must be at least 1") end
local sorted = S.sort_list(S.extract_list(data))
local ld = #sorted
if ld < 2 then S.raise_err("must have at least two data points") end
local res = {}
if method == "inclusive" then
local m = ld - 1
for i = 1, n - 1 do
local j = math.floor(i * m / n)
local delta = (i * m) - (j * n)
local interpolated = (S.to_float(sorted[j + 1]) * (n - delta) + S.to_float(sorted[j + 2]) * delta) / n
table.insert(res, interpolated)
end
elseif method == "exclusive" then
local m = ld + 1
for i = 1, n - 1 do
local j = math.floor(i * m / n)
if j < 1 then j = 1 elseif j > ld - 1 then j = ld - 1 end
local delta = (i * m) - (j * n)
local interpolated = (S.to_float(sorted[j]) * (n - delta) + S.to_float(sorted[j + 1]) * delta) / n
table.insert(res, interpolated)
end
else
Runtime.raise("ValueError", "unknown method: " .. tostring(method))
end
return { __type = "list", elements = res }
end
function S.var_core(data, center, is_sample)
local elts = S.extract_list(data)
local n = #elts
if is_sample and n < 2 then S.raise_err("variance requires at least two data points") end
if not is_sample and n < 1 then S.raise_err("pvariance requires at least one data point") end
local m = S.is_none(center) and S.mean_impl(data) or center
local sum_sq = S.is_dec(m) and S.v_sub(m, m) or 0.0
for i = 1, n do
local diff = S.v_sub(elts[i], m)
sum_sq = S.v_add(sum_sq, S.v_mul(diff, diff))
end
return S.v_div(sum_sq, is_sample and (n - 1) or n)
end
function S.covariance_impl(x, y)
local ex, ey = S.extract_list(x), S.extract_list(y)
local n = #ex
if #ey ~= n then S.raise_err("covariance requires that both inputs have same number of data points") end
if n < 2 then S.raise_err("covariance requires at least two data points") end
local mx, my = S.fmean_impl(x), S.fmean_impl(y)
local sxy = 0.0
for i = 1, n do
sxy = sxy + (S.to_float(ex[i]) - mx) * (S.to_float(ey[i]) - my)
end
return sxy / (n - 1)
end
function S.correlation_impl(x, y)
local ex, ey = S.extract_list(x), S.extract_list(y)
local n = #ex
if #ey ~= n then S.raise_err("correlation requires that both inputs have same number of data points") end
if n < 2 then S.raise_err("correlation requires at least two data points") end
local mx, my = S.fmean_impl(x), S.fmean_impl(y)
local sxx, syy, sxy = 0.0, 0.0, 0.0
for i = 1, n do
local dx, dy = S.to_float(ex[i]) - mx, S.to_float(ey[i]) - my
sxx = sxx + dx * dx
syy = syy + dy * dy
sxy = sxy + dx * dy
end
if sxx == 0.0 or syy == 0.0 then S.raise_err("at least one of the inputs is constant") end
local r = sxy / math.sqrt(sxx * syy)
if r > 1.0 then r = 1.0 elseif r < -1.0 then r = -1.0 end
return r
end
function S.linear_regression_impl(x, y, proportional)
local ex, ey = S.extract_list(x), S.extract_list(y)
local n = #ex
if #ey ~= n then S.raise_err("linear regression requires that both inputs have same number of data points") end
if n < 2 then S.raise_err("linear regression requires at least two data points") end
local sxx, sxy, mx, my = 0.0, 0.0, 0.0, 0.0
if proportional then
for i = 1, n do
local xi, yi = S.to_float(ex[i]), S.to_float(ey[i])
sxx = sxx + xi * xi
sxy = sxy + xi * yi
end
else
mx, my = S.fmean_impl(x), S.fmean_impl(y)
for i = 1, n do
local dx, dy = S.to_float(ex[i]) - mx, S.to_float(ey[i]) - my
sxx = sxx + dx * dx
sxy = sxy + dx * dy
end
end
if sxx == 0.0 then S.raise_err("x is constant") end
local slope = sxy / sxx
local intercept = proportional and 0.0 or (my - slope * mx)
if not S.linreg_cls then
local col_mod = require("Module:PythonScript/Librarys").get_module("collections")
local nt_fn = PyClass.get_attr(col_mod, "namedtuple")
S.linreg_cls = nt_fn.__call({ "LinearRegression", { __type = "list", elements = { "slope", "intercept" } } })
end
return require("Module:PythonScript/Evaluator").invoke(S.linreg_cls, { slope, intercept }, {})
end
-- ==========================================
-- 4. 延遲初始化 StatisticsError、NormalDist 與模組字典
-- ==========================================
function S.make_normal_dist(mu, sigma)
S.ensure_init()
if sigma < 0.0 then S.raise_err("sigma must be non-negative") end
local inst = PyClass.make_instance(S.cache.NormalDist)
inst.__dict._is_normaldist = true
inst.__dict._mu = mu * 1.0
inst.__dict._sigma = sigma * 1.0
return inst
end
function S.is_normal_dist(v)
return type(v) == "table" and v.__type == "instance" and v.__dict and v.__dict._is_normaldist == true
end
function S.ensure_init()
if S.initialized then return end
S.initialized = true
local B = Runtime.builtins
local C = S.cache
C.StatisticsError = PyClass.make_class("StatisticsError", { B.ValueError }, {})
C.StatisticsError.__dict["__module__"] = "statistics"
-- NormalDist 類別定義
local function make_prop(fn)
return { __type = "property", fget = { __type = "builtin_method", __call = fn } }
end
local nd_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local mu = (a[2] ~= nil and a[2]) or (kw and kw.mu) or 0.0
local sigma = (a[3] ~= nil and a[3]) or (kw and kw.sigma) or 1.0
mu, sigma = S.to_float(mu), S.to_float(sigma)
if sigma < 0.0 then S.raise_err("sigma must be non-negative") end
self.__dict._is_normaldist = true
self.__dict._mu = mu
self.__dict._sigma = sigma
return nil
end },
mean = make_prop(function(a) return a[1].__dict._mu end),
median = make_prop(function(a) return a[1].__dict._mu end),
mode = make_prop(function(a) return a[1].__dict._mu end),
stdev = make_prop(function(a) return a[1].__dict._sigma end),
variance = make_prop(function(a) local s = a[1].__dict._sigma; return s * s end),
from_samples = { __type = "classmethod", __func = { __type = "builtin_method", __name = "from_samples", __call = function(a)
local cls, data = a[1], a[2]
local mu = S.fmean_impl(data)
local sigma = math.sqrt(S.to_float(S.var_core(data, mu, true)))
local inst = S.make_normal_dist(mu, sigma)
inst.__class = cls
return inst
end } },
samples = { __type = "builtin_method", __call = function(a, kw)
local self, n = a[1], S.to_float(a[2])
local seed = kw and kw.seed
local rand_mod = require("Module:PythonScript/Librarys/Random").get_module()
local Evaluator = require("Module:PythonScript/Evaluator")
local gauss_fn
if not S.is_none(seed) then
local rng = Evaluator.invoke(PyClass.get_attr(rand_mod, "Random"), {seed}, {})
gauss_fn = PyClass.get_attr(rng, "gauss")
else
gauss_fn = PyClass.get_attr(rand_mod, "gauss")
end
local mu, sigma = self.__dict._mu, self.__dict._sigma
local res = {}
for i = 1, n do
res[i] = Evaluator.invoke(gauss_fn, {mu, sigma}, {})
end
return { __type = "list", elements = res }
end },
pdf = { __type = "builtin_method", __call = function(a)
local self, x = a[1], S.to_float(a[2])
local mu, sigma = self.__dict._mu, self.__dict._sigma
if sigma == 0.0 then S.raise_err("pdf() not defined when sigma is zero") end
local z = (x - mu) / sigma
return math.exp(-0.5 * z * z) / (sigma * math.sqrt(2.0 * math.pi))
end },
cdf = { __type = "builtin_method", __call = function(a)
local self, x = a[1], S.to_float(a[2])
local mu, sigma = self.__dict._mu, self.__dict._sigma
if sigma == 0.0 then S.raise_err("cdf() not defined when sigma is zero") end
return 0.5 * (1.0 + S.erf((x - mu) / (sigma * math.sqrt(2.0))))
end },
inv_cdf = { __type = "builtin_method", __call = function(a)
local self, p = a[1], S.to_float(a[2])
return self.__dict._mu + self.__dict._sigma * S.std_inv_cdf(p)
end },
quantiles = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local n = (a[2] ~= nil and a[2]) or (kw and kw.n) or 4
n = S.to_float(n)
if n < 1 or math.floor(n) ~= n then S.raise_err("n must be at least 1") end
local mu, sigma = self.__dict._mu, self.__dict._sigma
local res = {}
for i = 1, n - 1 do
res[i] = mu + sigma * S.std_inv_cdf(i / n)
end
return { __type = "list", elements = res }
end },
zscore = { __type = "builtin_method", __call = function(a)
local self, x = a[1], S.to_float(a[2])
local mu, sigma = self.__dict._mu, self.__dict._sigma
if sigma == 0.0 then S.raise_err("zscore() not defined when sigma is zero") end
return (x - mu) / sigma
end },
overlap = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if not S.is_normal_dist(other) then Runtime.raise("TypeError", "Expected another NormalDist instance") end
local x1, y1 = self.__dict._mu, self.__dict._sigma
local x2, y2 = other.__dict._mu, other.__dict._sigma
if y1 == 0.0 or y2 == 0.0 then S.raise_err("overlap() not defined when sigma is zero") end
if y2 < y1 then x1, y1, x2, y2 = x2, y2, x1, y1 end
local dv = y1 * y1 - y2 * y2
local dm = math.abs(x2 - x1)
if dv == 0.0 then
return 1.0 - S.erf(dm / (2.0 * y1 * math.sqrt(2.0)))
end
local a_val = x1 * y2 * y2 - x2 * y1 * y1
local b_val = y1 * y2 * math.sqrt(dm * dm + dv * math.log((y1 * y1) / (y2 * y2)))
local pt1, pt2 = (a_val + b_val) / dv, (a_val - b_val) / dv
local cdf1 = function(v) return 0.5 * (1.0 + S.erf((v - x1) / (y1 * math.sqrt(2.0)))) end
local cdf2 = function(v) return 0.5 * (1.0 + S.erf((v - x2) / (y2 * math.sqrt(2.0)))) end
return 1.0 - (math.abs(cdf2(pt1) - cdf1(pt1)) + math.abs(cdf2(pt2) - cdf1(pt2)))
end },
-- 運算子多載
__add__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if S.is_normal_dist(other) then
local s1, s2 = self.__dict._sigma, other.__dict._sigma
return S.make_normal_dist(self.__dict._mu + other.__dict._mu, math.sqrt(s1 * s1 + s2 * s2))
end
return S.make_normal_dist(self.__dict._mu + S.to_float(other), self.__dict._sigma)
end },
__radd__ = { __type = "builtin_method", __call = function(a)
return S.make_normal_dist(S.to_float(a[2]) + a[1].__dict._mu, a[1].__dict._sigma)
end },
__sub__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if S.is_normal_dist(other) then
local s1, s2 = self.__dict._sigma, other.__dict._sigma
return S.make_normal_dist(self.__dict._mu - other.__dict._mu, math.sqrt(s1 * s1 + s2 * s2))
end
return S.make_normal_dist(self.__dict._mu - S.to_float(other), self.__dict._sigma)
end },
__rsub__ = { __type = "builtin_method", __call = function(a)
return S.make_normal_dist(S.to_float(a[2]) - a[1].__dict._mu, a[1].__dict._sigma)
end },
__mul__ = { __type = "builtin_method", __call = function(a)
local c = S.to_float(a[2])
return S.make_normal_dist(a[1].__dict._mu * c, a[1].__dict._sigma * math.abs(c))
end },
__truediv__ = { __type = "builtin_method", __call = function(a)
local c = S.to_float(a[2])
if c == 0.0 then Runtime.raise("ZeroDivisionError", "NormalDist division by zero") end
return S.make_normal_dist(a[1].__dict._mu / c, a[1].__dict._sigma / math.abs(c))
end },
__pos__ = { __type = "builtin_method", __call = function(a)
return S.make_normal_dist(a[1].__dict._mu, a[1].__dict._sigma)
end },
__neg__ = { __type = "builtin_method", __call = function(a)
return S.make_normal_dist(-a[1].__dict._mu, a[1].__dict._sigma)
end },
__eq__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
if not S.is_normal_dist(other) then return false end
return self.__dict._mu == other.__dict._mu and self.__dict._sigma == other.__dict._sigma
end },
__hash__ = { __type = "builtin_method", __call = function(a)
return B.hash({ { __type = "tuple", elements = { a[1].__dict._mu, a[1].__dict._sigma } } })
end },
__repr__ = { __type = "builtin_method", __call = function(a)
return string.format("NormalDist(mu=%s, sigma=%s)", B.repr({a[1].__dict._mu}), B.repr({a[1].__dict._sigma}))
end }
}
nd_dict.__rmul__ = nd_dict.__mul__
nd_dict.__str__ = nd_dict.__repr__
C.NormalDist = PyClass.make_class("NormalDist", {}, nd_dict)
C.NormalDist.__dict["__module__"] = "statistics"
-- 模組層級函式包裝
local function wrap(name, fn)
C[name] = { __type = "builtin_method", __name = name, __call = fn }
end
wrap("mean", function(a) return S.mean_impl(a[1]) end)
wrap("fmean", function(a, kw) return S.fmean_impl(a[1], (a[2] ~= nil and a[2]) or (kw and kw.weights)) end)
wrap("geometric_mean", function(a) return S.geometric_mean_impl(a[1]) end)
wrap("harmonic_mean", function(a, kw) return S.harmonic_mean_impl(a[1], (a[2] ~= nil and a[2]) or (kw and kw.weights)) end)
wrap("median", function(a) return S.median_impl(a[1]) end)
wrap("median_low", function(a) return S.median_low_impl(a[1]) end)
wrap("median_high", function(a) return S.median_high_impl(a[1]) end)
wrap("median_grouped", function(a, kw) return S.median_grouped_impl(a[1], (a[2] ~= nil and a[2]) or (kw and kw.interval)) end)
wrap("mode", function(a) return S.mode_impl(a[1]) end)
wrap("multimode", function(a) return S.multimode_impl(a[1]) end)
wrap("quantiles", function(a, kw)
local n = (a[2] ~= nil and a[2]) or (kw and kw.n) or 4
local method = (a[3] ~= nil and a[3]) or (kw and kw.method) or "exclusive"
return S.quantiles_impl(a[1], n, method)
end)
wrap("pvariance", function(a, kw) return S.var_core(a[1], (a[2] ~= nil and a[2]) or (kw and kw.mu), false) end)
wrap("pstdev", function(a, kw) return S.v_sqrt(S.var_core(a[1], (a[2] ~= nil and a[2]) or (kw and kw.mu), false)) end)
wrap("variance", function(a, kw) return S.var_core(a[1], (a[2] ~= nil and a[2]) or (kw and kw.xbar), true) end)
wrap("stdev", function(a, kw) return S.v_sqrt(S.var_core(a[1], (a[2] ~= nil and a[2]) or (kw and kw.xbar), true)) end)
wrap("covariance", function(a) return S.covariance_impl(a[1], a[2]) end)
wrap("correlation", function(a) return S.correlation_impl(a[1], a[2]) end)
wrap("linear_regression", function(a, kw)
local prop = (a[3] ~= nil and a[3]) or (kw and kw.proportional) or false
return S.linear_regression_impl(a[1], a[2], B.bool.__call({prop}))
end)
end
-- ==========================================
-- 5. 模組匯出介面 (Lazy Loading)
-- ==========================================
function StatisticsMod.get_module()
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
S.ensure_init()
local val = S.cache[attr]
if val ~= nil then
self.__dict[attr] = val
return val
end
Runtime.raise("AttributeError", "module 'statistics' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "statistics"
mod.__dict["__all__"] = {
__type = "list",
elements = {
"NormalDist", "StatisticsError",
"fmean", "geometric_mean", "harmonic_mean", "mean",
"median", "median_grouped", "median_high", "median_low",
"mode", "multimode", "pstdev", "pvariance", "quantiles",
"stdev", "variance", "covariance", "correlation", "linear_regression"
}
}
return mod
end
return StatisticsMod