sapor 0.1b1

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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/Area Class Diagram.dia +0 -0
  3. data/Area Class Diagram.png +0 -0
  4. data/Class Diagram.dia +0 -0
  5. data/Class Diagram.png +0 -0
  6. data/Examples.md +361 -0
  7. data/LICENSE +674 -0
  8. data/README.md +70 -0
  9. data/Rakefile +18 -0
  10. data/Technical Documentation.md +14 -0
  11. data/bin/create_installation_package.sh +49 -0
  12. data/bin/install.sh +45 -0
  13. data/bin/sapor.rb +22 -0
  14. data/bin/sapor.sh +105 -0
  15. data/lib/sapor.rb +44 -0
  16. data/lib/sapor/binomials_cache.rb +45 -0
  17. data/lib/sapor/combinations_distribution.rb +180 -0
  18. data/lib/sapor/dichotomies.rb +98 -0
  19. data/lib/sapor/dichotomy.rb +138 -0
  20. data/lib/sapor/first_past_the_post.rb +78 -0
  21. data/lib/sapor/leveled_proportional.rb +64 -0
  22. data/lib/sapor/log4r_logger.rb +49 -0
  23. data/lib/sapor/log_facade.rb +40 -0
  24. data/lib/sapor/number_formatter.rb +45 -0
  25. data/lib/sapor/poll.rb +137 -0
  26. data/lib/sapor/polychotomy.rb +359 -0
  27. data/lib/sapor/proportional.rb +128 -0
  28. data/lib/sapor/pseudorandom_multirange_enumerator.rb +87 -0
  29. data/lib/sapor/regional_data/area.rb +80 -0
  30. data/lib/sapor/regional_data/catalonia-2012-2015.psv +100 -0
  31. data/lib/sapor/regional_data/catalonia-2012.psv +87 -0
  32. data/lib/sapor/regional_data/catalonia.rb +90 -0
  33. data/lib/sapor/regional_data/norway.rb +408 -0
  34. data/lib/sapor/regional_data/united_kingdom.rb +1075 -0
  35. data/lib/sapor/regional_data/utopia.rb +66 -0
  36. data/sapor.gemspec +35 -0
  37. data/spec/integration/area_spec.rb +28 -0
  38. data/spec/integration/poll_spec.rb +107 -0
  39. data/spec/integration/sample.poll +7 -0
  40. data/spec/spec_helper.rb +31 -0
  41. data/spec/unit/area_spec.rb +115 -0
  42. data/spec/unit/binomials_cache_spec.rb +34 -0
  43. data/spec/unit/catalonia_spec.rb +82 -0
  44. data/spec/unit/combinations_distribution_spec.rb +241 -0
  45. data/spec/unit/denominators_spec.rb +34 -0
  46. data/spec/unit/dichotomies_spec.rb +154 -0
  47. data/spec/unit/dichotomy_spec.rb +320 -0
  48. data/spec/unit/first_past_the_post_spec.rb +53 -0
  49. data/spec/unit/leveled_proportional_spec.rb +51 -0
  50. data/spec/unit/norway_spec.rb +47 -0
  51. data/spec/unit/number_formatter_spec.rb +173 -0
  52. data/spec/unit/poll_spec.rb +105 -0
  53. data/spec/unit/polychotomy_spec.rb +332 -0
  54. data/spec/unit/proportional_spec.rb +86 -0
  55. data/spec/unit/pseudorandom_multirange_enumerator_spec.rb +82 -0
  56. metadata +119 -0
