-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathstats.lua
More file actions
188 lines (159 loc) · 5.69 KB
/
stats.lua
File metadata and controls
188 lines (159 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
-- Stats utility functions for captain
local stats = {}
---Calculate percentile from a sorted array
---@param sorted_array number[] -- The already sorted array of values
---@param p number -- The percentile to calculate (0-100)
---@return number -- The value at the specified percentile
stats.percentile = function(sorted_array, p)
if #sorted_array == 0 then
return 0
end
local index = math.ceil(#sorted_array * (p / 100))
return sorted_array[math.min(index, #sorted_array)]
end
---Calculate the most common value in a table (mode)
---@param values number[] -- Array of values
---@param roundingPrecision number? -- Optional rounding precision to group similar values
---@return number? -- The most frequently occurring value, or nil if values is empty
---@return number? -- The number of times the most common value appears
stats.mode = function(values, roundingPrecision)
if not values or #values == 0 then
return nil
end
-- Count occurrences of each value (with optional rounding)
local counts = {}
for _, value in ipairs(values) do
local roundedValue = value
if roundingPrecision then
-- Round to the specified precision
roundedValue = math.floor(value / roundingPrecision) * roundingPrecision
end
counts[roundedValue] = (counts[roundedValue] or 0) + 1
end
-- Find the most common value
local mostCommonValue = nil
local highestCount = 0
for value, count in pairs(counts) do
if count > highestCount then
mostCommonValue = value
highestCount = count
end
end
return mostCommonValue, highestCount
end
---Calculate frequency distribution as percentages
---@param values table -- Table of values where the key is the category and value is the count
---@param minThreshold number? -- Optional minimum threshold to include in results (0-100)
---@param filter function? -- Optional filter function(key, percentage) returning true to include
---@return table -- Table of {key = percentage} pairs
stats.distribution = function(values, minThreshold, filter)
local result = {}
local total = 0
-- Calculate total
for _, count in pairs(values) do
total = total + count
end
if total == 0 then
return result
end
-- Calculate percentages
for key, count in pairs(values) do
local percentage = (count / total) * 100
-- Apply min threshold if provided
if not minThreshold or percentage >= minThreshold then
-- Apply filter if provided
if not filter or filter(key, percentage) then
result[key] = percentage
end
end
end
return result
end
---Calculate standard deviation
---@param values number[] -- Array of values
---@param mean number -- The mean/average of the values
---@return number -- The standard deviation
stats.stddev = function(values, mean)
if #values <= 1 then
return 0
end
local sum_sq_diff = 0
for _, value in ipairs(values) do
local diff = value - mean
sum_sq_diff = sum_sq_diff + (diff * diff)
end
return math.sqrt(sum_sq_diff / (#values - 1))
end
---Calculate the median value
---@param sorted_values number[] -- The already sorted array of values
---@return number -- The median value
stats.median = function(sorted_values)
if #sorted_values == 0 then
return 0
end
local mid = math.floor(#sorted_values / 2) + 1
if #sorted_values % 2 == 0 then
return (sorted_values[mid - 1] + sorted_values[mid]) / 2
else
return sorted_values[mid]
end
end
---Calculate Wald confidence interval for a proportion
---@param successes number -- Number of successes (e.g., hits)
---@param trials number -- Total number of trials (e.g., attacks)
---@param z number? -- Z-score for confidence level (default 1.96 for 95% CI)
---@return number -- Lower bound of the confidence interval
---@return number -- Upper bound of the confidence interval
---@return number -- Point estimate (proportion)
stats.waldCI = function(successes, trials, z)
if trials == 0 then
return 0, 0, 0
end
z = z or 1.96
local p = successes / trials
local se = math.sqrt((p * (1 - p)) / trials)
local lower = math.max(0, p - z * se)
local upper = math.min(1, p + z * se)
return lower, upper, p
end
---Calculate comprehensive statistics from an array of values
---@param values number[] -- Array of values to analyze
---@return table -- A table containing min, max, percentiles, average, median and standard deviation
stats.calculate = function(values)
if #values == 0 then
return
{
min = 0,
max = 0,
p90 = 0,
p95 = 0,
p99 = 0,
avg = 0,
median = 0,
stddev = 0,
}
end
-- Sort the values for percentile calculations
local sorted = {}
for i, v in ipairs(values) do
sorted[i] = v
end
table.sort(sorted)
local sum = 0
for _, value in ipairs(values) do
sum = sum + value
end
local mean = sum / #values
return
{
min = sorted[1],
max = sorted[#sorted],
p90 = stats.percentile(sorted, 90),
p95 = stats.percentile(sorted, 95),
p99 = stats.percentile(sorted, 99),
avg = mean,
median = stats.median(sorted),
stddev = stats.stddev(values, mean),
}
end
return stats