├── README.md ├── common.lua ├── distributions ├── bernoulli.lua ├── exponential.lua ├── laplace.lua ├── logistic.lua ├── pareto.lua └── uniform.lua ├── example.lua └── stats.lua /README.md: -------------------------------------------------------------------------------- 1 | # luastats 2 | Statistics for lua 3 | 4 | ### Installation 5 | Clone the repo and put it in your project. 6 | 7 | ### Example 8 | ```lua 9 | local s = require("stats") 10 | local b = s.bernoulli.new(.3) 11 | print(b:pmf(0)) --> .7 12 | print(b:random()) --> 1 or 0 13 | ``` 14 | Or fit a random variable from data: 15 | ```lua 16 | local s = require("stats") 17 | local bernoulli_data = {1,1,1,0} 18 | local b = s.bernoulli.fit(bernoulli_data) 19 | print(b:pmf(1)) --> .75 20 | ``` 21 | ### Features 22 | ##### General 23 | - Compute the PMF/PDF, CDF, and Quantile of a distribution 24 | - Fit data to a distribution using the MLE 25 | 26 | ##### Distributions 27 | - uniform 28 | - pareto 29 | - laplace 30 | - exponential 31 | - bernoulli 32 | -------------------------------------------------------------------------------- /common.lua: -------------------------------------------------------------------------------- 1 | local stats = {} 2 | stats.__index = stats 3 | -- 4 | --seed random 5 | math.randomseed(os.clock()*10^20/729351) 6 | -- on some OSs the first number may not be random 7 | math.random() 8 | 9 | -- each distribution implements this 10 | function stats.random(dist) 11 | return dist.quantile(dist, math.random()) 12 | end 13 | 14 | function stats.average(data) 15 | return stats.sum(data)/#data 16 | end 17 | 18 | function stats.sum(data) 19 | total = 0 20 | for i = 1, #data do 21 | total = total + data[i] 22 | end 23 | return total 24 | end 25 | 26 | function stats.median(data) 27 | table.sort(data) 28 | if #data % 2 == 1 then 29 | return data[math.ceil(#data/2)] 30 | else 31 | return (data[#data/2]+data[1+(#data/2)])/2 32 | end 33 | end 34 | 35 | function stats.counter(data) 36 | local cnt = {} 37 | for i = 1, #data do 38 | if cnt[data[i]] == nil then 39 | cnt[data[i]] = 1 40 | else 41 | cnt[data[i]] = cnt[data[i]]+1 42 | end 43 | end 44 | return cnt 45 | end 46 | 47 | -- returns the last max if there are ties 48 | function stats.mode(data) 49 | local cnt = {} 50 | local max = {nil, 0} 51 | for i = 1, #data do 52 | if cnt[data[i]] == nil then 53 | cnt[data[i]] = 1 54 | else 55 | cnt[data[i]] = cnt[data[i]]+1 56 | end 57 | if cnt[data[i]] > max[2] then 58 | max = {data[i], cnt[data[i]]} 59 | end 60 | end 61 | return max 62 | end 63 | 64 | return stats 65 | -------------------------------------------------------------------------------- /distributions/bernoulli.lua: -------------------------------------------------------------------------------- 1 | stats = require("common") 2 | local bernoulli = {} 3 | bernoulli.__index = bernoulli 4 | 5 | function bernoulli.new(p) 6 | local self = setmetatable({}, bernoulli ) 7 | self.p = p 8 | return self 9 | end 10 | 11 | function bernoulli.fit(data) 12 | return bernoulli.new(stats.average(data)) 13 | end 14 | 15 | function bernoulli.pmf(self, k) 16 | if k == 0 then 17 | return 1 - self.p 18 | elseif k == 1 then 19 | return self.p 20 | else 21 | return -1 22 | end 23 | end 24 | 25 | function bernoulli.cdf(self, k) 26 | if k < 0 then 27 | return 0 28 | elseif k < 1 then 29 | return 1 - self.p 30 | elseif k >= 1 then 31 | return 1 32 | else 33 | return -1 34 | end 35 | end 36 | 37 | function bernoulli.quantile(self, x) 38 | if x >= 0 and x <= 1 then 39 | if x < 1 - self.p then 40 | return 0 41 | elseif x >= 1 - self.p then 42 | return 1 43 | end 44 | else 45 | return -1 46 | end 47 | end 48 | 49 | function bernoulli.random(self) 50 | return stats.random(self) 51 | --return self.quantile(self, math.random()) 52 | end 53 | 54 | return bernoulli 55 | -------------------------------------------------------------------------------- /distributions/exponential.lua: -------------------------------------------------------------------------------- 1 | stats = require("common") 2 | local exponential = {} 3 | exponential.__index = exponential 4 | 5 | function exponential.new(l) 6 | local self = setmetatable({}, exponential ) 7 | self.l = l 8 | return self 9 | end 10 | 11 | function exponential.fit(data) 12 | return exponential.new(1/stats.average(data)) 13 | end 14 | 15 | function exponential.pdf(self, x) 16 | return self.l*math.exp(-self.l*x) 17 | end 18 | 19 | function exponential.cdf(self, x) 20 | return 1 - math.exp(-self.l*x) 21 | end 22 | 23 | function exponential.quantile(self, p) 24 | return -math.log(1-p)/self.l 25 | end 26 | 27 | function exponential.random(self) 28 | return stats.random(self) 29 | end 30 | 31 | return exponential 32 | -------------------------------------------------------------------------------- /distributions/laplace.lua: -------------------------------------------------------------------------------- 1 | stats = require("common") 2 | local laplace = {} 3 | laplace.__index = laplace 4 | 5 | function laplace.new(m, b) 6 | local self = setmetatable({}, laplace ) 7 | self.m = m 8 | self.b = b 9 | return self 10 | end 11 | 12 | function laplace.fit(data) 13 | p = stats.median(data) 14 | total = 0 15 | for i = 1,#data do 16 | total = total + math.abs(data[i] - p) 17 | end 18 | return laplace.new(p, self.b/#data) 19 | end 20 | 21 | function laplace.pdf(self, x) 22 | return math.exp(-math.abs(x-self.m)/self.b)/(2*self.b) 23 | end 24 | 25 | function laplace.cdf(self, x) 26 | if x < self.m then 27 | return math.exp((x-self.m)/self.b)/2 28 | elseif x >= self.m then 29 | return 1-(math.exp((self.m-x)/self.b)/2) 30 | end 31 | end 32 | 33 | function laplace.quantile(self, x) 34 | if x>0 and x<=1/2 then 35 | return self.m + self.b*math.log(2*x); 36 | elseif x>=1/2 and x<1 then 37 | return self.m - self.b*math.log(2*(1-x)); 38 | end 39 | end 40 | 41 | function laplace.random(self) 42 | return stats.random(self) 43 | end 44 | 45 | return laplace 46 | -------------------------------------------------------------------------------- /distributions/logistic.lua: -------------------------------------------------------------------------------- 1 | stats = require("common") 2 | local logistic = {} 3 | logistic.__index = logistic 4 | 5 | function logistic.new(m, s) 6 | local self = setmetatable({}, logistic) 7 | self.m = m 8 | if s <= 0 then 9 | error("scale must be greater than 0") 10 | return 11 | end 12 | self.s = s 13 | return self 14 | end 15 | 16 | function logistic.pdf(self, x) 17 | term = math.exp(-(x-self.m)/self.s) 18 | return term/(self.s*(math.pow(1+term,2))) 19 | end 20 | 21 | function logistic.cdf(self, x) 22 | return 1/(1+math.exp(-(x-self.m)/self.s)) 23 | end 24 | 25 | function logistic.quantile(self, p) 26 | return self.m + self.s*math.log(p/(1-p)) 27 | end 28 | 29 | function logistic.random(self) 30 | return stats.random(self) 31 | end 32 | 33 | return logistic 34 | -------------------------------------------------------------------------------- /distributions/pareto.lua: -------------------------------------------------------------------------------- 1 | stats = require("common") 2 | local pareto = {} 3 | pareto.__index = pareto 4 | 5 | function pareto.new(x,a) 6 | local self = setmetatable({}, pareto ) 7 | self.x = x 8 | self.a = a 9 | return self 10 | end 11 | 12 | function pareto.fit(data) 13 | local x = math.min(data) 14 | local total = 0 15 | for i=1,#data do 16 | total = total + math.log(data[i])-math.log(x) 17 | end 18 | local a = #data/total 19 | return pareto.new(x,a) 20 | end 21 | 22 | function pareto.pdf(self, x) 23 | return self.a*math.pow(self.x,self.a)/math.pow(x,self.a+1) 24 | end 25 | 26 | function pareto.cdf(self, x) 27 | if x self.a) and (x < self.b) then 20 | return 1/(self.b-self.a) 21 | else 22 | return 0 23 | end 24 | end 25 | 26 | function uniform.cdf(self, x) 27 | if x < self.a then 28 | return 0 29 | elseif x < self.b then 30 | return (x - self.a)/(self.b - self.a) 31 | elseif x > self.b then 32 | return 1 33 | else 34 | return -1 35 | end 36 | end 37 | 38 | function uniform.quantile(self, p) 39 | return p*(self.b-self.a) + self.a 40 | end 41 | 42 | function uniform.random(self) 43 | return stats.random(self) 44 | end 45 | 46 | return uniform 47 | -------------------------------------------------------------------------------- /example.lua: -------------------------------------------------------------------------------- 1 | local bernoulli = require("distributions/bernoulli") 2 | local stats = require("common") 3 | local s = require("stats") 4 | local laplace = require("distributions/laplace") 5 | local i = s.bernoulli.new(.5) 6 | local p = s.pareto.new(5,.5) 7 | print(p:random()) --> .5 8 | print(i:pmf(1)) --> .5 9 | local l = s.laplace.new(0,1) 10 | print(l:pdf(0)) --> .5 11 | print(l:random()) --> .5 12 | --print(i:quantile(.4)) --> 0 13 | print(i:random()) 14 | local array = {4,2,3,1} 15 | print(stats.average(array)) --> 2.5 16 | print(stats.median(array)) --> 2.5 17 | print(stats.sum(array)) --> 10 18 | local bernoulli_data = {1,1,1,0} 19 | local b = bernoulli.fit(bernoulli_data) 20 | print(b:pmf(1)) --> .75 21 | local mode_array = {4,4,2,3,3,1} 22 | local mode = s.common.mode(mode_array) 23 | for i = 1, #mode do 24 | print(mode[i]) 25 | end 26 | -------------------------------------------------------------------------------- /stats.lua: -------------------------------------------------------------------------------- 1 | local stats = {} 2 | stats.__index = stats 3 | stats['bernoulli']=require("distributions/bernoulli") 4 | stats['laplace']=require("distributions/laplace") 5 | stats['exponential']=require("distributions/exponential") 6 | stats['pareto']=require("distributions/pareto") 7 | stats['uniform']=require("distributions/uniform") 8 | stats['common']=require("common") 9 | return stats 10 | --------------------------------------------------------------------------------