sapor 0.3.3

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 (332) 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/Example-Catalonia.md +361 -0
  7. data/Example-Flanders.md +486 -0
  8. data/Example-Greece.md +25 -0
  9. data/Example-Oslo.md +678 -0
  10. data/Example-UnitedKingdom-Referendum.md +132 -0
  11. data/Examples.md +15 -0
  12. data/LICENSE +674 -0
  13. data/README.md +103 -0
  14. data/Rakefile +18 -0
  15. data/Technical Documentation.md +14 -0
  16. data/bin/create_installation_package.sh +49 -0
  17. data/bin/install.sh +45 -0
  18. data/bin/sapor.rb +24 -0
  19. data/bin/sapor.sh +106 -0
  20. data/data/hu/hungary-2014.txt +1680 -0
  21. data/data/hu/hungary_2014_screen_scraper.rb +48 -0
  22. data/data/hu/hungary_2014_to_psv.rb +80 -0
  23. data/data/hu/index-2014.txt +106 -0
  24. data/data/pl/2015-gl-lis-okr.csv +42 -0
  25. data/data/pl/poland_2015_to_psv.rb +79 -0
  26. data/data/pl/poland_2015_to_psv_with_ko_and_rsw.rb +94 -0
  27. data/data/pl/poland_2015_to_psv_with_ko_konf_kp_l_and_zp.rb +100 -0
  28. data/data/pl/poland_2015_to_psv_with_ko_sld_and_wi.rb +92 -0
  29. data/data/pl/poland_2015_to_psv_with_sld.rb +84 -0
  30. data/data/pl/poland_2015_to_psv_with_sld_and_wi.rb +85 -0
  31. data/data/uk/inject_ukip_2015_as_brexit_2019_in_2017.rb +54 -0
  32. data/data/uk/united_kingdom_2015.txt +651 -0
  33. data/data/uk/united_kingdom_2015_to_psv.rb +104 -0
  34. data/data/uk/united_kingdom_2017.txt +651 -0
  35. data/data/uk/united_kingdom_2017_to_psv.rb +104 -0
  36. data/data/uk/united_kingdom_2017_to_psv_with_brexit_and_chuk.rb +113 -0
  37. data/data/uk/united_kingdom_2017_to_psv_with_tig.rb +111 -0
  38. data/lib/sapor.rb +150 -0
  39. data/lib/sapor/binomials_cache.rb +45 -0
  40. data/lib/sapor/combinations_distribution.rb +222 -0
  41. data/lib/sapor/denominators.rb +67 -0
  42. data/lib/sapor/dichotomies.rb +138 -0
  43. data/lib/sapor/dichotomy.rb +164 -0
  44. data/lib/sapor/first_past_the_post.rb +82 -0
  45. data/lib/sapor/largest_remainder.rb +118 -0
  46. data/lib/sapor/log4r_logger.rb +49 -0
  47. data/lib/sapor/log_facade.rb +40 -0
  48. data/lib/sapor/multi_district_leveled_proportional.rb +64 -0
  49. data/lib/sapor/multi_district_proportional.rb +123 -0
  50. data/lib/sapor/multi_district_variable_threshold_proportional.rb +128 -0
  51. data/lib/sapor/number_formatter.rb +45 -0
  52. data/lib/sapor/options.rb +73 -0
  53. data/lib/sapor/poll.rb +286 -0
  54. data/lib/sapor/polychotomy.rb +200 -0
  55. data/lib/sapor/pseudorandom_multirange_enumerator.rb +87 -0
  56. data/lib/sapor/referendum_polychotomy.rb +165 -0
  57. data/lib/sapor/regional_data/area.rb +82 -0
  58. data/lib/sapor/regional_data/austria.rb +84 -0
  59. data/lib/sapor/regional_data/belgium-brussels-2014.psv +46 -0
  60. data/lib/sapor/regional_data/belgium-brussels-20190526.psv +33 -0
  61. data/lib/sapor/regional_data/belgium-flanders-2014.psv +80 -0
  62. data/lib/sapor/regional_data/belgium-flanders-20190526.psv +74 -0
  63. data/lib/sapor/regional_data/belgium-wallonia-2014.psv +114 -0
  64. data/lib/sapor/regional_data/belgium-wallonia-20190526.psv +93 -0
  65. data/lib/sapor/regional_data/belgium.rb +97 -0
  66. data/lib/sapor/regional_data/belgium_brussels.rb +62 -0
  67. data/lib/sapor/regional_data/belgium_flanders.rb +64 -0
  68. data/lib/sapor/regional_data/belgium_wallonia.rb +63 -0
  69. data/lib/sapor/regional_data/catalonia-2012-2015.psv +100 -0
  70. data/lib/sapor/regional_data/catalonia-2012.psv +87 -0
  71. data/lib/sapor/regional_data/catalonia-2015-jxcat.psv +68 -0
  72. data/lib/sapor/regional_data/catalonia-2015-no-jxsi.psv +68 -0
  73. data/lib/sapor/regional_data/catalonia-2015.psv +63 -0
  74. data/lib/sapor/regional_data/catalonia-jxcat.rb +109 -0
  75. data/lib/sapor/regional_data/catalonia-no-jxsi.rb +96 -0
  76. data/lib/sapor/regional_data/catalonia.rb +96 -0
  77. data/lib/sapor/regional_data/denmark-20150618-with-e-and-p.psv +164 -0
  78. data/lib/sapor/regional_data/denmark-20150618-with-e.psv +153 -0
  79. data/lib/sapor/regional_data/denmark-20150618-with-p.psv +153 -0
  80. data/lib/sapor/regional_data/denmark-20150618.psv +142 -0
  81. data/lib/sapor/regional_data/denmark.rb +128 -0
  82. data/lib/sapor/regional_data/denmark_with_e.rb +128 -0
  83. data/lib/sapor/regional_data/denmark_with_e_and_p.rb +128 -0
  84. data/lib/sapor/regional_data/denmark_with_p.rb +128 -0
  85. data/lib/sapor/regional_data/estonia.rb +88 -0
  86. data/lib/sapor/regional_data/european-union-great-britain-20140522-brexit-chuk.psv +172 -0
  87. data/lib/sapor/regional_data/european-union-great-britain-20140522.psv +146 -0
  88. data/lib/sapor/regional_data/european-union-great-britain-20190523.psv +141 -0
  89. data/lib/sapor/regional_data/european-union-ireland-2014-ia-ri-sd.psv +64 -0
  90. data/lib/sapor/regional_data/european-union-ireland-2014-ia-sd.psv +60 -0
  91. data/lib/sapor/regional_data/european-union-ireland-2014-ia.psv +56 -0
  92. data/lib/sapor/regional_data/european-union-ireland-2014-sd.psv +56 -0
  93. data/lib/sapor/regional_data/european-union-ireland-2014.psv +50 -0
  94. data/lib/sapor/regional_data/european-union-ireland-20190524-ia.psv +58 -0
  95. data/lib/sapor/regional_data/european-union-ireland-20190524.psv +52 -0
  96. data/lib/sapor/regional_data/european_union_27_austria.rb +76 -0
  97. data/lib/sapor/regional_data/european_union_27_croatia.rb +81 -0
  98. data/lib/sapor/regional_data/european_union_27_denmark.rb +77 -0
  99. data/lib/sapor/regional_data/european_union_27_estonia.rb +74 -0
  100. data/lib/sapor/regional_data/european_union_27_finland.rb +74 -0
  101. data/lib/sapor/regional_data/european_union_27_ireland.rb +96 -0
  102. data/lib/sapor/regional_data/european_union_27_ireland_with_ia.rb +97 -0
  103. data/lib/sapor/regional_data/european_union_27_italy.rb +84 -0
  104. data/lib/sapor/regional_data/european_union_27_netherlands.rb +81 -0
  105. data/lib/sapor/regional_data/european_union_27_poland.rb +84 -0
  106. data/lib/sapor/regional_data/european_union_27_romania.rb +78 -0
  107. data/lib/sapor/regional_data/european_union_27_slovakia.rb +80 -0
  108. data/lib/sapor/regional_data/european_union_27_spain.rb +82 -0
  109. data/lib/sapor/regional_data/european_union_27_sweden.rb +76 -0
  110. data/lib/sapor/regional_data/european_union_austria.rb +76 -0
  111. data/lib/sapor/regional_data/european_union_bulgaria.rb +81 -0
  112. data/lib/sapor/regional_data/european_union_croatia.rb +81 -0
  113. data/lib/sapor/regional_data/european_union_cyprus.rb +72 -0
  114. data/lib/sapor/regional_data/european_union_czech_republic.rb +82 -0
  115. data/lib/sapor/regional_data/european_union_denmark.rb +77 -0
  116. data/lib/sapor/regional_data/european_union_estonia.rb +74 -0
  117. data/lib/sapor/regional_data/european_union_finland.rb +74 -0
  118. data/lib/sapor/regional_data/european_union_flanders.rb +74 -0
  119. data/lib/sapor/regional_data/european_union_france.rb +84 -0
  120. data/lib/sapor/regional_data/european_union_france_2019.rb +84 -0
  121. data/lib/sapor/regional_data/european_union_french_community_of_belgium.rb +73 -0
  122. data/lib/sapor/regional_data/european_union_germany.rb +86 -0
  123. data/lib/sapor/regional_data/european_union_great_britain.rb +98 -0
  124. data/lib/sapor/regional_data/european_union_greece.rb +77 -0
  125. data/lib/sapor/regional_data/european_union_hungary.rb +76 -0
  126. data/lib/sapor/regional_data/european_union_ireland.rb +96 -0
  127. data/lib/sapor/regional_data/european_union_ireland_with_ia.rb +97 -0
  128. data/lib/sapor/regional_data/european_union_italy.rb +84 -0
  129. data/lib/sapor/regional_data/european_union_latvia.rb +81 -0
  130. data/lib/sapor/regional_data/european_union_lithuania.rb +80 -0
  131. data/lib/sapor/regional_data/european_union_luxembourg.rb +75 -0
  132. data/lib/sapor/regional_data/european_union_malta.rb +71 -0
  133. data/lib/sapor/regional_data/european_union_netherlands.rb +81 -0
  134. data/lib/sapor/regional_data/european_union_northern_ireland.rb +75 -0
  135. data/lib/sapor/regional_data/european_union_poland.rb +84 -0
  136. data/lib/sapor/regional_data/european_union_portugal.rb +75 -0
  137. data/lib/sapor/regional_data/european_union_romania.rb +78 -0
  138. data/lib/sapor/regional_data/european_union_slovakia.rb +81 -0
  139. data/lib/sapor/regional_data/european_union_slovenia.rb +85 -0
  140. data/lib/sapor/regional_data/european_union_spain.rb +82 -0
  141. data/lib/sapor/regional_data/european_union_sweden.rb +76 -0
  142. data/lib/sapor/regional_data/finland-20150419-with-sin.psv +224 -0
  143. data/lib/sapor/regional_data/finland-20150419.psv +212 -0
  144. data/lib/sapor/regional_data/finland.rb +107 -0
  145. data/lib/sapor/regional_data/finland_with_sin.rb +107 -0
  146. data/lib/sapor/regional_data/flanders-2014.psv +96 -0
  147. data/lib/sapor/regional_data/flanders-20190526.psv +87 -0
  148. data/lib/sapor/regional_data/flanders.rb +115 -0
  149. data/lib/sapor/regional_data/france.rb +38 -0
  150. data/lib/sapor/regional_data/greece.rb +92 -0
  151. data/lib/sapor/regional_data/hungary-2014.psv +2104 -0
  152. data/lib/sapor/regional_data/hungary.rb +116 -0
  153. data/lib/sapor/regional_data/iceland-20161029-midflokkurinn.psv +94 -0
  154. data/lib/sapor/regional_data/iceland-20161029.psv +88 -0
  155. data/lib/sapor/regional_data/iceland-20171028.psv +85 -0
  156. data/lib/sapor/regional_data/iceland.rb +133 -0
  157. data/lib/sapor/regional_data/latvia-20141004-kpv-p-par.psv +109 -0
  158. data/lib/sapor/regional_data/latvia-20141004-kpv-par.psv +103 -0
  159. data/lib/sapor/regional_data/latvia-20141004-kpv.psv +97 -0
  160. data/lib/sapor/regional_data/latvia-20141004.psv +89 -0
  161. data/lib/sapor/regional_data/latvia.rb +112 -0
  162. data/lib/sapor/regional_data/latvia_kpv.rb +112 -0
  163. data/lib/sapor/regional_data/latvia_kpv_p_par.rb +112 -0
  164. data/lib/sapor/regional_data/latvia_kpv_par.rb +112 -0
  165. data/lib/sapor/regional_data/luxembourg-20131020.psv +76 -0
  166. data/lib/sapor/regional_data/luxembourg.rb +82 -0
  167. data/lib/sapor/regional_data/netherlands.rb +108 -0
  168. data/lib/sapor/regional_data/norway.rb +425 -0
  169. data/lib/sapor/regional_data/norwegian_municipality.rb +68 -0
  170. data/lib/sapor/regional_data/poland-20151025-with-ko-and-l-without-n-po-r-and-zl.psv +321 -0
  171. data/lib/sapor/regional_data/poland-20151025-with-ko-konf-kp-l-and-zp-without-k-k15-n-pis-po-psl-r-and-zl.psv +280 -0
  172. data/lib/sapor/regional_data/poland-20151025-with-ko-sld-and-wi-without-n-po-and-zl.psv +403 -0
  173. data/lib/sapor/regional_data/poland-20151025-with-sld-and-wi-without-zl.psv +444 -0
  174. data/lib/sapor/regional_data/poland-20151025-with-sld-without-zl.psv +403 -0
  175. data/lib/sapor/regional_data/poland-20151025.psv +403 -0
  176. data/lib/sapor/regional_data/poland.rb +125 -0
  177. data/lib/sapor/regional_data/poland_with_ko_and_l_without_n_po_r_and_zl.rb +122 -0
  178. data/lib/sapor/regional_data/poland_with_ko_konf_kp_l_and_zp_without_k_k15_n_pis_po_psl_r_and_zl.rb +123 -0
  179. data/lib/sapor/regional_data/poland_with_ko_sld_and_wi_without_n_po_and_zl.rb +125 -0
  180. data/lib/sapor/regional_data/poland_with_sld_and_wi_without_zl.rb +126 -0
  181. data/lib/sapor/regional_data/poland_with_sld_without_zl.rb +126 -0
  182. data/lib/sapor/regional_data/portugal-20151004-with-a-and-ch-without-paf.psv +438 -0
  183. data/lib/sapor/regional_data/portugal-20151004-with-a-and-il-without-paf.psv +438 -0
  184. data/lib/sapor/regional_data/portugal-20151004-with-a-ch-and-il-without-paf.psv +461 -0
  185. data/lib/sapor/regional_data/portugal-20151004-with-a-without-paf.psv +415 -0
  186. data/lib/sapor/regional_data/portugal-20151004-with-ch-and-il-without-paf.psv +438 -0
  187. data/lib/sapor/regional_data/portugal-20151004-without-paf.psv +392 -0
  188. data/lib/sapor/regional_data/portugal-20151004.psv +370 -0
  189. data/lib/sapor/regional_data/portugal.rb +101 -0
  190. data/lib/sapor/regional_data/portugal_with_a_and_ch_without_paf.rb +92 -0
  191. data/lib/sapor/regional_data/portugal_with_a_and_il_without_paf.rb +92 -0
  192. data/lib/sapor/regional_data/portugal_with_a_ch_and_il_without_paf.rb +92 -0
  193. data/lib/sapor/regional_data/portugal_with_a_without_paf.rb +92 -0
  194. data/lib/sapor/regional_data/portugal_with_ch_and_il_without_paf.rb +92 -0
  195. data/lib/sapor/regional_data/portugal_without_paf.rb +92 -0
  196. data/lib/sapor/regional_data/slovakia.rb +81 -0
  197. data/lib/sapor/regional_data/slovenia.rb +114 -0
  198. data/lib/sapor/regional_data/spain-20160626.psv +619 -0
  199. data/lib/sapor/regional_data/spain.rb +136 -0
  200. data/lib/sapor/regional_data/sweden.rb +92 -0
  201. data/lib/sapor/regional_data/sweden_20140914.rb +89 -0
  202. data/lib/sapor/regional_data/united_kingdom-2015.psv +4358 -0
  203. data/lib/sapor/regional_data/united_kingdom-20170608-brexit-chuk.psv +5154 -0
  204. data/lib/sapor/regional_data/united_kingdom-20170608-brexit.psv +4521 -0
  205. data/lib/sapor/regional_data/united_kingdom-20170608-tig.psv +4529 -0
  206. data/lib/sapor/regional_data/united_kingdom-20170608.psv +3894 -0
  207. data/lib/sapor/regional_data/united_kingdom.rb +94 -0
  208. data/lib/sapor/regional_data/united_kingdom_with_brexit.rb +110 -0
  209. data/lib/sapor/regional_data/united_kingdom_with_brexit_and_chuk.rb +111 -0
  210. data/lib/sapor/regional_data/united_kingdom_with_tig.rb +111 -0
  211. data/lib/sapor/regional_data/utopia.rb +66 -0
  212. data/lib/sapor/regional_data/wallonia-2014.psv +101 -0
  213. data/lib/sapor/regional_data/wallonia-20190526.psv +88 -0
  214. data/lib/sapor/regional_data/wallonia.rb +112 -0
  215. data/lib/sapor/representatives_polychotomy.rb +338 -0
  216. data/lib/sapor/single_district_proportional.rb +75 -0
  217. data/sapor.gemspec +35 -0
  218. data/spec/integration/area_spec.rb +28 -0
  219. data/spec/integration/poll_spec.rb +112 -0
  220. data/spec/integration/sample.poll +8 -0
  221. data/spec/spec_helper.rb +31 -0
  222. data/spec/unit/area_spec.rb +115 -0
  223. data/spec/unit/austria_spec.rb +76 -0
  224. data/spec/unit/belgium_brussels_spec.rb +58 -0
  225. data/spec/unit/belgium_flanders_spec.rb +62 -0
  226. data/spec/unit/belgium_spec.rb +26 -0
  227. data/spec/unit/belgium_wallonia_spec.rb +65 -0
  228. data/spec/unit/binomials_cache_spec.rb +34 -0
  229. data/spec/unit/catalonia_spec.rb +74 -0
  230. data/spec/unit/combinations_distribution_spec.rb +241 -0
  231. data/spec/unit/denmark_spec.rb +56 -0
  232. data/spec/unit/denmark_with_e_and_p_spec.rb +58 -0
  233. data/spec/unit/denmark_with_e_spec.rb +57 -0
  234. data/spec/unit/denmark_with_p_spec.rb +57 -0
  235. data/spec/unit/denominators_spec.rb +40 -0
  236. data/spec/unit/dichotomies_spec.rb +154 -0
  237. data/spec/unit/dichotomy_spec.rb +320 -0
  238. data/spec/unit/estonia_spec.rb +65 -0
  239. data/spec/unit/european_union_27_austria_spec.rb +61 -0
  240. data/spec/unit/european_union_27_croatia_spec.rb +60 -0
  241. data/spec/unit/european_union_27_denmark_spec.rb +62 -0
  242. data/spec/unit/european_union_27_estonia_spec.rb +94 -0
  243. data/spec/unit/european_union_27_finland_spec.rb +75 -0
  244. data/spec/unit/european_union_27_ireland_spec.rb +72 -0
  245. data/spec/unit/european_union_27_ireland_with_ia_spec.rb +74 -0
  246. data/spec/unit/european_union_27_italy_spec.rb +69 -0
  247. data/spec/unit/european_union_27_netherlands_spec.rb +81 -0
  248. data/spec/unit/european_union_27_poland_spec.rb +69 -0
  249. data/spec/unit/european_union_27_romania_spec.rb +67 -0
  250. data/spec/unit/european_union_27_slovakia_spec.rb +111 -0
  251. data/spec/unit/european_union_27_spain_spec.rb +130 -0
  252. data/spec/unit/european_union_27_sweden_spec.rb +89 -0
  253. data/spec/unit/european_union_austria_spec.rb +61 -0
  254. data/spec/unit/european_union_bulgaria_spec.rb +97 -0
  255. data/spec/unit/european_union_croatia_spec.rb +59 -0
  256. data/spec/unit/european_union_cyprus_spec.rb +65 -0
  257. data/spec/unit/european_union_czech_republic_spec.rb +125 -0
  258. data/spec/unit/european_union_denmark_spec.rb +61 -0
  259. data/spec/unit/european_union_estonia_spec.rb +93 -0
  260. data/spec/unit/european_union_finland_spec.rb +75 -0
  261. data/spec/unit/european_union_flanders_spec.rb +56 -0
  262. data/spec/unit/european_union_france_2019_spec.rb +73 -0
  263. data/spec/unit/european_union_france_spec.rb +73 -0
  264. data/spec/unit/european_union_french_community_of_belgium_spec.rb +61 -0
  265. data/spec/unit/european_union_germany_spec.rb +90 -0
  266. data/spec/unit/european_union_great_britain_spec.rb +87 -0
  267. data/spec/unit/european_union_greece_spec.rb +148 -0
  268. data/spec/unit/european_union_hungary_spec.rb +57 -0
  269. data/spec/unit/european_union_ireland_spec.rb +72 -0
  270. data/spec/unit/european_union_ireland_with_ia_spec.rb +74 -0
  271. data/spec/unit/european_union_italy_spec.rb +69 -0
  272. data/spec/unit/european_union_latvia_spec.rb +76 -0
  273. data/spec/unit/european_union_lithuania_spec.rb +68 -0
  274. data/spec/unit/european_union_luxembourg_spec.rb +63 -0
  275. data/spec/unit/european_union_malta_spec.rb +60 -0
  276. data/spec/unit/european_union_netherlands_spec.rb +81 -0
  277. data/spec/unit/european_union_northern_ireland_spec.rb +66 -0
  278. data/spec/unit/european_union_poland_spec.rb +69 -0
  279. data/spec/unit/european_union_portugal_spec.rb +77 -0
  280. data/spec/unit/european_union_romania_spec.rb +67 -0
  281. data/spec/unit/european_union_slovakia_spec.rb +111 -0
  282. data/spec/unit/european_union_slovenia_spec.rb +77 -0
  283. data/spec/unit/european_union_spain_spec.rb +129 -0
  284. data/spec/unit/european_union_sweden_spec.rb +89 -0
  285. data/spec/unit/finland_spec.rb +65 -0
  286. data/spec/unit/finland_with_sin_spec.rb +67 -0
  287. data/spec/unit/first_past_the_post_spec.rb +54 -0
  288. data/spec/unit/flanders_spec.rb +70 -0
  289. data/spec/unit/france_spec.rb +32 -0
  290. data/spec/unit/greece_spec.rb +118 -0
  291. data/spec/unit/hungary_spec.rb +132 -0
  292. data/spec/unit/iceland_spec.rb +57 -0
  293. data/spec/unit/largest_remainder_spec.rb +79 -0
  294. data/spec/unit/latvia_kpv_p_par_spec.rb +38 -0
  295. data/spec/unit/latvia_kpv_par_spec.rb +38 -0
  296. data/spec/unit/latvia_kpv_spec.rb +38 -0
  297. data/spec/unit/latvia_spec.rb +60 -0
  298. data/spec/unit/luxembourg_spec.rb +54 -0
  299. data/spec/unit/multi_district_leveled_proportional_spec.rb +49 -0
  300. data/spec/unit/multi_district_proportional_spec.rb +81 -0
  301. data/spec/unit/netherlands_spec.rb +107 -0
  302. data/spec/unit/norway_spec.rb +64 -0
  303. data/spec/unit/norwegian_municipality_spec.rb +89 -0
  304. data/spec/unit/number_formatter_spec.rb +173 -0
  305. data/spec/unit/poland_spec.rb +62 -0
  306. data/spec/unit/poland_with_ko_and_l_without_n_po_r_and_zl_spec.rb +60 -0
  307. data/spec/unit/poland_with_ko_konf_kp_l_and_zp_without_k_k15_n_pis_po_psl_r_and_zl_spec.rb +59 -0
  308. data/spec/unit/poland_with_ko_sld_and_wi_without_n_po_and_zl_spec.rb +62 -0
  309. data/spec/unit/poland_with_sld_and_wi_without_zl_spec.rb +63 -0
  310. data/spec/unit/poland_with_sld_without_zl_spec.rb +62 -0
  311. data/spec/unit/poll_spec.rb +110 -0
  312. data/spec/unit/portugal_spec.rb +66 -0
  313. data/spec/unit/portugal_with_a_and_ch_without_paf_spec.rb +68 -0
  314. data/spec/unit/portugal_with_a_and_il_without_paf_spec.rb +68 -0
  315. data/spec/unit/portugal_with_a_ch_and_il_without_paf_spec.rb +69 -0
  316. data/spec/unit/portugal_with_a_without_paf_spec.rb +67 -0
  317. data/spec/unit/portugal_with_ch_and_il_without_paf_spec.rb +68 -0
  318. data/spec/unit/portugal_without_paf_spec.rb +66 -0
  319. data/spec/unit/pseudorandom_multirange_enumerator_spec.rb +82 -0
  320. data/spec/unit/referendum_polychotomy_spec.rb +289 -0
  321. data/spec/unit/representatives_polychotomy_spec.rb +332 -0
  322. data/spec/unit/slovakia_spec.rb +99 -0
  323. data/spec/unit/slovenia_spec.rb +80 -0
  324. data/spec/unit/spain_spec.rb +101 -0
  325. data/spec/unit/sweden_20140914_spec.rb +112 -0
  326. data/spec/unit/sweden_spec.rb +113 -0
  327. data/spec/unit/united_kingdom_spec.rb +65 -0
  328. data/spec/unit/united_kingdom_with_brexit_and_chuk_spec.rb +67 -0
  329. data/spec/unit/united_kingdom_with_brexit_spec.rb +66 -0
  330. data/spec/unit/united_kingdom_with_tig_spec.rb +66 -0
  331. data/spec/unit/wallonia_spec.rb +70 -0
  332. metadata +490 -0
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 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 'large_binomials'
21
+
22
+ module Sapor
23
+ #
24
+ # Caches binomials.
25
+ #
26
+ class BinomialsCache
27
+ include Singleton
28
+
29
+ def initialize
30
+ @cache = {}
31
+ end
32
+
33
+ def self.binomial(k, n)
34
+ instance.get_binomial(k, n)
35
+ end
36
+
37
+ def get_binomial(k, n)
38
+ @cache[n] = {} unless @cache.key?(n)
39
+ unless @cache[n].key?(k)
40
+ @cache[n][k] = k.large_float_binomial_by_product_of_divisions(n)
41
+ end
42
+ @cache[n][k]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,222 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 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
+ module Sapor
21
+ #
22
+ # Represents the distribution of combinations over a set of values. This is
23
+ # basically a simple hash with some helper methods for the Sapor domain.
24
+ #
25
+ class CombinationsDistribution
26
+ def initialize
27
+ @distribution = {}
28
+ end
29
+
30
+ def []=(value, combinations)
31
+ @distribution[value] = combinations
32
+ end
33
+
34
+ def [](value)
35
+ @distribution[value]
36
+ end
37
+
38
+ def +(other)
39
+ sum = CombinationsDistribution.new
40
+ @distribution.keys.each { |key| sum[key] = self[key] }
41
+ other.values.each do |key|
42
+ if self[key].nil?
43
+ sum[key] = other[key]
44
+ else
45
+ sum[key] += other[key]
46
+ end
47
+ end
48
+ sum
49
+ end
50
+
51
+ def empty?
52
+ @distribution.empty?
53
+ end
54
+
55
+ def interval_probabilities(intervals, population_size)
56
+ value_interval_probabilities(intervals.map {|interval| [interval.first * population_size, interval.last * population_size]})
57
+ end
58
+
59
+ def value_interval_probabilities(value_intervals)
60
+ total = @distribution.values.inject(:+)
61
+ value_intervals.map do | value_interval |
62
+ distribution_in_interval = @distribution.select do |k, _|
63
+ k >= value_interval.first && k < value_interval.last
64
+ end
65
+ if distribution_in_interval.empty?
66
+ 0
67
+ else
68
+ in_interval = distribution_in_interval.values.inject(:+)
69
+ probability = in_interval / total
70
+ probability.mantissa * (10**probability.exponent)
71
+ end
72
+ end
73
+ end
74
+
75
+ def value_threshold_probability(threshold_value)
76
+ total = @distribution.values.inject(:+)
77
+ distribution_over_threshold = @distribution.select do |k, _|
78
+ k >= threshold_value
79
+ end
80
+ if distribution_over_threshold.empty?
81
+ 0
82
+ else
83
+ over_threshold = distribution_over_threshold.values.inject(:+)
84
+ probability = over_threshold / total
85
+ probability.mantissa * (10**probability.exponent)
86
+ end
87
+ end
88
+
89
+ def probabilities(values)
90
+ total = @distribution.values.inject(:+)
91
+ values.map do | value |
92
+ if @distribution.key?(value)
93
+ probability = @distribution[value] / total
94
+ probability.mantissa * (10**probability.exponent)
95
+ else
96
+ 0
97
+ end
98
+ end
99
+ end
100
+
101
+ def probability(value)
102
+ if @distribution.key?(value)
103
+ total = @distribution.values.inject(:+)
104
+ probability = @distribution[value] / total
105
+ probability.mantissa * (10**probability.exponent)
106
+ else
107
+ 0
108
+ end
109
+ end
110
+
111
+ def threshold_probability(threshold, population_size)
112
+ value_threshold_probability(population_size * threshold)
113
+ end
114
+
115
+ def size
116
+ @distribution.size
117
+ end
118
+
119
+ def values
120
+ @distribution.keys
121
+ end
122
+
123
+ def most_probable_value
124
+ @distribution.max { |a, b| a.last <=> b.last }[0]
125
+ end
126
+
127
+ # Given all fractions rounded to one decimal, returns the one that has the
128
+ # highest probability.
129
+ def most_probable_rounded_fraction(population_size)
130
+ rf_combinations = \
131
+ calculate_rounded_fractions_combinations(population_size)
132
+ max_probability = rf_combinations.values.max
133
+ opt_rfs = rf_combinations.reject { |_, v| v < max_probability }.keys
134
+ opt_rfs.sort[opt_rfs.size / 2]
135
+ end
136
+
137
+ def confidence_interval(level, population_size = nil)
138
+ combinations_sum = @distribution.values.inject(:+)
139
+ one_side_level = (1 - level) / 2
140
+ one_side_threshold = combinations_sum * one_side_level
141
+ bottom = find_confidence_interval_bottom(one_side_threshold,
142
+ population_size)
143
+ top = find_confidence_interval_top(one_side_threshold, population_size)
144
+ [bottom, top]
145
+ end
146
+
147
+ def confidence_interval_values(level)
148
+ interval = confidence_interval(level)
149
+ @distribution.keys.reject do |value|
150
+ value < interval.first || value > interval.last
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def confidence_interval_index(sorted_combinations, one_side_threshold)
157
+ i = 0
158
+ sum_to_i = sorted_combinations[i][1]
159
+ while sum_to_i < one_side_threshold
160
+ i += 1
161
+ sum_to_i += sorted_combinations[i][1]
162
+ end
163
+ i
164
+ end
165
+
166
+ def find_confidence_interval_bottom(one_side_threshold, population_size)
167
+ sorted_combinations = @distribution.sort
168
+ i = confidence_interval_index(sorted_combinations, one_side_threshold)
169
+ if i == 0
170
+ population_size.nil? ? sorted_combinations[0][0] : 0
171
+ else
172
+ (sorted_combinations[i - 1][0] + sorted_combinations[i][0] + 1) / 2
173
+ end
174
+ end
175
+
176
+ def find_confidence_interval_top(one_side_threshold, population_size)
177
+ sorted_combinations = @distribution.sort.reverse
178
+ i = confidence_interval_index(sorted_combinations, one_side_threshold)
179
+ if i == 0
180
+ population_size.nil? ? sorted_combinations[0][0] : population_size
181
+ else
182
+ (sorted_combinations[i - 1][0] + sorted_combinations[i][0]) / 2
183
+ end
184
+ end
185
+
186
+ def calculate_rounded_fractions_combinations(population_size)
187
+ sorted_combinations = @distribution.sort
188
+ combinations_by_interval = []
189
+ sorted_combinations.each_with_index do |c, ix|
190
+ if ix == 0
191
+ bottom = 0
192
+ else
193
+ bottom = combinations_by_interval[ix - 1].first.last + 1
194
+ end
195
+ if ix == sorted_combinations.size - 1
196
+ top = population_size
197
+ else
198
+ top = (sorted_combinations[ix].first + sorted_combinations[ix + 1].first) / 2
199
+ end
200
+ combinations_by_interval << [[bottom, top], c.last]
201
+ end
202
+ result = Hash.new(0.to_lf)
203
+ combinations_by_interval.each do |i_c|
204
+ bottom = (i_c.first.first.to_f / population_size).round(3)
205
+ top = (i_c.first.last.to_f / population_size).round(3)
206
+ ix = bottom
207
+ loop do
208
+ interval_bottom = ((ix - 0.0005) * population_size).ceil
209
+ interval_top = ((ix + 0.0005) * population_size).ceil - 1
210
+ if (((interval_bottom + interval_top) / 2).to_f / population_size).round(3) == ix
211
+ low = [i_c.first.first, interval_bottom].max
212
+ high = [i_c.first.last, interval_top].min
213
+ result[ix] += i_c.last * (high + 1 - low)
214
+ end
215
+ break if ix == top
216
+ ix = (ix + 0.001).round(3)
217
+ end
218
+ end
219
+ result
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # Statistical Analysis of Polling Results (SAPoR)
3
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
4
+ #
5
+ # This file is part of SAPoR.
6
+ #
7
+ # SAPoR is free software: you can redistribute it and/or modify it under the
8
+ # terms of the GNU General Public License as published by the Free Software
9
+ # Foundation, either version 3 of the License, or (at your option) any later
10
+ # version.
11
+ #
12
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15
+ #
16
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
17
+ #
18
+
19
+ module Sapor
20
+ #
21
+ # Class building the denominators for D'Hondt.
22
+ #
23
+ class DhondtDenominators
24
+ def self.get(size)
25
+ Range.new(1, size)
26
+ end
27
+ end
28
+
29
+ #
30
+ # Class building the denominators for modified D'Hondt raising the
31
+ # denominators to the power 0.9.
32
+ #
33
+ class Dhondt09Denominators
34
+ def self.get(size)
35
+ Range.new(1, size).map { |a| a**0.9 }
36
+ end
37
+ end
38
+
39
+ #
40
+ # Class building the denominators for Sainte-Lague.
41
+ #
42
+ class SainteLagueDenominators
43
+ def self.get(size)
44
+ Range.new(1, size).map { |a| a * 2 - 1 }
45
+ end
46
+ end
47
+
48
+ #
49
+ # Class building the denominators for the modified Sainte-Lague starting with
50
+ # 1.2.
51
+ #
52
+ class SainteLague12Denominators
53
+ def self.get(size)
54
+ Range.new(1, size).map { |a| a.equal?(1) ? 1.2 : a * 2 - 1 }
55
+ end
56
+ end
57
+
58
+ #
59
+ # Class building the denominators for the modified Sainte-Lague starting with
60
+ # 1.4.
61
+ #
62
+ class SainteLague14Denominators
63
+ def self.get(size)
64
+ Range.new(1, size).map { |a| a.equal?(1) ? 1.4 : a * 2 - 1 }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,138 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 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
+ module Sapor
21
+ #
22
+ # Represents a set of dichotomies.
23
+ #
24
+ class Dichotomies
25
+ include NumberFormatter
26
+
27
+ def initialize(results, population_size, threshold = nil)
28
+ sample_size = results.values.inject(:+)
29
+ @dichotomy_hash = {}
30
+ results.each_pair do |choice, number|
31
+ @dichotomy_hash[choice] = Dichotomy.new(number, sample_size,
32
+ population_size)
33
+ end
34
+ @threshold = threshold
35
+ end
36
+
37
+ def refine
38
+ @dichotomy_hash.values.each(&:refine)
39
+ end
40
+
41
+ def error_estimate
42
+ @dichotomy_hash.values.map(&:error_estimate).max
43
+ end
44
+
45
+ def confidence_interval_values(choice, level)
46
+ @dichotomy_hash[choice].confidence_interval_values(level)
47
+ end
48
+
49
+ def report
50
+ choice_lengths = @dichotomy_hash.keys.map(&:length)
51
+ choice_lengths << 6
52
+ max_choice_width = choice_lengths.max
53
+ sorted_choices = sort_choices_by_label_and_mpv
54
+ lines = sorted_choices.map do |choice|
55
+ create_report_line(choice, @dichotomy_hash[choice], max_choice_width)
56
+ end
57
+ "Most probable fractions and 95% confidence intervals:\n" +
58
+ 'Choice'.ljust(max_choice_width) + ' MPF CI(95%)' +
59
+ (@threshold.nil? ? '' : ' P(≥' + (100 * @threshold).to_i.to_s +
60
+ '%)') +
61
+ "\n" + lines.join("\n")
62
+ end
63
+
64
+ def progress_report
65
+ size = @dichotomy_hash.values.first.values.size
66
+ "Number of data points: #{with_thousands_separator(size)}."
67
+ end
68
+
69
+ def size
70
+ @dichotomy_hash.values.first.values.size
71
+ end
72
+
73
+ def write_outputs(filename)
74
+ write_confidence_intervals(filename)
75
+ write_probabilities(filename)
76
+ end
77
+
78
+ private
79
+
80
+ def compare_choices_by_label_and_mpv(a, b)
81
+ if a == OTHER
82
+ 1
83
+ elsif b == OTHER
84
+ -1
85
+ else
86
+ mpv_a = @dichotomy_hash[a].most_probable_value
87
+ mpv_b = @dichotomy_hash[b].most_probable_value
88
+ mpv_a == mpv_b ? a <=> b : mpv_b <=> mpv_a
89
+ end
90
+ end
91
+
92
+ def sort_choices_by_label_and_mpv
93
+ @dichotomy_hash.keys.sort do |a, b|
94
+ compare_choices_by_label_and_mpv(a, b)
95
+ end
96
+ end
97
+
98
+ def create_report_line(choice, dichotomy, max_choice_width)
99
+ choice.ljust(max_choice_width) + ' ' + \
100
+ six_char_percentage(dichotomy.most_probable_fraction) + ' ' + \
101
+ six_char_percentage(dichotomy.confidence_interval.first) + '–' + \
102
+ six_char_percentage(dichotomy.confidence_interval.last) +
103
+ (@threshold.nil? ? '' : ' ' +
104
+ six_char_percentage(dichotomy.threshold_probability(@threshold)))
105
+ end
106
+
107
+ def write_confidence_intervals(filename)
108
+ ci_filename = filename.gsub('.poll', '-dichotomies-confidence-intervals.psv')
109
+ File.open(ci_filename, 'w') do |file|
110
+ file.puts('Choice | CI(80%) Bottom | CI(80%) Top | CI(90%) Bottom | CI(90%) Top | CI(95%) Bottom | CI(95%) Top | CI(99%) Bottom | CI(99%) Top')
111
+ @dichotomy_hash.each_pair do | choice, dichotomy |
112
+ ci80 = dichotomy.confidence_interval(0.8)
113
+ ci90 = dichotomy.confidence_interval(0.9)
114
+ ci95 = dichotomy.confidence_interval(0.95)
115
+ ci99 = dichotomy.confidence_interval(0.99)
116
+ file.puts(choice + ' | ' + ci80.first.to_s + ' | ' + \
117
+ ci80.last.to_s + ' | ' + ci90.first.to_s + ' | ' + \
118
+ ci90.last.to_s + ' | ' + ci95.first.to_s + ' | ' + \
119
+ ci95.last.to_s + ' | ' + ci99.first.to_s + ' | ' + \
120
+ ci99.last.to_s)
121
+ end
122
+ end
123
+ end
124
+
125
+ def write_probabilities(filename)
126
+ ci_filename = filename.gsub('.poll', '-dichotomies-probabilities.psv')
127
+ File.open(ci_filename, 'w') do |file|
128
+ intervals = Range.new(0, 1999).map{ | i | [i.to_f / 2000, (i + 1).to_f / 2000]}
129
+ file.puts('Choice | ' + intervals.map{|a| "#{sprintf('%.4f', a.first)}–#{sprintf('%.4f', a.last)}"}.join(' | '))
130
+ @dichotomy_hash.each_pair do | choice, dichotomy |
131
+ unless choice == OTHER
132
+ file.puts(choice + ' | ' + dichotomy.interval_probabilities(intervals).join(' | '))
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end