@@ -0,0 +1,241 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2014 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ require 'spec_helper'
21
+
22
+ describe Sapor::CombinationsDistribution, '#new' do
23
+ it 'creates an empty distribution' do
24
+ distribution = Sapor::CombinationsDistribution.new
25
+ expect(distribution).to be_empty
26
+ end
27
+ end
28
+
29
+ describe Sapor::CombinationsDistribution, '#[]=' do
30
+ it 'sets a value-combinations pair' do
31
+ distribution = Sapor::CombinationsDistribution.new
32
+ distribution[1] = 2
33
+ expect(distribution[1]).to eq(2)
34
+ end
35
+ end
36
+
37
+ describe Sapor::CombinationsDistribution, '#+' do
38
+ it 'adds two combiation distributions together' do
39
+ a = Sapor::CombinationsDistribution.new
40
+ a[10] = 2
41
+ a[21] = 3
42
+ a[23] = 11
43
+ b = Sapor::CombinationsDistribution.new
44
+ b[10] = 5
45
+ b[21] = 7
46
+ b[29] = 13
47
+ sum = Sapor::CombinationsDistribution.new
48
+ sum[10] = 7
49
+ sum[21] = 10
50
+ sum[23] = 11
51
+ sum[29] = 13
52
+ expect((a + b)[10]).to eq(sum[10])
53
+ expect((a + b)[21]).to eq(sum[21])
54
+ expect((a + b)[23]).to eq(sum[23])
55
+ expect((a + b)[29]).to eq(sum[29])
56
+ end
57
+ end
58
+
59
+ describe Sapor::CombinationsDistribution, '#confidence_interval' do
60
+ it 'returns the single value as boundaries (default)' do
61
+ distribution = Sapor::CombinationsDistribution.new
62
+ distribution[5] = 2
63
+ expect(distribution.confidence_interval(0.95)).to eq([5, 5])
64
+ end
65
+
66
+ it 'returns the full interval as boundaries if single value (extensive)' do
67
+ distribution = Sapor::CombinationsDistribution.new
68
+ distribution[5] = 2
69
+ expect(distribution.confidence_interval(0.95, 10)).to eq([0, 10])
70
+ end
71
+
72
+ it 'returns the boundaries (default)' do
73
+ distribution = Sapor::CombinationsDistribution.new
74
+ distribution[3] = 2
75
+ distribution[7] = 2
76
+ expect(distribution.confidence_interval(0.95)).to eq([3, 7])
77
+ end
78
+
79
+ it 'returns the full interval as boundaries (extensive)' do
80
+ distribution = Sapor::CombinationsDistribution.new
81
+ distribution[3] = 2
82
+ distribution[7] = 2
83
+ expect(distribution.confidence_interval(0.95, 10)).to eq([0, 10])
84
+ end
85
+
86
+ it 'returns the full interval if level too high' do
87
+ distribution = Sapor::CombinationsDistribution.new
88
+ distribution[5] = 1
89
+ distribution[10] = 98
90
+ distribution[15] = 1
91
+ expect(distribution.confidence_interval(0.99)).to eq([5, 15])
92
+ end
93
+
94
+ it 'returns the values in between as boundaries (even)' do
95
+ distribution = Sapor::CombinationsDistribution.new
96
+ distribution[5] = 1
97
+ distribution[10] = 98
98
+ distribution[15] = 1
99
+ expect(distribution.confidence_interval(0.95)).to eq([8, 12])
100
+ end
101
+
102
+ it 'returns the values in between as boundaries (odd)' do
103
+ distribution = Sapor::CombinationsDistribution.new
104
+ distribution[6] = 1.to_lf
105
+ distribution[10] = 98.to_lf
106
+ distribution[14] = 1.to_lf
107
+ expect(distribution.confidence_interval(0.95)).to eq([8, 12])
108
+ end
109
+ end
110
+
111
+ describe Sapor::CombinationsDistribution, '#confidence_interval_values' do
112
+ it 'extracts the single value as confidence interval value' do
113
+ distribution = Sapor::CombinationsDistribution.new
114
+ distribution[5] = 2.to_lf
115
+ expect(distribution.confidence_interval_values(0.95)).to eq([5])
116
+ end
117
+
118
+ it 'extracts all values as confidence intervalue values' do
119
+ distribution = Sapor::CombinationsDistribution.new
120
+ distribution[3] = 2.to_lf
121
+ distribution[7] = 2.to_lf
122
+ expect(distribution.confidence_interval_values(0.95).sort).to eq([3, 7])
123
+ end
124
+
125
+ it 'extracts the middle values as confidence interval values' do
126
+ distribution = Sapor::CombinationsDistribution.new
127
+ distribution[5] = 1.to_lf
128
+ distribution[10] = 98.to_lf
129
+ distribution[15] = 1.to_lf
130
+ expect(distribution.confidence_interval_values(0.95)).to eq([10])
131
+ end
132
+ end
133
+
134
+ describe Sapor::CombinationsDistribution, '#empty?' do
135
+ it 'returns true if no value-combinations pair has been added' do
136
+ distribution = Sapor::CombinationsDistribution.new
137
+ expect(distribution.empty?).to be true
138
+ end
139
+
140
+ it 'returns false if a value-combinations pair has been added' do
141
+ distribution = Sapor::CombinationsDistribution.new
142
+ distribution[1] = 2.to_lf
143
+ expect(distribution.empty?).to be false
144
+ end
145
+ end
146
+
147
+ describe Sapor::CombinationsDistribution, '#most_probable_rounded_fraction' do
148
+ it "returns the median rounded fraction if there's more than one with" \
149
+ ' maximal probability' do
150
+ distribution = Sapor::CombinationsDistribution.new
151
+ distribution[500] = 2.to_lf
152
+ expect(distribution.most_probable_rounded_fraction(1000)).to eq(0.5)
153
+ end
154
+
155
+ it 'returns the rounded fraction with the maximal probability' do
156
+ distribution = Sapor::CombinationsDistribution.new
157
+ distribution[199] = 2.to_lf
158
+ distribution[200] = 3.to_lf
159
+ distribution[201] = 2.to_lf
160
+ expect(distribution.most_probable_rounded_fraction(1000)).to eq(0.2)
161
+ end
162
+ end
163
+
164
+ describe Sapor::CombinationsDistribution, '#most_probable_value' do
165
+ it "returns the single value if there's only one value" do
166
+ distribution = Sapor::CombinationsDistribution.new
167
+ distribution[1] = 2
168
+ expect(distribution.most_probable_value).to eq(1)
169
+ end
170
+
171
+ it 'returns the first of two values if that has the greatest number ' \
172
+ 'of combinations' do
173
+ distribution = Sapor::CombinationsDistribution.new
174
+ distribution[1] = 2
175
+ distribution[2] = 1
176
+ expect(distribution.most_probable_value).to eq(1)
177
+ end
178
+
179
+ it 'returns the second of two values if that has the greatest number ' \
180
+ 'of combinations' do
181
+ distribution = Sapor::CombinationsDistribution.new
182
+ distribution[1] = 1
183
+ distribution[2] = 2
184
+ expect(distribution.most_probable_value).to eq(2)
185
+ end
186
+ end
187
+
188
+ describe Sapor::CombinationsDistribution, '#size' do
189
+ it 'returns 0 for an empty distribution' do
190
+ distribution = Sapor::CombinationsDistribution.new
191
+ expect(distribution.size).to eq(0)
192
+ end
193
+
194
+ it 'returns 1 when a value-combinations pair has been added' do
195
+ distribution = Sapor::CombinationsDistribution.new
196
+ distribution[1] = 2
197
+ expect(distribution.size).to eq(1)
198
+ end
199
+ end
200
+
201
+ describe Sapor::CombinationsDistribution, '#threshold_probability' do
202
+ it 'returns 1 if the single value is above the threshold' do
203
+ distribution = Sapor::CombinationsDistribution.new
204
+ distribution[5] = 2.to_lf
205
+ expect(distribution.threshold_probability(0.1, 10)).to eq(1)
206
+ end
207
+
208
+ it 'returns 0 if the single value is below the threshold' do
209
+ distribution = Sapor::CombinationsDistribution.new
210
+ distribution[5] = 2.to_lf
211
+ expect(distribution.threshold_probability(0.6, 10)).to eq(0)
212
+ end
213
+
214
+ it 'returns 0.8 if the threshold is between combinations 2 and 8' do
215
+ distribution = Sapor::CombinationsDistribution.new
216
+ distribution[2] = 2.to_lf
217
+ distribution[7] = 8.to_lf
218
+ expect(distribution.threshold_probability(0.5, 10)).to eq(0.8)
219
+ end
220
+
221
+ it 'includes the probability at the threshold' do
222
+ distribution = Sapor::CombinationsDistribution.new
223
+ distribution[2] = 2.to_lf
224
+ distribution[5] = 5.to_lf
225
+ distribution[7] = 3.to_lf
226
+ expect(distribution.threshold_probability(0.5, 10)).to eq(0.8)
227
+ end
228
+ end
229
+
230
+ describe Sapor::CombinationsDistribution, '#values' do
231
+ it 'returns an empty array for an empty distribution' do
232
+ distribution = Sapor::CombinationsDistribution.new
233
+ expect(distribution.values).to eq([])
234
+ end
235
+
236
+ it 'returns 1 when a value-combinations pair has been added' do
237
+ distribution = Sapor::CombinationsDistribution.new
238
+ distribution[1] = 2
239
+ expect(distribution.values).to eq([1])
240
+ end
241
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2014 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ require 'spec_helper'
21
+
22
+ describe Sapor::DhondtDenominators, '#get' do
23
+ it 'returns [1, 2, 3, 4, 5] as the five first elements' do
24
+ denominators = Sapor::DhondtDenominators.get(5).each.to_a
25
+ expect(denominators).to eq([1, 2, 3, 4, 5])
26
+ end
27
+ end
28
+
29
+ describe Sapor::SainteLague14Denominators, '#get' do
30
+ it 'returns [1.4, 3, 5, 7, 9] as the five first elements' do
31
+ denominators = Sapor::SainteLague14Denominators.get(5).each.to_a
32
+ expect(denominators).to eq([1.4, 3, 5, 7, 9])
33
+ end
34
+ end
@@ -0,0 +1,154 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2014 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ require 'spec_helper'
21
+
22
+ SAMPLE_RESULTS = { 'Red' => 1, 'Green' => 2, 'Blue' => 3, 'Other' => 1 }
23
+ SAMPLE_POPULATION_SIZE = 1_000_000
24
+
25
+ describe Sapor::Dichotomies, '#confidence_interval_values' do
26
+ it 'returns the confidence interval values' do
27
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
28
+ SAMPLE_POPULATION_SIZE)
29
+ expect(dichotomies.confidence_interval_values('Red', 0.95)).to \
30
+ eq([500_000])
31
+ end
32
+
33
+ it 'returns the non-default confidence interval values' do
34
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
35
+ SAMPLE_POPULATION_SIZE)
36
+ dichotomies.refine
37
+ expect(dichotomies.confidence_interval_values('Red', 0.9999).sort).to \
38
+ eq([166_667, 500_000, 833_333])
39
+ end
40
+ end
41
+
42
+ describe Sapor::Dichotomies, '#error_estimate' do
43
+ it 'returns the largest error estimate of the underlying Dichotomy ' \
44
+ 'objects' do
45
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
46
+ SAMPLE_POPULATION_SIZE)
47
+ expect(dichotomies.error_estimate).to eq(1)
48
+ end
49
+ end
50
+
51
+ describe Sapor::Dichotomies, '#progress_report' do
52
+ it 'reports progress as 1 data point by default' do
53
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
54
+ SAMPLE_POPULATION_SIZE)
55
+ expect(dichotomies.progress_report).to eq('Number of data points: 1.')
56
+ end
57
+
58
+ it 'reports progress as 3 data points after the first refinement' do
59
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
60
+ SAMPLE_POPULATION_SIZE)
61
+ dichotomies.refine
62
+ expect(dichotomies.progress_report).to eq('Number of data points: 3.')
63
+ end
64
+ end
65
+
66
+ describe Sapor::Dichotomies, '#report' do
67
+ it 'produces a report by default for short choice labels' do
68
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
69
+ SAMPLE_POPULATION_SIZE)
70
+ expected_report = 'Most probable fractions and 95% confidence ' \
71
+ "intervals:\n" \
72
+ "Choice MPF CI(95%)\n" \
73
+ "Blue 50.0% 0.0%–100.0%\n" \
74
+ "Green 50.0% 0.0%–100.0%\n" \
75
+ "Red 50.0% 0.0%–100.0%\n" \
76
+ 'Other 50.0% 0.0%–100.0%'
77
+ expect(dichotomies.report).to eq(expected_report)
78
+ end
79
+
80
+ it 'produces a report by default for long choice labels' do
81
+ dichotomies = Sapor::Dichotomies.new({ 'Dark Red' => 1,
82
+ 'Light Green' => 2,
83
+ 'Medium Blue' => 3, 'Other' => 1 },
84
+ SAMPLE_POPULATION_SIZE)
85
+ expected_report = 'Most probable fractions and 95% confidence ' \
86
+ "intervals:\n" \
87
+ "Choice MPF CI(95%)\n" \
88
+ "Dark Red 50.0% 0.0%–100.0%\n" \
89
+ "Light Green 50.0% 0.0%–100.0%\n" \
90
+ "Medium Blue 50.0% 0.0%–100.0%\n" \
91
+ 'Other 50.0% 0.0%–100.0%'
92
+ expect(dichotomies.report).to eq(expected_report)
93
+ end
94
+
95
+ it 'produces a report after the first refinement' do
96
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
97
+ SAMPLE_POPULATION_SIZE)
98
+ dichotomies.refine
99
+ expected_report = 'Most probable fractions and 95% confidence ' \
100
+ "intervals:\n" \
101
+ "Choice MPF CI(95%)\n" \
102
+ "Blue 50.0% 0.0%–100.0%\n" \
103
+ "Green 16.7% 0.0%– 66.7%\n" \
104
+ "Red 16.7% 0.0%– 66.7%\n" \
105
+ 'Other 16.7% 0.0%– 66.7%'
106
+ expect(dichotomies.report).to eq(expected_report)
107
+ end
108
+
109
+ it 'sorts the choices according to MPV' do
110
+ dichotomies = Sapor::Dichotomies.new({ 'Red' => 3, 'Green' => 2,
111
+ 'Blue' => 1, 'Other' => 1 },
112
+ SAMPLE_POPULATION_SIZE)
113
+ dichotomies.refine
114
+ expected_report = 'Most probable fractions and 95% confidence ' \
115
+ "intervals:\n" \
116
+ "Choice MPF CI(95%)\n" \
117
+ "Red 50.0% 0.0%–100.0%\n" \
118
+ "Blue 16.7% 0.0%– 66.7%\n" \
119
+ "Green 16.7% 0.0%– 66.7%\n" \
120
+ 'Other 16.7% 0.0%– 66.7%'
121
+ expect(dichotomies.report).to eq(expected_report)
122
+ end
123
+
124
+ it "puts Other on the last line, even if it's listed first and largest" do
125
+ dichotomies = Sapor::Dichotomies.new({ 'Other' => 4, 'Red' => 1,
126
+ 'Green' => 2, 'Blue' => 3 },
127
+ SAMPLE_POPULATION_SIZE)
128
+ dichotomies.refine
129
+ expected_report = 'Most probable fractions and 95% confidence ' \
130
+ "intervals:\n" \
131
+ "Choice MPF CI(95%)\n" \
132
+ "Blue 16.7% 0.0%– 66.7%\n" \
133
+ "Green 16.7% 0.0%– 66.7%\n" \
134
+ "Red 16.7% 0.0%– 66.7%\n" \
135
+ 'Other 50.0% 0.0%– 66.7%'
136
+ expect(dichotomies.report).to eq(expected_report)
137
+ end
138
+
139
+ it 'includes threshold probabilities when a threshold provided' do
140
+ dichotomies = Sapor::Dichotomies.new(SAMPLE_RESULTS,
141
+ SAMPLE_POPULATION_SIZE,
142
+ 0.15)
143
+ dichotomies.refine
144
+ dichotomies.refine
145
+ expected_report = 'Most probable fractions and 95% confidence ' \
146
+ "intervals:\n" \
147
+ "Choice MPF CI(95%) P(≥15%)\n" \
148
+ "Blue 38.9% 11.1%– 77.8% 99.6%\n" \
149
+ "Green 27.8% 0.0%– 66.7% 95.7%\n" \
150
+ "Red 16.7% 0.0%– 55.6% 76.1%\n" \
151
+ 'Other 16.7% 0.0%– 55.6% 76.1%'
152
+ expect(dichotomies.report).to eq(expected_report)
153
+ end
154
+ end
@@ -0,0 +1,320 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2014 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ require 'spec_helper'
21
+
22
+ def dichotomy_of_eight
23
+ Sapor::Dichotomy.new(2, 5, 8)
24
+ end
25
+
26
+ def dichotomy_of_nine
27
+ Sapor::Dichotomy.new(2, 5, 9)
28
+ end
29
+
30
+ def dichotomy_of_thousand
31
+ Sapor::Dichotomy.new(2, 5, 1000)
32
+ end
33
+
34
+ def dichotomy_of_nine_hundred_ninety_nine
35
+ Sapor::Dichotomy.new(2, 5, 999)
36
+ end
37
+
38
+ describe Sapor::Dichotomy, '#new' do
39
+ it 'creates an array with a value at 50% of the population size' do
40
+ expect(dichotomy_of_eight.values).to eq([4])
41
+ end
42
+
43
+ it 'creates an array with a value at 50% of the population size' \
44
+ ' (rounding)' do
45
+ expect(dichotomy_of_nine.values).to eq([4])
46
+ end
47
+
48
+ it 'creates an array with a combination for 50% of the population size' do
49
+ expect(dichotomy_of_eight.combinations(4)).to eq(24.to_lf)
50
+ end
51
+ end
52
+
53
+ describe Sapor::Dichotomy, '#confidence_interval' do
54
+ it 'returns [0%, 100%] after new for default level 95%' do
55
+ expect(dichotomy_of_eight.confidence_interval).to eq([0.0, 1.0])
56
+ end
57
+
58
+ it 'returns the default 95% confidence interval after one refinement' do
59
+ dichotomy = Sapor::Dichotomy.new(20, 50, 80)
60
+ dichotomy.refine
61
+ expect(dichotomy.confidence_interval).to eq([0.3375, 0.6625])
62
+ end
63
+
64
+ it 'returns the default 95% confidence interval after three refinements' do
65
+ dichotomy = dichotomy_of_thousand
66
+ dichotomy.refine
67
+ dichotomy.refine
68
+ dichotomy.refine
69
+ expect(dichotomy.confidence_interval).to eq([0.112, 0.777])
70
+ end
71
+
72
+ it 'returns the 80% confidence interval after three refinements' do
73
+ dichotomy = dichotomy_of_thousand
74
+ dichotomy.refine
75
+ dichotomy.refine
76
+ dichotomy.refine
77
+ expect(dichotomy.confidence_interval(0.8)).to eq([0.186, 0.666])
78
+ end
79
+ end
80
+
81
+ describe Sapor::Dichotomy, '#error_estimate' do
82
+ it 'is 0 when population size is reached' do
83
+ dichotomy = dichotomy_of_eight
84
+ dichotomy.refine
85
+ dichotomy.refine
86
+ dichotomy.refine
87
+ expect(dichotomy.error_estimate).to eq(0)
88
+ end
89
+
90
+ it 'is at least the resolution (no refinement)' do
91
+ dichotomy = dichotomy_of_eight
92
+ expect(dichotomy.error_estimate).to eq(1.0)
93
+ end
94
+
95
+ it 'is at least the resolution (one refinement)' do
96
+ dichotomy = dichotomy_of_eight
97
+ dichotomy.refine
98
+ expect(dichotomy.error_estimate).to eq(1.to_f / 3)
99
+ end
100
+ end
101
+
102
+ describe Sapor::Dichotomy, '#most_probable_fraction' do
103
+ it 'returns the fraction of the single value after new' do
104
+ expect(dichotomy_of_eight.most_probable_fraction).to eq(0.5)
105
+ end
106
+
107
+ it 'returns the one probable value after one refinement' do
108
+ dichotomy = dichotomy_of_eight
109
+ dichotomy.refine
110
+ expect(dichotomy.most_probable_fraction).to eq(0.5)
111
+ end
112
+
113
+ it 'returns the new most probable value after two refinements' do
114
+ dichotomy = dichotomy_of_eight
115
+ dichotomy.refine
116
+ dichotomy.refine
117
+ expect(dichotomy.most_probable_fraction).to eq(0.375)
118
+ end
119
+ end
120
+
121
+ describe Sapor::Dichotomy, '#most_probable_value' do
122
+ it 'returns the single value after new' do
123
+ expect(dichotomy_of_eight.most_probable_value).to eq(4)
124
+ end
125
+
126
+ it 'returns the one probable value after one refinement' do
127
+ dichotomy = dichotomy_of_eight
128
+ dichotomy.refine
129
+ expect(dichotomy.most_probable_value).to eq(4)
130
+ end
131
+
132
+ it 'returns the new most probable value after two refinements' do
133
+ dichotomy = dichotomy_of_eight
134
+ dichotomy.refine
135
+ dichotomy.refine
136
+ expect(dichotomy.most_probable_value).to eq(3)
137
+ end
138
+ end
139
+
140
+ describe Sapor::Dichotomy, '#refine' do
141
+ it 'adds values after one refinement (power of three)' do
142
+ dichotomy = dichotomy_of_eight
143
+ dichotomy.refine
144
+ expect(dichotomy.values.sort).to eq([1, 4, 7])
145
+ end
146
+
147
+ it 'adds values after one refinement (small population)' do
148
+ dichotomy = dichotomy_of_nine
149
+ dichotomy.refine
150
+ expect(dichotomy.values.sort).to eq([1, 4, 7])
151
+ end
152
+
153
+ it 'adds values after one refinement (large population)' do
154
+ dichotomy = dichotomy_of_thousand
155
+ dichotomy.refine
156
+ expect(dichotomy.values.sort).to eq([167, 500, 833])
157
+ end
158
+
159
+ it 'adds values after two refinements (power of three)' do
160
+ dichotomy = dichotomy_of_eight
161
+ dichotomy.refine
162
+ dichotomy.refine
163
+ expect(dichotomy.values.sort).to eq([0, 1, 2, 3, 4, 5, 6, 7, 8])
164
+ end
165
+
166
+ it 'adds values after two refinements (small population)' do
167
+ dichotomy = dichotomy_of_nine
168
+ dichotomy.refine
169
+ dichotomy.refine
170
+ expect(dichotomy.values.sort).to eq([0, 1, 2, 3, 4, 5, 6, 7, 8])
171
+ end
172
+
173
+ it 'adds values after two refinements (small population)' do
174
+ dichotomy = Sapor::Dichotomy.new(2, 5, 60)
175
+ dichotomy.refine
176
+ dichotomy.refine
177
+ expect(dichotomy.values.sort).to eq([3, 10, 17, 23, 30, 37, 43, 50, 57])
178
+ end
179
+
180
+ it 'adds values after two refinements (large population)' do
181
+ dichotomy = dichotomy_of_thousand
182
+ dichotomy.refine
183
+ dichotomy.refine
184
+ expect(dichotomy.values.sort).to eq([56, 167, 278, 389, 500, 611, 722, 833,
185
+ 944])
186
+ end
187
+
188
+ it "doesn't add values when population size is reached" do
189
+ dichotomy = dichotomy_of_eight
190
+ dichotomy.refine
191
+ dichotomy.refine
192
+ dichotomy.refine
193
+ expect(dichotomy.values.sort).to eq([0, 1, 2, 3, 4, 5, 6, 7, 8])
194
+ end
195
+
196
+ it 'fills up the values when population size is reached' do
197
+ dichotomy = dichotomy_of_nine
198
+ dichotomy.refine
199
+ dichotomy.refine
200
+ dichotomy.refine
201
+ expect(dichotomy.values.sort).to eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
202
+ end
203
+
204
+ it 'fills up the values when population size is reached' do
205
+ dichotomy = Sapor::Dichotomy.new(2, 5, 11)
206
+ dichotomy.refine
207
+ dichotomy.refine
208
+ dichotomy.refine
209
+ expect(dichotomy.values.sort).to eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
210
+ end
211
+
212
+ it 'adds combinations after a refinement' do
213
+ dichotomy = dichotomy_of_thousand
214
+ dichotomy.refine
215
+ expect(dichotomy.combinations(167)).to eq(1_330_493_216_416.to_lf)
216
+ expect(dichotomy.combinations(833)).to eq(264_177_353_440.to_lf)
217
+ end
218
+
219
+ it 'adds combinations of zero for impossible values' do
220
+ dichotomy = dichotomy_of_eight
221
+ dichotomy.refine
222
+ expect(dichotomy.combinations(1)).to eq(0.to_lf)
223
+ expect(dichotomy.combinations(7)).to eq(0.to_lf)
224
+ end
225
+ end
226
+
227
+ describe Sapor::Dichotomy, '#threshold_probability' do
228
+ it 'returns 100% after new for a 50% threshold' do
229
+ expect(dichotomy_of_thousand.threshold_probability(0.5)).to eq(1)
230
+ end
231
+
232
+ it 'returns 0% after new for a 51% threshold' do
233
+ expect(dichotomy_of_thousand.threshold_probability(0.51)).to eq(0)
234
+ end
235
+
236
+ it 'returns 100% for a 5% threshold after one refinement' do
237
+ dichotomy = dichotomy_of_thousand
238
+ dichotomy.refine
239
+ expect(dichotomy.threshold_probability(0.05)).to eq(1)
240
+ end
241
+
242
+ it 'returns 68% (after rounding) for a 50% threshold after one refinement' do
243
+ dichotomy = dichotomy_of_thousand
244
+ dichotomy.refine
245
+ expect(dichotomy.threshold_probability(0.5).round(2)).to eq(0.68)
246
+ end
247
+
248
+ it 'returns 0% for a 95% threshold after one refinement' do
249
+ dichotomy = dichotomy_of_thousand
250
+ dichotomy.refine
251
+ expect(dichotomy.threshold_probability(0.95)).to eq(0)
252
+ end
253
+ end
254
+
255
+ describe Sapor::Dichotomy, '#value_confidence_interval' do
256
+ it 'returns [0, population_size] values after new for default level 95%' do
257
+ expect(dichotomy_of_eight.value_confidence_interval).to eq([0, 8])
258
+ end
259
+
260
+ it 'returns [0, population_size] values after new for level 80%' do
261
+ expect(dichotomy_of_eight.value_confidence_interval(0.8)).to eq([0, 8])
262
+ end
263
+
264
+ it 'returns the value confidence interval after one refinement' do
265
+ dichotomy = Sapor::Dichotomy.new(20, 50, 80)
266
+ dichotomy.refine
267
+ expect(dichotomy.value_confidence_interval).to eq([27, 53])
268
+ end
269
+
270
+ it 'returns the value confidence interval after full refinement' do
271
+ dichotomy = dichotomy_of_eight
272
+ dichotomy.refine
273
+ dichotomy.refine
274
+ expect(dichotomy.value_confidence_interval).to eq([2, 5])
275
+ end
276
+
277
+ it 'returns the 80% value confidence interval after full refinement' do
278
+ dichotomy = dichotomy_of_eight
279
+ dichotomy.refine
280
+ dichotomy.refine
281
+ expect(dichotomy.value_confidence_interval(0.8)).to eq([2, 5])
282
+ end
283
+
284
+ it 'returns the value confidence interval after two refinements' do
285
+ dichotomy = dichotomy_of_thousand
286
+ dichotomy.refine
287
+ dichotomy.refine
288
+ expect(dichotomy.value_confidence_interval).to eq([112, 777])
289
+ end
290
+
291
+ it 'returns the 80% value confidence interval after two refinements' do
292
+ dichotomy = dichotomy_of_thousand
293
+ dichotomy.refine
294
+ dichotomy.refine
295
+ expect(dichotomy.value_confidence_interval(0.8)).to eq([112, 666])
296
+ end
297
+
298
+ it 'returns the value confidence interval after three refinements' do
299
+ dichotomy = dichotomy_of_thousand
300
+ dichotomy.refine
301
+ dichotomy.refine
302
+ dichotomy.refine
303
+ expect(dichotomy.value_confidence_interval).to eq([112, 777])
304
+ end
305
+
306
+ it 'can return a confidence interval starting at 0 after refinements' do
307
+ dichotomy = Sapor::Dichotomy.new(0, 5, 1000)
308
+ dichotomy.refine
309
+ dichotomy.refine
310
+ expect(dichotomy.value_confidence_interval).to eq([0, 555])
311
+ end
312
+
313
+ it 'can return a confidence interval ending at population size after ' \
314
+ 'refinements' do
315
+ dichotomy = Sapor::Dichotomy.new(5, 5, 1000)
316
+ dichotomy.refine
317
+ dichotomy.refine
318
+ expect(dichotomy.value_confidence_interval).to eq([445, 1000])
319
+ end
320
+ end