statistics.rb 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d48404847c10c54ef090f41c5dc470e3ca6409ab07918828b6181e54836bc22b
4
- data.tar.gz: 42f80e662e6834081f993c168e96e75451be5a83106bbe8ebe0a2260a47f84c1
3
+ metadata.gz: 9db0156fb926432eb2d48821c410a0badec96b32dacd485893b0bdf8e7b25cb5
4
+ data.tar.gz: 77ac6c90e08b0017d1f66ad427ed0c5f0560ef1732fa79627bb0a2160eea292e
5
5
  SHA512:
6
- metadata.gz: eca8442077f05a25a936475b44c5d6cc75b6a1f009ec1ec15e4803d4b43727c554b4036a1c3ae44e674acacb4c5c0d4605aca24783998cdd13962a86bca887f5
7
- data.tar.gz: 14e9b21b4db384c2bd01bed96d7889c88949fb45a1e2c352df721d9af974ff1daf91e020bcac211b6f94639aafd3d63a16ac3d9a0d27f38f65ce87ade157cefd
6
+ metadata.gz: 43cf596e2166e2cd345528dd6aae8aaadcf78e9bfe62cfe5f28903729bca2ae60a9b142796ab6ab699da57754b27480c4b7c07dae81c37f569f244064daa05f1
7
+ data.tar.gz: e0f26dd100a0741855f46b466b5c9c28a673f386c674a65feafe2cf5793f7353d4791dc54e1e4f1c95f2200724caefcbcc7901b71a830743f7cb97f653ba3692
data/CHANGELOG.md ADDED
@@ -0,0 +1,117 @@
1
+ # CHANGELOG
2
+
3
+ ## [0.6.1] - 20260513
4
+ ## Statistics::Histogram::Bin --> Statistics::Bin
5
+
6
+ 1. lib/Statistics/Histogram/Bin.rb --> lib/Statistics/Bin.rb
7
+ 2. test/Statistics/Histogram/Bin\_test.rb --> test/Statistics/Bin\_test.rb
8
+ 3. ~ lib/Statistics/Histogram.rb: /require\_relative './Histogram/Bin'/require\_relative './Bin'/
9
+ 4. ~ lib/statistics.rb: + require\_relative './Statistics/Bin'
10
+ 5. ~ .gitignore: Add standard contents.
11
+ 6. ~ README.md: + Contributions section
12
+ 7. ~ lib/Statistics/VERSION.rb: /0.6.0/0.6.1/
13
+
14
+ ## [0.6.0] - 20260504
15
+ ## + Percentile, StandardDeviation, IQR
16
+
17
+ 1. + lib/Statistics/Percentile.rb
18
+ 2. + lib/Statistics/StandardDeviation.rb
19
+ 3. + lib/Statistics/IQR.rb
20
+ 4. + test/Statistics/Percentile\_test.rb
21
+ 5. + test/Statistics/StandardDeviation\_test.rb
22
+ 6. + test/Statistics/IQR\_test.rb
23
+ 7. - Gemfile: As there are no dependencies it isn't doing anything!
24
+ 8. ~ statistics.rb.gemspec: dependencies=() also therefore not used.
25
+ 9. /CHANGELOG/CHANGELOG.md/
26
+
27
+
28
+ ## [0.5.0] - 20260503
29
+ ## + Bin.bin\_for\_value, Bin split, gem scaffolding.
30
+
31
+ 1. + Bin.bin\_for\_value (wraps index\_for\_value, now private)
32
+ 2. ~ Histogram#allocate\_values: Use Bin.bin\_for\_value
33
+ 3. ~ Bin split out to lib/Statistics/Histogram/Bin.rb
34
+ 4. + lib/statistics.rb
35
+ 5. + lib/Statistics/VERSION.rb
36
+ 6. + statistics.rb.gemspec
37
+ 7. + Gemfile
38
+ 8. + Rakefile
39
+ 9. + README.md
40
+ 10. + LICENSE
41
+ 11. + test/Statistics/Histogram\_test.rb
42
+ 12. + test/Statistics/Histogram/Bin\_test.rb
43
+
44
+
45
+ ## [0.4.0] - 20260502
46
+ ## + Bin instances; consolidated class methods.
47
+
48
+ 1. + Bin instances (count-tracking via increment, attr\_reader :interval)
49
+ 2. ~ Bin.width: handles bin\_width, bin\_count, zero-range, and method selection
50
+ 3. + Bin.boundaries (from 0.3.0)
51
+ 4. + Bin.index\_for\_value (was Histogram#index\_for\_value)
52
+ 5. + Bin.data\_range
53
+ 6. ~ class << self for class methods with private boundary
54
+ 7. ~ attr\_reader :count replaces hand-written method
55
+
56
+
57
+ ## [0.3.0] - 20260417
58
+ ## Hash-based bins; Bin as class-methods-only.
59
+
60
+ 1. + Bin.boundaries class method
61
+ 2. ~ initialize: delegates to Bin.width and Bin.boundaries
62
+ 3. ~ Bin: hash-based bins (no Bin instances), Bin is class-methods-only
63
+ 4. - index\_for\_value (inlined back into allocate\_values)
64
+ 5. - determine\_bin\_width (replaced by Bin.width delegation)
65
+ 6. - zero-range guard
66
+
67
+
68
+ ## [0.2.0] - 20260417
69
+ ## Swap storing values per bin for counting per bin; interval; zero-range guard.
70
+
71
+ 1. ~ Bin: count-tracking via increment instead of storing values in array
72
+ 2. ~ Bin: attr\_reader :interval instead of :range
73
+ 3. + Bin#empty? checks @count == 0 instead of @values.empty?
74
+ 4. + determine\_bin\_width: zero-range guard
75
+ 5. + index\_for\_value extracted from allocate\_values
76
+
77
+
78
+ ## [0.1.1] - 20260417
79
+ ## + Statistics::Histogram#determine\_bin\_width
80
+
81
+ 1. ~ initialize: extracted determine\_bin\_width from one-liner
82
+ 2. /compute\_boundaries/calculate\_boundaries/
83
+
84
+
85
+ ## [0.1.0] - 20260417
86
+ ## + Statistics::Histogram::Bin
87
+
88
+ 1. + Statistics::Histogram::Bin
89
+ 2. ~ allocate\_values: creates Bin instances instead of hash entries
90
+
91
+
92
+ ## [0.0.0] - 20260417
93
+ ## + Statistics::Histogram
94
+
95
+ 1. + lib/Statistics/Histogram.rb
96
+ 2. - lib/BinWidth.rb
97
+
98
+
99
+ ## [pre-0.0.0] - 20260416
100
+ ## ~ BinWidth: Reintroduce binning strategies; extra guard clauses.
101
+
102
+ 1. Reintroduce all named strategies.
103
+ 2. /0.1.0/0.2.0/
104
+
105
+
106
+ ## [pre-0.0.0] - 20260416
107
+ ## + BinWidth#tuneable_root
108
+
109
+ 1. + tuneable\_root as the general form
110
+ 2. ~ square\_root rewritten as range * n^(-1/2) form
111
+ 3. /0.0.0/0.1.0/
112
+
113
+
114
+ ## [pre-0.0.0] - 20260416
115
+ ## + BinWidth
116
+
117
+ 1. + lib/BinWidth.rb
data/README.md CHANGED
@@ -68,13 +68,47 @@ h.bins.first.width # => 2.57
68
68
  h.bins.first.empty? # => false
69
69
  ```
70
70
 
71
+ ### Percentile
72
+
73
+ These methods employ linear interpolation. See Hyndman and Fan method 7.
74
+
75
+ ```ruby
76
+ require 'statistics.rb'
77
+
78
+ values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
79
+
80
+ Statistics::Percentile.of(values, 75) # => 7.75
81
+ Statistics::Percentile.q25(values) # => 3.25
82
+ Statistics::Percentile.q75(values) # => 7.75
83
+ ```
84
+
85
+ ### Standard Deviation
86
+
87
+ ```ruby
88
+ Statistics::StandardDeviation.of(values) # => population (default)
89
+ Statistics::StandardDeviation.of(values, sample: true) # => sample (Bessel's correction)
90
+ ```
91
+
92
+ ### Interquartile Range
93
+
94
+ ```ruby
95
+ Statistics::IQR.of(values) # => 4.5
96
+ ```
97
+
71
98
  ## Roadmap
72
99
 
73
100
  - Optional per-bin value storage
74
101
  - Additional bin width methods (Freedman-Diaconis, Scott, Sturges, cube root, tuneable root)
75
- - Composable statistical primitives (Percentile, StandardDeviation, IQR)
76
102
  - Aligned/neat bin boundaries
77
103
 
104
+ ## Contributing
105
+
106
+ 1. Fork it [https://github.com/thoran/statistics/fork](https://github.com/thoran/statistics/fork)
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create a new pull request
111
+
78
112
  ## License
79
113
 
80
114
  MIT
@@ -0,0 +1,74 @@
1
+ # Statistics/Bin.rb
2
+ # Statistics::Bin
3
+
4
+ module Statistics
5
+ class Bin
6
+ class << self
7
+ def width(values, bin_width: nil, bin_count: nil, method: :square_root)
8
+ if bin_width
9
+ bin_width
10
+ elsif bin_count
11
+ data_range(values) / bin_count.to_f
12
+ elsif data_range(values) == 0
13
+ 1.0
14
+ else
15
+ send("#{method}_width", values)
16
+ end
17
+ end
18
+
19
+ def count(values, method: :square_root)
20
+ send("#{method}_count", values)
21
+ end
22
+
23
+ def data_range(values)
24
+ values.last - values.first
25
+ end
26
+
27
+ def boundaries(values, bin_width: nil, bin_count: nil, method: :square_root)
28
+ w = bin_width || width(values, bin_count: bin_count, method: method)
29
+ values.first.step(to: values.last + w, by: w).to_a
30
+ end
31
+
32
+ def bin_for_value(value, bins, bottom_boundary, bin_width)
33
+ index = index_for_value(value, bins.count, bottom_boundary, bin_width)
34
+ bins[index]
35
+ end
36
+
37
+ private
38
+
39
+ def index_for_value(value, bin_count, bottom_boundary, bin_width)
40
+ i = ((value - bottom_boundary) / bin_width).floor
41
+ i >= bin_count ? bin_count - 1 : i
42
+ end
43
+
44
+ def square_root_width(values)
45
+ data_range(values) * values.size ** (-1.0 / 2)
46
+ end
47
+
48
+ def square_root_count(values)
49
+ Math.sqrt(values.size).ceil
50
+ end
51
+ end # class << self
52
+
53
+ attr_reader :count, :interval
54
+
55
+ def increment
56
+ @count += 1
57
+ end
58
+
59
+ def width
60
+ @interval.end - @interval.begin
61
+ end
62
+
63
+ def empty?
64
+ @count == 0
65
+ end
66
+
67
+ private
68
+
69
+ def initialize(interval)
70
+ @interval = interval
71
+ @count = 0
72
+ end
73
+ end
74
+ end
@@ -1,7 +1,7 @@
1
1
  # Statistics/Histogram.rb
2
2
  # Statistics::Histogram
3
3
 
4
- require_relative './Histogram/Bin'
4
+ require_relative './Bin'
5
5
 
6
6
  module Statistics
7
7
  class Histogram
@@ -0,0 +1,14 @@
1
+ # Statistics/IQR.rb
2
+ # Statistics::IQR
3
+
4
+ require_relative './Percentile'
5
+
6
+ module Statistics
7
+ module IQR
8
+ module_function
9
+
10
+ def of(values)
11
+ Percentile.q75(values) - Percentile.q25(values)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ # Statistics/Percentile.rb
2
+ # Statistics::Percentile
3
+
4
+ # These methods employ linear interpolation. See Hyndman and Fan method 7.
5
+
6
+ module Statistics
7
+ module Percentile
8
+ module_function
9
+
10
+ def of(values, p)
11
+ sorted = values.map(&:to_f).sort
12
+ k = (p / 100.0) * (sorted.size - 1)
13
+ lower = sorted[k.floor]
14
+ upper = sorted[k.ceil]
15
+ lower + (upper - lower) * (k - k.floor)
16
+ end
17
+
18
+ def q25(values)
19
+ of(values, 25)
20
+ end
21
+
22
+ def q75(values)
23
+ of(values, 75)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # Statistics/StandardDeviation.rb
2
+ # Statistics::StandardDeviation
3
+
4
+ module Statistics
5
+ module StandardDeviation
6
+ module_function
7
+
8
+ def of(values, sample: false)
9
+ floats = values.map(&:to_f)
10
+ mean = floats.sum / floats.size
11
+ denominator = sample ? floats.size - 1 : floats.size
12
+ variance = floats.map{|v| (v - mean) ** 2}.sum / denominator
13
+ Math.sqrt(variance)
14
+ end
15
+ end
16
+ end
@@ -2,5 +2,5 @@
2
2
  # Statistics::VERSION
3
3
 
4
4
  module Statistics
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.1'
6
6
  end
data/lib/statistics.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  # statistics.rb
2
2
 
3
- require_relative './Statistics/VERSION'
3
+ require_relative './Statistics/Bin'
4
4
  require_relative './Statistics/Histogram'
5
+ require_relative './Statistics/IQR'
6
+ require_relative './Statistics/Percentile'
7
+ require_relative './Statistics/StandardDeviation'
8
+ require_relative './Statistics/VERSION'
@@ -1,10 +1,6 @@
1
1
  require_relative './lib/Statistics/VERSION'
2
2
 
3
3
  class Gem::Specification
4
- def dependencies=(gems)
5
- gems.each{|gem| add_dependency(*gem)}
6
- end
7
-
8
4
  def development_dependencies=(gems)
9
5
  gems.each{|gem| add_development_dependency(*gem)}
10
6
  end
@@ -12,12 +8,10 @@ end
12
8
 
13
9
  Gem::Specification.new do |spec|
14
10
  spec.name = 'statistics.rb'
15
-
16
11
  spec.version = Statistics::VERSION
17
- spec.date = '2026-05-03'
18
12
 
19
13
  spec.summary = "A statistics library for Ruby."
20
- spec.description = "A composable statistics library for Ruby. Histogram with automatic bin width calculation and composable Bin class."
14
+ spec.description = "A composable statistics library for Ruby. Histogram, Percentile, StandardDeviation, IQR."
21
15
 
22
16
  spec.author = 'thoran'
23
17
  spec.email = 'code@thoran.com'
@@ -29,8 +23,7 @@ Gem::Specification.new do |spec|
29
23
  spec.files = [
30
24
  Dir['lib/**/*.rb'],
31
25
  Dir['test/**/*.rb'],
32
- 'CHANGELOG',
33
- 'Gemfile',
26
+ 'CHANGELOG.md',
34
27
  'LICENSE',
35
28
  'Rakefile',
36
29
  'README.md',
@@ -1,33 +1,33 @@
1
- # test/Statistics/Histogram/Bin_test.rb
1
+ # test/Statistics/Bin_test.rb
2
2
 
3
3
  require 'minitest/autorun'
4
4
 
5
- require_relative '../../../lib/Statistics/Histogram/Bin'
5
+ require_relative '../../lib/Statistics/Bin'
6
6
 
7
- describe Statistics::Histogram::Bin do
7
+ describe Statistics::Bin do
8
8
  describe '.width' do
9
9
  it 'calculates square root width by default' do
10
10
  values = (1..100).to_a.map(&:to_f)
11
- width = Statistics::Histogram::Bin.width(values)
11
+ width = Statistics::Bin.width(values)
12
12
  expected = (values.last - values.first) * values.size ** (-1.0 / 2)
13
13
  _(width).must_be_close_to expected
14
14
  end
15
15
 
16
16
  it 'returns explicit bin_width when provided' do
17
17
  values = (1..100).to_a.map(&:to_f)
18
- width = Statistics::Histogram::Bin.width(values, bin_width: 7.5)
18
+ width = Statistics::Bin.width(values, bin_width: 7.5)
19
19
  _(width).must_equal 7.5
20
20
  end
21
21
 
22
22
  it 'calculates width from bin_count when provided' do
23
23
  values = (1..100).to_a.map(&:to_f)
24
- width = Statistics::Histogram::Bin.width(values, bin_count: 10)
24
+ width = Statistics::Bin.width(values, bin_count: 10)
25
25
  _(width).must_be_close_to 9.9
26
26
  end
27
27
 
28
28
  it 'returns 1.0 for zero-range data' do
29
29
  values = [5.0, 5.0, 5.0]
30
- width = Statistics::Histogram::Bin.width(values)
30
+ width = Statistics::Bin.width(values)
31
31
  _(width).must_equal 1.0
32
32
  end
33
33
  end
@@ -35,13 +35,13 @@ describe Statistics::Histogram::Bin do
35
35
  describe '.boundaries' do
36
36
  it 'returns an array starting at the minimum value' do
37
37
  values = (1..10).to_a.map(&:to_f)
38
- boundaries = Statistics::Histogram::Bin.boundaries(values, bin_width: 3.0)
38
+ boundaries = Statistics::Bin.boundaries(values, bin_width: 3.0)
39
39
  _(boundaries.first).must_equal 1.0
40
40
  end
41
41
 
42
42
  it 'extends past the maximum value' do
43
43
  values = (1..10).to_a.map(&:to_f)
44
- boundaries = Statistics::Histogram::Bin.boundaries(values, bin_width: 3.0)
44
+ boundaries = Statistics::Bin.boundaries(values, bin_width: 3.0)
45
45
  _(boundaries.last).must_be :>=, 10.0
46
46
  end
47
47
  end
@@ -49,24 +49,24 @@ describe Statistics::Histogram::Bin do
49
49
  describe '.bin_for_value' do
50
50
  it 'returns the correct bin' do
51
51
  bins = [5.0, 10.0, 15.0, 20.0].each_cons(2).map do |lower, upper|
52
- Statistics::Histogram::Bin.new(lower...upper)
52
+ Statistics::Bin.new(lower...upper)
53
53
  end
54
- bin = Statistics::Histogram::Bin.bin_for_value(7.0, bins, 5.0, 5.0)
54
+ bin = Statistics::Bin.bin_for_value(7.0, bins, 5.0, 5.0)
55
55
  _(bin.interval).must_equal(5.0...10.0)
56
56
  end
57
57
 
58
58
  it 'clamps to the last bin for values at the upper boundary' do
59
59
  bins = [5.0, 10.0, 15.0].each_cons(2).map do |lower, upper|
60
- Statistics::Histogram::Bin.new(lower...upper)
60
+ Statistics::Bin.new(lower...upper)
61
61
  end
62
- bin = Statistics::Histogram::Bin.bin_for_value(15.0, bins, 5.0, 5.0)
62
+ bin = Statistics::Bin.bin_for_value(15.0, bins, 5.0, 5.0)
63
63
  _(bin.interval).must_equal(10.0...15.0)
64
64
  end
65
65
  end
66
66
 
67
67
  describe 'instance methods' do
68
68
  before do
69
- @bin = Statistics::Histogram::Bin.new(1.0...5.0)
69
+ @bin = Statistics::Bin.new(1.0...5.0)
70
70
  end
71
71
 
72
72
  it 'starts with count of zero' do
@@ -0,0 +1,30 @@
1
+ # test/Statistics/IQR_test.rb
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require_relative '../../lib/Statistics/IQR'
6
+
7
+ describe Statistics::IQR do
8
+ describe '.of' do
9
+ it 'returns the difference between q75 and q25' do
10
+ values = [1, 2, 3, 4, 5]
11
+ expected = Statistics::Percentile.q75(values) - Statistics::Percentile.q25(values)
12
+ _(Statistics::IQR.of(values)).must_equal expected
13
+ end
14
+
15
+ it 'returns zero for identical values' do
16
+ _(Statistics::IQR.of([5, 5, 5, 5])).must_equal 0.0
17
+ end
18
+
19
+ it 'handles a two-value dataset' do
20
+ values = [10, 20]
21
+ _(Statistics::IQR.of(values)).must_be_close_to 5.0
22
+ end
23
+
24
+ it 'handles unsorted input' do
25
+ sorted = [1, 2, 3, 4, 5, 6, 7, 8]
26
+ shuffled = [5, 1, 8, 3, 7, 2, 6, 4]
27
+ _(Statistics::IQR.of(shuffled)).must_equal Statistics::IQR.of(sorted)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ # test/Statistics/Percentile_test.rb
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require_relative '../../lib/Statistics/Percentile'
6
+
7
+ describe Statistics::Percentile do
8
+ describe '.of' do
9
+ it 'returns the minimum for the 0th percentile' do
10
+ values = [1, 2, 3, 4, 5]
11
+ _(Statistics::Percentile.of(values, 0)).must_equal 1.0
12
+ end
13
+
14
+ it 'returns the maximum for the 100th percentile' do
15
+ values = [1, 2, 3, 4, 5]
16
+ _(Statistics::Percentile.of(values, 100)).must_equal 5.0
17
+ end
18
+
19
+ it 'returns the median for the 50th percentile' do
20
+ values = [1, 2, 3, 4, 5]
21
+ _(Statistics::Percentile.of(values, 50)).must_equal 3.0
22
+ end
23
+
24
+ it 'interpolates between values' do
25
+ values = [10, 20, 30, 40]
26
+ _(Statistics::Percentile.of(values, 25)).must_equal 17.5
27
+ end
28
+
29
+ it 'handles a single value' do
30
+ _(Statistics::Percentile.of([42], 50)).must_equal 42.0
31
+ end
32
+
33
+ it 'handles unsorted input' do
34
+ values = [5, 1, 3, 2, 4]
35
+ _(Statistics::Percentile.of(values, 50)).must_equal 3.0
36
+ end
37
+ end
38
+
39
+ describe '.q25' do
40
+ it 'returns the 25th percentile' do
41
+ values = [1, 2, 3, 4, 5]
42
+ _(Statistics::Percentile.q25(values)).must_equal Statistics::Percentile.of(values, 25)
43
+ end
44
+ end
45
+
46
+ describe '.q75' do
47
+ it 'returns the 75th percentile' do
48
+ values = [1, 2, 3, 4, 5]
49
+ _(Statistics::Percentile.q75(values)).must_equal Statistics::Percentile.of(values, 75)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,34 @@
1
+ # test/Statistics/StandardDeviation_test.rb
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require_relative '../../lib/Statistics/StandardDeviation'
6
+
7
+ describe Statistics::StandardDeviation do
8
+ describe '.of' do
9
+ it 'returns zero for identical values' do
10
+ _(Statistics::StandardDeviation.of([5, 5, 5, 5])).must_equal 0.0
11
+ end
12
+
13
+ it 'calculates population standard deviation by default' do
14
+ values = [2, 4, 4, 4, 5, 5, 7, 9]
15
+ _(Statistics::StandardDeviation.of(values)).must_be_close_to 2.0
16
+ end
17
+
18
+ it 'calculates sample standard deviation when requested' do
19
+ values = [2, 4, 4, 4, 5, 5, 7, 9]
20
+ sample_sd = Statistics::StandardDeviation.of(values, sample: true)
21
+ population_sd = Statistics::StandardDeviation.of(values)
22
+ _(sample_sd).must_be :>, population_sd
23
+ end
24
+
25
+ it 'handles a single value' do
26
+ _(Statistics::StandardDeviation.of([42])).must_equal 0.0
27
+ end
28
+
29
+ it 'handles two values' do
30
+ values = [0, 10]
31
+ _(Statistics::StandardDeviation.of(values)).must_be_close_to 5.0
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statistics.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -37,25 +37,30 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
- description: A composable statistics library for Ruby. Histogram with automatic bin
41
- width calculation and composable Bin class.
40
+ description: A composable statistics library for Ruby. Histogram, Percentile, StandardDeviation,
41
+ IQR.
42
42
  email: code@thoran.com
43
43
  executables: []
44
44
  extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
- - CHANGELOG
48
- - Gemfile
47
+ - CHANGELOG.md
49
48
  - LICENSE
50
49
  - README.md
51
50
  - Rakefile
51
+ - lib/Statistics/Bin.rb
52
52
  - lib/Statistics/Histogram.rb
53
- - lib/Statistics/Histogram/Bin.rb
53
+ - lib/Statistics/IQR.rb
54
+ - lib/Statistics/Percentile.rb
55
+ - lib/Statistics/StandardDeviation.rb
54
56
  - lib/Statistics/VERSION.rb
55
57
  - lib/statistics.rb
56
58
  - statistics.rb.gemspec
57
- - test/Statistics/Histogram/Bin_test.rb
59
+ - test/Statistics/Bin_test.rb
58
60
  - test/Statistics/Histogram_test.rb
61
+ - test/Statistics/IQR_test.rb
62
+ - test/Statistics/Percentile_test.rb
63
+ - test/Statistics/StandardDeviation_test.rb
59
64
  homepage: http://github.com/thoran/statistics
60
65
  licenses:
61
66
  - MIT
@@ -74,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
79
  - !ruby/object:Gem::Version
75
80
  version: '0'
76
81
  requirements: []
77
- rubygems_version: 4.0.10
82
+ rubygems_version: 4.0.11
78
83
  specification_version: 4
79
84
  summary: A statistics library for Ruby.
80
85
  test_files: []
data/CHANGELOG DELETED
@@ -1,92 +0,0 @@
1
- # CHANGELOG
2
-
3
- ## [0.5.0] - 20260503
4
- ## Bin.bin_for_value, Bin split, gem scaffolding
5
-
6
- 1. + Bin.bin_for_value (wraps index_for_value, now private)
7
- 2. ~ Histogram#allocate_values: Use Bin.bin_for_value
8
- 3. ~ Bin split out to lib/Statistics/Histogram/Bin.rb
9
- 4. + lib/statistics.rb
10
- 5. + lib/Statistics/VERSION.rb
11
- 6. + statistics.rb.gemspec
12
- 7. + Gemfile
13
- 8. + Rakefile
14
- 9. + README.md
15
- 10. + LICENSE
16
- 11. + test/Statistics/Histogram_test.rb
17
- 12. + test/Statistics/Histogram/Bin_test.rb
18
-
19
-
20
- ## [0.4.0] - 20260502
21
- ## Bin instances and consolidated class methods
22
-
23
- 1. + Bin instances (count-tracking via increment, attr_reader :interval)
24
- 2. ~ Bin.width: handles bin_width, bin_count, zero-range, and method selection
25
- 3. + Bin.boundaries (from 0.3.0)
26
- 4. + Bin.index_for_value (was Histogram#index_for_value)
27
- 5. + Bin.data_range
28
- 6. ~ class << self for class methods with private boundary
29
- 7. ~ attr_reader :count replaces hand-written method
30
-
31
-
32
- ## [0.3.0] - 20260417
33
- ## Hash-based bins, Bin as class-methods-only
34
-
35
- 1. + Bin.boundaries class method
36
- 2. ~ initialize: delegates to Bin.width and Bin.boundaries
37
- 3. ~ Bin: hash-based bins (no Bin instances), Bin is class-methods-only
38
- 4. - index_for_value (inlined back into allocate_values)
39
- 5. - determine_bin_width (replaced by Bin.width delegation)
40
- 6. - zero-range guard
41
-
42
-
43
- ## [0.2.0] - 20260417
44
- ## Count-tracking, interval, zero-range guard
45
-
46
- 1. ~ Bin: count-tracking via increment instead of storing values in array
47
- 2. ~ Bin: attr_reader :interval instead of :range
48
- 3. + Bin#empty? checks @count == 0 instead of @values.empty?
49
- 4. + determine_bin_width: zero-range guard
50
- 5. + index_for_value extracted from allocate_values
51
-
52
-
53
- ## [0.1.1] - 20260417
54
- ## Extract determine_bin_width
55
-
56
- 1. ~ initialize: extracted determine_bin_width from one-liner
57
- 2. /compute_boundaries/calculate_boundaries/
58
-
59
-
60
- ## [0.1.0] - 20260417
61
- ## Bin class
62
-
63
- 1. + Statistics::Histogram::Bin
64
- 2. ~ allocate_values: creates Bin instances instead of hash entries
65
-
66
-
67
- ## [0.0.0] - 20260417
68
- ## Statistics::Histogram
69
-
70
- 1. + lib/Statistics/Histogram.rb
71
- 2. - lib/BinWidth.rb
72
-
73
-
74
- ## 20260416
75
- ## BinWidth 0.1.0 to 0.2.0: Reintroduce all named strategies
76
-
77
- 1. /0.1.0/0.2.0/
78
- 2. Reintroduce all named strategies.
79
-
80
-
81
- ## 20260416
82
- ## BinWidth 0.0.0 to 0.1.0: Tuneable root generalisation
83
-
84
- 1. /0.0.0/0.1.0/
85
- 2. + tuneable_root as the general form
86
- 3. ~ square_root rewritten as range * n^(-1/2) form
87
-
88
-
89
- ## 20260416
90
- ## BinWidth 0.0.0
91
-
92
- 1. + lib/BinWidth.rb
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
@@ -1,76 +0,0 @@
1
- # Statistics/Histogram/Bin.rb
2
- # Statistics::Histogram::Bin
3
-
4
- module Statistics
5
- class Histogram
6
- class Bin
7
- class << self
8
- def width(values, bin_width: nil, bin_count: nil, method: :square_root)
9
- if bin_width
10
- bin_width
11
- elsif bin_count
12
- data_range(values) / bin_count.to_f
13
- elsif data_range(values) == 0
14
- 1.0
15
- else
16
- send("#{method}_width", values)
17
- end
18
- end
19
-
20
- def count(values, method: :square_root)
21
- send("#{method}_count", values)
22
- end
23
-
24
- def data_range(values)
25
- values.last - values.first
26
- end
27
-
28
- def boundaries(values, bin_width: nil, bin_count: nil, method: :square_root)
29
- w = bin_width || width(values, bin_count: bin_count, method: method)
30
- values.first.step(to: values.last + w, by: w).to_a
31
- end
32
-
33
- def bin_for_value(value, bins, bottom_boundary, bin_width)
34
- index = index_for_value(value, bins.count, bottom_boundary, bin_width)
35
- bins[index]
36
- end
37
-
38
- private
39
-
40
- def index_for_value(value, bin_count, bottom_boundary, bin_width)
41
- i = ((value - bottom_boundary) / bin_width).floor
42
- i >= bin_count ? bin_count - 1 : i
43
- end
44
-
45
- def square_root_width(values)
46
- data_range(values) * values.size ** (-1.0 / 2)
47
- end
48
-
49
- def square_root_count(values)
50
- Math.sqrt(values.size).ceil
51
- end
52
- end # class << self
53
-
54
- attr_reader :count, :interval
55
-
56
- def increment
57
- @count += 1
58
- end
59
-
60
- def width
61
- @interval.end - @interval.begin
62
- end
63
-
64
- def empty?
65
- @count == 0
66
- end
67
-
68
- private
69
-
70
- def initialize(interval)
71
- @interval = interval
72
- @count = 0
73
- end
74
- end
75
- end
76
- end