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.
- checksums.yaml +7 -0
- data/Area Class Diagram.dia +0 -0
- data/Area Class Diagram.png +0 -0
- data/Class Diagram.dia +0 -0
- data/Class Diagram.png +0 -0
- data/Example-Catalonia.md +361 -0
- data/Example-Flanders.md +486 -0
- data/Example-Greece.md +25 -0
- data/Example-Oslo.md +678 -0
- data/Example-UnitedKingdom-Referendum.md +132 -0
- data/Examples.md +15 -0
- data/LICENSE +674 -0
- data/README.md +103 -0
- data/Rakefile +18 -0
- data/Technical Documentation.md +14 -0
- data/bin/create_installation_package.sh +49 -0
- data/bin/install.sh +45 -0
- data/bin/sapor.rb +24 -0
- data/bin/sapor.sh +106 -0
- data/data/hu/hungary-2014.txt +1680 -0
- data/data/hu/hungary_2014_screen_scraper.rb +48 -0
- data/data/hu/hungary_2014_to_psv.rb +80 -0
- data/data/hu/index-2014.txt +106 -0
- data/data/pl/2015-gl-lis-okr.csv +42 -0
- data/data/pl/poland_2015_to_psv.rb +79 -0
- data/data/pl/poland_2015_to_psv_with_ko_and_rsw.rb +94 -0
- data/data/pl/poland_2015_to_psv_with_ko_konf_kp_l_and_zp.rb +100 -0
- data/data/pl/poland_2015_to_psv_with_ko_sld_and_wi.rb +92 -0
- data/data/pl/poland_2015_to_psv_with_sld.rb +84 -0
- data/data/pl/poland_2015_to_psv_with_sld_and_wi.rb +85 -0
- data/data/uk/inject_ukip_2015_as_brexit_2019_in_2017.rb +54 -0
- data/data/uk/united_kingdom_2015.txt +651 -0
- data/data/uk/united_kingdom_2015_to_psv.rb +104 -0
- data/data/uk/united_kingdom_2017.txt +651 -0
- data/data/uk/united_kingdom_2017_to_psv.rb +104 -0
- data/data/uk/united_kingdom_2017_to_psv_with_brexit_and_chuk.rb +113 -0
- data/data/uk/united_kingdom_2017_to_psv_with_tig.rb +111 -0
- data/lib/sapor.rb +150 -0
- data/lib/sapor/binomials_cache.rb +45 -0
- data/lib/sapor/combinations_distribution.rb +222 -0
- data/lib/sapor/denominators.rb +67 -0
- data/lib/sapor/dichotomies.rb +138 -0
- data/lib/sapor/dichotomy.rb +164 -0
- data/lib/sapor/first_past_the_post.rb +82 -0
- data/lib/sapor/largest_remainder.rb +118 -0
- data/lib/sapor/log4r_logger.rb +49 -0
- data/lib/sapor/log_facade.rb +40 -0
- data/lib/sapor/multi_district_leveled_proportional.rb +64 -0
- data/lib/sapor/multi_district_proportional.rb +123 -0
- data/lib/sapor/multi_district_variable_threshold_proportional.rb +128 -0
- data/lib/sapor/number_formatter.rb +45 -0
- data/lib/sapor/options.rb +73 -0
- data/lib/sapor/poll.rb +286 -0
- data/lib/sapor/polychotomy.rb +200 -0
- data/lib/sapor/pseudorandom_multirange_enumerator.rb +87 -0
- data/lib/sapor/referendum_polychotomy.rb +165 -0
- data/lib/sapor/regional_data/area.rb +82 -0
- data/lib/sapor/regional_data/austria.rb +84 -0
- data/lib/sapor/regional_data/belgium-brussels-2014.psv +46 -0
- data/lib/sapor/regional_data/belgium-brussels-20190526.psv +33 -0
- data/lib/sapor/regional_data/belgium-flanders-2014.psv +80 -0
- data/lib/sapor/regional_data/belgium-flanders-20190526.psv +74 -0
- data/lib/sapor/regional_data/belgium-wallonia-2014.psv +114 -0
- data/lib/sapor/regional_data/belgium-wallonia-20190526.psv +93 -0
- data/lib/sapor/regional_data/belgium.rb +97 -0
- data/lib/sapor/regional_data/belgium_brussels.rb +62 -0
- data/lib/sapor/regional_data/belgium_flanders.rb +64 -0
- data/lib/sapor/regional_data/belgium_wallonia.rb +63 -0
- data/lib/sapor/regional_data/catalonia-2012-2015.psv +100 -0
- data/lib/sapor/regional_data/catalonia-2012.psv +87 -0
- data/lib/sapor/regional_data/catalonia-2015-jxcat.psv +68 -0
- data/lib/sapor/regional_data/catalonia-2015-no-jxsi.psv +68 -0
- data/lib/sapor/regional_data/catalonia-2015.psv +63 -0
- data/lib/sapor/regional_data/catalonia-jxcat.rb +109 -0
- data/lib/sapor/regional_data/catalonia-no-jxsi.rb +96 -0
- data/lib/sapor/regional_data/catalonia.rb +96 -0
- data/lib/sapor/regional_data/denmark-20150618-with-e-and-p.psv +164 -0
- data/lib/sapor/regional_data/denmark-20150618-with-e.psv +153 -0
- data/lib/sapor/regional_data/denmark-20150618-with-p.psv +153 -0
- data/lib/sapor/regional_data/denmark-20150618.psv +142 -0
- data/lib/sapor/regional_data/denmark.rb +128 -0
- data/lib/sapor/regional_data/denmark_with_e.rb +128 -0
- data/lib/sapor/regional_data/denmark_with_e_and_p.rb +128 -0
- data/lib/sapor/regional_data/denmark_with_p.rb +128 -0
- data/lib/sapor/regional_data/estonia.rb +88 -0
- data/lib/sapor/regional_data/european-union-great-britain-20140522-brexit-chuk.psv +172 -0
- data/lib/sapor/regional_data/european-union-great-britain-20140522.psv +146 -0
- data/lib/sapor/regional_data/european-union-great-britain-20190523.psv +141 -0
- data/lib/sapor/regional_data/european-union-ireland-2014-ia-ri-sd.psv +64 -0
- data/lib/sapor/regional_data/european-union-ireland-2014-ia-sd.psv +60 -0
- data/lib/sapor/regional_data/european-union-ireland-2014-ia.psv +56 -0
- data/lib/sapor/regional_data/european-union-ireland-2014-sd.psv +56 -0
- data/lib/sapor/regional_data/european-union-ireland-2014.psv +50 -0
- data/lib/sapor/regional_data/european-union-ireland-20190524-ia.psv +58 -0
- data/lib/sapor/regional_data/european-union-ireland-20190524.psv +52 -0
- data/lib/sapor/regional_data/european_union_27_austria.rb +76 -0
- data/lib/sapor/regional_data/european_union_27_croatia.rb +81 -0
- data/lib/sapor/regional_data/european_union_27_denmark.rb +77 -0
- data/lib/sapor/regional_data/european_union_27_estonia.rb +74 -0
- data/lib/sapor/regional_data/european_union_27_finland.rb +74 -0
- data/lib/sapor/regional_data/european_union_27_ireland.rb +96 -0
- data/lib/sapor/regional_data/european_union_27_ireland_with_ia.rb +97 -0
- data/lib/sapor/regional_data/european_union_27_italy.rb +84 -0
- data/lib/sapor/regional_data/european_union_27_netherlands.rb +81 -0
- data/lib/sapor/regional_data/european_union_27_poland.rb +84 -0
- data/lib/sapor/regional_data/european_union_27_romania.rb +78 -0
- data/lib/sapor/regional_data/european_union_27_slovakia.rb +80 -0
- data/lib/sapor/regional_data/european_union_27_spain.rb +82 -0
- data/lib/sapor/regional_data/european_union_27_sweden.rb +76 -0
- data/lib/sapor/regional_data/european_union_austria.rb +76 -0
- data/lib/sapor/regional_data/european_union_bulgaria.rb +81 -0
- data/lib/sapor/regional_data/european_union_croatia.rb +81 -0
- data/lib/sapor/regional_data/european_union_cyprus.rb +72 -0
- data/lib/sapor/regional_data/european_union_czech_republic.rb +82 -0
- data/lib/sapor/regional_data/european_union_denmark.rb +77 -0
- data/lib/sapor/regional_data/european_union_estonia.rb +74 -0
- data/lib/sapor/regional_data/european_union_finland.rb +74 -0
- data/lib/sapor/regional_data/european_union_flanders.rb +74 -0
- data/lib/sapor/regional_data/european_union_france.rb +84 -0
- data/lib/sapor/regional_data/european_union_france_2019.rb +84 -0
- data/lib/sapor/regional_data/european_union_french_community_of_belgium.rb +73 -0
- data/lib/sapor/regional_data/european_union_germany.rb +86 -0
- data/lib/sapor/regional_data/european_union_great_britain.rb +98 -0
- data/lib/sapor/regional_data/european_union_greece.rb +77 -0
- data/lib/sapor/regional_data/european_union_hungary.rb +76 -0
- data/lib/sapor/regional_data/european_union_ireland.rb +96 -0
- data/lib/sapor/regional_data/european_union_ireland_with_ia.rb +97 -0
- data/lib/sapor/regional_data/european_union_italy.rb +84 -0
- data/lib/sapor/regional_data/european_union_latvia.rb +81 -0
- data/lib/sapor/regional_data/european_union_lithuania.rb +80 -0
- data/lib/sapor/regional_data/european_union_luxembourg.rb +75 -0
- data/lib/sapor/regional_data/european_union_malta.rb +71 -0
- data/lib/sapor/regional_data/european_union_netherlands.rb +81 -0
- data/lib/sapor/regional_data/european_union_northern_ireland.rb +75 -0
- data/lib/sapor/regional_data/european_union_poland.rb +84 -0
- data/lib/sapor/regional_data/european_union_portugal.rb +75 -0
- data/lib/sapor/regional_data/european_union_romania.rb +78 -0
- data/lib/sapor/regional_data/european_union_slovakia.rb +81 -0
- data/lib/sapor/regional_data/european_union_slovenia.rb +85 -0
- data/lib/sapor/regional_data/european_union_spain.rb +82 -0
- data/lib/sapor/regional_data/european_union_sweden.rb +76 -0
- data/lib/sapor/regional_data/finland-20150419-with-sin.psv +224 -0
- data/lib/sapor/regional_data/finland-20150419.psv +212 -0
- data/lib/sapor/regional_data/finland.rb +107 -0
- data/lib/sapor/regional_data/finland_with_sin.rb +107 -0
- data/lib/sapor/regional_data/flanders-2014.psv +96 -0
- data/lib/sapor/regional_data/flanders-20190526.psv +87 -0
- data/lib/sapor/regional_data/flanders.rb +115 -0
- data/lib/sapor/regional_data/france.rb +38 -0
- data/lib/sapor/regional_data/greece.rb +92 -0
- data/lib/sapor/regional_data/hungary-2014.psv +2104 -0
- data/lib/sapor/regional_data/hungary.rb +116 -0
- data/lib/sapor/regional_data/iceland-20161029-midflokkurinn.psv +94 -0
- data/lib/sapor/regional_data/iceland-20161029.psv +88 -0
- data/lib/sapor/regional_data/iceland-20171028.psv +85 -0
- data/lib/sapor/regional_data/iceland.rb +133 -0
- data/lib/sapor/regional_data/latvia-20141004-kpv-p-par.psv +109 -0
- data/lib/sapor/regional_data/latvia-20141004-kpv-par.psv +103 -0
- data/lib/sapor/regional_data/latvia-20141004-kpv.psv +97 -0
- data/lib/sapor/regional_data/latvia-20141004.psv +89 -0
- data/lib/sapor/regional_data/latvia.rb +112 -0
- data/lib/sapor/regional_data/latvia_kpv.rb +112 -0
- data/lib/sapor/regional_data/latvia_kpv_p_par.rb +112 -0
- data/lib/sapor/regional_data/latvia_kpv_par.rb +112 -0
- data/lib/sapor/regional_data/luxembourg-20131020.psv +76 -0
- data/lib/sapor/regional_data/luxembourg.rb +82 -0
- data/lib/sapor/regional_data/netherlands.rb +108 -0
- data/lib/sapor/regional_data/norway.rb +425 -0
- data/lib/sapor/regional_data/norwegian_municipality.rb +68 -0
- data/lib/sapor/regional_data/poland-20151025-with-ko-and-l-without-n-po-r-and-zl.psv +321 -0
- 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
- data/lib/sapor/regional_data/poland-20151025-with-ko-sld-and-wi-without-n-po-and-zl.psv +403 -0
- data/lib/sapor/regional_data/poland-20151025-with-sld-and-wi-without-zl.psv +444 -0
- data/lib/sapor/regional_data/poland-20151025-with-sld-without-zl.psv +403 -0
- data/lib/sapor/regional_data/poland-20151025.psv +403 -0
- data/lib/sapor/regional_data/poland.rb +125 -0
- data/lib/sapor/regional_data/poland_with_ko_and_l_without_n_po_r_and_zl.rb +122 -0
- 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
- data/lib/sapor/regional_data/poland_with_ko_sld_and_wi_without_n_po_and_zl.rb +125 -0
- data/lib/sapor/regional_data/poland_with_sld_and_wi_without_zl.rb +126 -0
- data/lib/sapor/regional_data/poland_with_sld_without_zl.rb +126 -0
- data/lib/sapor/regional_data/portugal-20151004-with-a-and-ch-without-paf.psv +438 -0
- data/lib/sapor/regional_data/portugal-20151004-with-a-and-il-without-paf.psv +438 -0
- data/lib/sapor/regional_data/portugal-20151004-with-a-ch-and-il-without-paf.psv +461 -0
- data/lib/sapor/regional_data/portugal-20151004-with-a-without-paf.psv +415 -0
- data/lib/sapor/regional_data/portugal-20151004-with-ch-and-il-without-paf.psv +438 -0
- data/lib/sapor/regional_data/portugal-20151004-without-paf.psv +392 -0
- data/lib/sapor/regional_data/portugal-20151004.psv +370 -0
- data/lib/sapor/regional_data/portugal.rb +101 -0
- data/lib/sapor/regional_data/portugal_with_a_and_ch_without_paf.rb +92 -0
- data/lib/sapor/regional_data/portugal_with_a_and_il_without_paf.rb +92 -0
- data/lib/sapor/regional_data/portugal_with_a_ch_and_il_without_paf.rb +92 -0
- data/lib/sapor/regional_data/portugal_with_a_without_paf.rb +92 -0
- data/lib/sapor/regional_data/portugal_with_ch_and_il_without_paf.rb +92 -0
- data/lib/sapor/regional_data/portugal_without_paf.rb +92 -0
- data/lib/sapor/regional_data/slovakia.rb +81 -0
- data/lib/sapor/regional_data/slovenia.rb +114 -0
- data/lib/sapor/regional_data/spain-20160626.psv +619 -0
- data/lib/sapor/regional_data/spain.rb +136 -0
- data/lib/sapor/regional_data/sweden.rb +92 -0
- data/lib/sapor/regional_data/sweden_20140914.rb +89 -0
- data/lib/sapor/regional_data/united_kingdom-2015.psv +4358 -0
- data/lib/sapor/regional_data/united_kingdom-20170608-brexit-chuk.psv +5154 -0
- data/lib/sapor/regional_data/united_kingdom-20170608-brexit.psv +4521 -0
- data/lib/sapor/regional_data/united_kingdom-20170608-tig.psv +4529 -0
- data/lib/sapor/regional_data/united_kingdom-20170608.psv +3894 -0
- data/lib/sapor/regional_data/united_kingdom.rb +94 -0
- data/lib/sapor/regional_data/united_kingdom_with_brexit.rb +110 -0
- data/lib/sapor/regional_data/united_kingdom_with_brexit_and_chuk.rb +111 -0
- data/lib/sapor/regional_data/united_kingdom_with_tig.rb +111 -0
- data/lib/sapor/regional_data/utopia.rb +66 -0
- data/lib/sapor/regional_data/wallonia-2014.psv +101 -0
- data/lib/sapor/regional_data/wallonia-20190526.psv +88 -0
- data/lib/sapor/regional_data/wallonia.rb +112 -0
- data/lib/sapor/representatives_polychotomy.rb +338 -0
- data/lib/sapor/single_district_proportional.rb +75 -0
- data/sapor.gemspec +35 -0
- data/spec/integration/area_spec.rb +28 -0
- data/spec/integration/poll_spec.rb +112 -0
- data/spec/integration/sample.poll +8 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/area_spec.rb +115 -0
- data/spec/unit/austria_spec.rb +76 -0
- data/spec/unit/belgium_brussels_spec.rb +58 -0
- data/spec/unit/belgium_flanders_spec.rb +62 -0
- data/spec/unit/belgium_spec.rb +26 -0
- data/spec/unit/belgium_wallonia_spec.rb +65 -0
- data/spec/unit/binomials_cache_spec.rb +34 -0
- data/spec/unit/catalonia_spec.rb +74 -0
- data/spec/unit/combinations_distribution_spec.rb +241 -0
- data/spec/unit/denmark_spec.rb +56 -0
- data/spec/unit/denmark_with_e_and_p_spec.rb +58 -0
- data/spec/unit/denmark_with_e_spec.rb +57 -0
- data/spec/unit/denmark_with_p_spec.rb +57 -0
- data/spec/unit/denominators_spec.rb +40 -0
- data/spec/unit/dichotomies_spec.rb +154 -0
- data/spec/unit/dichotomy_spec.rb +320 -0
- data/spec/unit/estonia_spec.rb +65 -0
- data/spec/unit/european_union_27_austria_spec.rb +61 -0
- data/spec/unit/european_union_27_croatia_spec.rb +60 -0
- data/spec/unit/european_union_27_denmark_spec.rb +62 -0
- data/spec/unit/european_union_27_estonia_spec.rb +94 -0
- data/spec/unit/european_union_27_finland_spec.rb +75 -0
- data/spec/unit/european_union_27_ireland_spec.rb +72 -0
- data/spec/unit/european_union_27_ireland_with_ia_spec.rb +74 -0
- data/spec/unit/european_union_27_italy_spec.rb +69 -0
- data/spec/unit/european_union_27_netherlands_spec.rb +81 -0
- data/spec/unit/european_union_27_poland_spec.rb +69 -0
- data/spec/unit/european_union_27_romania_spec.rb +67 -0
- data/spec/unit/european_union_27_slovakia_spec.rb +111 -0
- data/spec/unit/european_union_27_spain_spec.rb +130 -0
- data/spec/unit/european_union_27_sweden_spec.rb +89 -0
- data/spec/unit/european_union_austria_spec.rb +61 -0
- data/spec/unit/european_union_bulgaria_spec.rb +97 -0
- data/spec/unit/european_union_croatia_spec.rb +59 -0
- data/spec/unit/european_union_cyprus_spec.rb +65 -0
- data/spec/unit/european_union_czech_republic_spec.rb +125 -0
- data/spec/unit/european_union_denmark_spec.rb +61 -0
- data/spec/unit/european_union_estonia_spec.rb +93 -0
- data/spec/unit/european_union_finland_spec.rb +75 -0
- data/spec/unit/european_union_flanders_spec.rb +56 -0
- data/spec/unit/european_union_france_2019_spec.rb +73 -0
- data/spec/unit/european_union_france_spec.rb +73 -0
- data/spec/unit/european_union_french_community_of_belgium_spec.rb +61 -0
- data/spec/unit/european_union_germany_spec.rb +90 -0
- data/spec/unit/european_union_great_britain_spec.rb +87 -0
- data/spec/unit/european_union_greece_spec.rb +148 -0
- data/spec/unit/european_union_hungary_spec.rb +57 -0
- data/spec/unit/european_union_ireland_spec.rb +72 -0
- data/spec/unit/european_union_ireland_with_ia_spec.rb +74 -0
- data/spec/unit/european_union_italy_spec.rb +69 -0
- data/spec/unit/european_union_latvia_spec.rb +76 -0
- data/spec/unit/european_union_lithuania_spec.rb +68 -0
- data/spec/unit/european_union_luxembourg_spec.rb +63 -0
- data/spec/unit/european_union_malta_spec.rb +60 -0
- data/spec/unit/european_union_netherlands_spec.rb +81 -0
- data/spec/unit/european_union_northern_ireland_spec.rb +66 -0
- data/spec/unit/european_union_poland_spec.rb +69 -0
- data/spec/unit/european_union_portugal_spec.rb +77 -0
- data/spec/unit/european_union_romania_spec.rb +67 -0
- data/spec/unit/european_union_slovakia_spec.rb +111 -0
- data/spec/unit/european_union_slovenia_spec.rb +77 -0
- data/spec/unit/european_union_spain_spec.rb +129 -0
- data/spec/unit/european_union_sweden_spec.rb +89 -0
- data/spec/unit/finland_spec.rb +65 -0
- data/spec/unit/finland_with_sin_spec.rb +67 -0
- data/spec/unit/first_past_the_post_spec.rb +54 -0
- data/spec/unit/flanders_spec.rb +70 -0
- data/spec/unit/france_spec.rb +32 -0
- data/spec/unit/greece_spec.rb +118 -0
- data/spec/unit/hungary_spec.rb +132 -0
- data/spec/unit/iceland_spec.rb +57 -0
- data/spec/unit/largest_remainder_spec.rb +79 -0
- data/spec/unit/latvia_kpv_p_par_spec.rb +38 -0
- data/spec/unit/latvia_kpv_par_spec.rb +38 -0
- data/spec/unit/latvia_kpv_spec.rb +38 -0
- data/spec/unit/latvia_spec.rb +60 -0
- data/spec/unit/luxembourg_spec.rb +54 -0
- data/spec/unit/multi_district_leveled_proportional_spec.rb +49 -0
- data/spec/unit/multi_district_proportional_spec.rb +81 -0
- data/spec/unit/netherlands_spec.rb +107 -0
- data/spec/unit/norway_spec.rb +64 -0
- data/spec/unit/norwegian_municipality_spec.rb +89 -0
- data/spec/unit/number_formatter_spec.rb +173 -0
- data/spec/unit/poland_spec.rb +62 -0
- data/spec/unit/poland_with_ko_and_l_without_n_po_r_and_zl_spec.rb +60 -0
- 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
- data/spec/unit/poland_with_ko_sld_and_wi_without_n_po_and_zl_spec.rb +62 -0
- data/spec/unit/poland_with_sld_and_wi_without_zl_spec.rb +63 -0
- data/spec/unit/poland_with_sld_without_zl_spec.rb +62 -0
- data/spec/unit/poll_spec.rb +110 -0
- data/spec/unit/portugal_spec.rb +66 -0
- data/spec/unit/portugal_with_a_and_ch_without_paf_spec.rb +68 -0
- data/spec/unit/portugal_with_a_and_il_without_paf_spec.rb +68 -0
- data/spec/unit/portugal_with_a_ch_and_il_without_paf_spec.rb +69 -0
- data/spec/unit/portugal_with_a_without_paf_spec.rb +67 -0
- data/spec/unit/portugal_with_ch_and_il_without_paf_spec.rb +68 -0
- data/spec/unit/portugal_without_paf_spec.rb +66 -0
- data/spec/unit/pseudorandom_multirange_enumerator_spec.rb +82 -0
- data/spec/unit/referendum_polychotomy_spec.rb +289 -0
- data/spec/unit/representatives_polychotomy_spec.rb +332 -0
- data/spec/unit/slovakia_spec.rb +99 -0
- data/spec/unit/slovenia_spec.rb +80 -0
- data/spec/unit/spain_spec.rb +101 -0
- data/spec/unit/sweden_20140914_spec.rb +112 -0
- data/spec/unit/sweden_spec.rb +113 -0
- data/spec/unit/united_kingdom_spec.rb +65 -0
- data/spec/unit/united_kingdom_with_brexit_and_chuk_spec.rb +67 -0
- data/spec/unit/united_kingdom_with_brexit_spec.rb +66 -0
- data/spec/unit/united_kingdom_with_tig_spec.rb +66 -0
- data/spec/unit/wallonia_spec.rb +70 -0
- metadata +490 -0
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
# Class representing a multi-district proportional electoral system with a
|
|
23
|
+
# second round of leveling seats.
|
|
24
|
+
#
|
|
25
|
+
class MultiDistrictLeveledProportional
|
|
26
|
+
def initialize(last_election_result, last_detailed_election_result,
|
|
27
|
+
seat_distribution, leveling_seats, leveling_threshold,
|
|
28
|
+
denominators_class)
|
|
29
|
+
@proportional = MultiDistrictProportional.new(last_election_result,
|
|
30
|
+
last_detailed_election_result,
|
|
31
|
+
seat_distribution, denominators_class)
|
|
32
|
+
@leveling_seats = leveling_seats
|
|
33
|
+
@leveling_threshold = leveling_threshold
|
|
34
|
+
@denominators_class = denominators_class
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def project(simulation)
|
|
38
|
+
result = @proportional.project(simulation)
|
|
39
|
+
threshold = @leveling_threshold * simulation.values.inject(:+)
|
|
40
|
+
quotients = []
|
|
41
|
+
simulation.each_pair do |choice, votes|
|
|
42
|
+
next if votes < threshold
|
|
43
|
+
denominators(result[choice]).each do |d|
|
|
44
|
+
quotients << [choice, votes.to_f / d]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
seats = quotients.sort { |a, b| b.last <=> a.last }.map(&:first).slice(0, @leveling_seats)
|
|
48
|
+
seats.each do |seat|
|
|
49
|
+
if result.key?(seat)
|
|
50
|
+
result[seat] += 1
|
|
51
|
+
else
|
|
52
|
+
result[seat] = 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def denominators(seats)
|
|
61
|
+
@denominators_class.get(seats + @leveling_seats).reverse_each.take(@leveling_seats)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
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 representing a proportional electoral system with more than one
|
|
22
|
+
# district.
|
|
23
|
+
#
|
|
24
|
+
class MultiDistrictProportional
|
|
25
|
+
def initialize(last_election_result, last_detailed_election_result,
|
|
26
|
+
seat_distribution, denominators_class, threshold = 0,
|
|
27
|
+
national_threshold = 0)
|
|
28
|
+
@last_election_result = last_election_result
|
|
29
|
+
@last_detailed_election_result = last_detailed_election_result
|
|
30
|
+
@seat_distribution = seat_distribution
|
|
31
|
+
@denominators_class = denominators_class
|
|
32
|
+
@threshold = threshold
|
|
33
|
+
@national_threshold = national_threshold
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def project(simulation)
|
|
37
|
+
allowed_parties = []
|
|
38
|
+
unless @national_threshold.zero?
|
|
39
|
+
threshold = @national_threshold * simulation.values.inject(:+)
|
|
40
|
+
simulation.each_pair do |choice, votes|
|
|
41
|
+
allowed_parties << choice if votes >= threshold
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
multiplicators = calculate_multiplicators(simulation)
|
|
45
|
+
result = create_empty_result(simulation)
|
|
46
|
+
@last_detailed_election_result.each_pair do |name, local_last_result|
|
|
47
|
+
no_of_seats = @seat_distribution[name]
|
|
48
|
+
seats = local_seats(no_of_seats, local_last_result, multiplicators,
|
|
49
|
+
allowed_parties)
|
|
50
|
+
add_seats_to_result(result, seats)
|
|
51
|
+
end
|
|
52
|
+
result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def add_seats_to_result(result, seats)
|
|
58
|
+
seats.each do |seat|
|
|
59
|
+
if result.key?(seat)
|
|
60
|
+
result[seat] += 1
|
|
61
|
+
else
|
|
62
|
+
result[seat] = 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def calculate_multiplicators(simulation)
|
|
68
|
+
simulation_sum = simulation.values.inject(:+)
|
|
69
|
+
last_election_sum = @last_election_result.values.inject(:+)
|
|
70
|
+
multiplicators = {}
|
|
71
|
+
simulation.each_key do |choice|
|
|
72
|
+
new_fraction = simulation[choice].to_f / simulation_sum
|
|
73
|
+
last_fraction = @last_election_result[choice].to_f / last_election_sum
|
|
74
|
+
multiplicators[choice] = new_fraction / last_fraction
|
|
75
|
+
end
|
|
76
|
+
multiplicators
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def create_empty_result(simulation)
|
|
80
|
+
result = {}
|
|
81
|
+
simulation.each_key do |choice|
|
|
82
|
+
result[choice] = 0
|
|
83
|
+
end
|
|
84
|
+
result[OTHER] = 0
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def local_votes(local_last_result, multiplicators)
|
|
89
|
+
local_votes = {}
|
|
90
|
+
local_last_result.each_pair do |choice, votes|
|
|
91
|
+
local_votes[choice] = if multiplicators.key?(choice)
|
|
92
|
+
votes * multiplicators[choice]
|
|
93
|
+
else
|
|
94
|
+
votes
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
local_votes
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def local_quotients(local_votes, local_threshold, no_of_seats,
|
|
101
|
+
allowed_parties)
|
|
102
|
+
local_quotients = []
|
|
103
|
+
local_votes.each_pair do |choice, new_value|
|
|
104
|
+
next unless allowed_parties.empty? || allowed_parties.include?(choice)
|
|
105
|
+
next if new_value < local_threshold
|
|
106
|
+
@denominators_class.get(no_of_seats).each do |d|
|
|
107
|
+
local_quotients << [choice, new_value.to_f / d]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
local_quotients
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def local_seats(no_of_seats, local_last_result, multiplicators,
|
|
114
|
+
allowed_parties)
|
|
115
|
+
local_votes = local_votes(local_last_result, multiplicators)
|
|
116
|
+
local_threshold = local_votes.values.inject(:+).to_f * @threshold
|
|
117
|
+
local_quotients = local_quotients(local_votes, local_threshold,
|
|
118
|
+
no_of_seats, allowed_parties)
|
|
119
|
+
sorted_quotients = local_quotients.sort { |a, b| b.last <=> a.last }
|
|
120
|
+
sorted_quotients.map(&:first).slice(0, no_of_seats)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
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 representing a proportional electoral system with more than one
|
|
22
|
+
# district.
|
|
23
|
+
#
|
|
24
|
+
class MultiDistrictVariableThresholdProportional
|
|
25
|
+
def initialize(last_election_result, last_detailed_election_result,
|
|
26
|
+
seat_distribution, denominators_class,
|
|
27
|
+
party_list_threshold, coalition_list_threshold,
|
|
28
|
+
coalition_lists, minority_lists)
|
|
29
|
+
@last_election_result = last_election_result
|
|
30
|
+
@last_detailed_election_result = last_detailed_election_result
|
|
31
|
+
@seat_distribution = seat_distribution
|
|
32
|
+
@denominators_class = denominators_class
|
|
33
|
+
@party_list_threshold = party_list_threshold
|
|
34
|
+
@coalition_list_threshold = coalition_list_threshold
|
|
35
|
+
@coalition_lists = coalition_lists
|
|
36
|
+
@minority_lists = minority_lists
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def project(simulation)
|
|
40
|
+
allowed_parties = []
|
|
41
|
+
total_votes = simulation.values.inject(:+)
|
|
42
|
+
party_list_threshold = @party_list_threshold * total_votes
|
|
43
|
+
coalition_list_threshold = @coalition_list_threshold * total_votes
|
|
44
|
+
simulation.each_pair do |choice, votes|
|
|
45
|
+
next unless @minority_lists.include?(choice) ||
|
|
46
|
+
@coalition_lists.include?(choice) &&
|
|
47
|
+
votes >= coalition_list_threshold ||
|
|
48
|
+
!@coalition_lists.include?(choice) &&
|
|
49
|
+
votes >= party_list_threshold
|
|
50
|
+
allowed_parties << choice
|
|
51
|
+
end
|
|
52
|
+
multiplicators = calculate_multiplicators(simulation)
|
|
53
|
+
result = create_empty_result(simulation)
|
|
54
|
+
@last_detailed_election_result.each_pair do |name, local_last_result|
|
|
55
|
+
no_of_seats = @seat_distribution[name]
|
|
56
|
+
seats = local_seats(no_of_seats, local_last_result, multiplicators,
|
|
57
|
+
allowed_parties)
|
|
58
|
+
add_seats_to_result(result, seats)
|
|
59
|
+
end
|
|
60
|
+
result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def add_seats_to_result(result, seats)
|
|
66
|
+
seats.each do |seat|
|
|
67
|
+
if result.key?(seat)
|
|
68
|
+
result[seat] += 1
|
|
69
|
+
else
|
|
70
|
+
result[seat] = 1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def calculate_multiplicators(simulation)
|
|
76
|
+
simulation_sum = simulation.values.inject(:+)
|
|
77
|
+
last_election_sum = @last_election_result.values.inject(:+)
|
|
78
|
+
multiplicators = {}
|
|
79
|
+
simulation.each_key do |choice|
|
|
80
|
+
new_fraction = simulation[choice].to_f / simulation_sum
|
|
81
|
+
last_fraction = @last_election_result[choice].to_f / last_election_sum
|
|
82
|
+
multiplicators[choice] = new_fraction / last_fraction
|
|
83
|
+
end
|
|
84
|
+
multiplicators
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_empty_result(simulation)
|
|
88
|
+
result = {}
|
|
89
|
+
simulation.each_key do |choice|
|
|
90
|
+
result[choice] = 0
|
|
91
|
+
end
|
|
92
|
+
result[OTHER] = 0
|
|
93
|
+
result
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def local_votes(local_last_result, multiplicators)
|
|
97
|
+
local_votes = {}
|
|
98
|
+
local_last_result.each_pair do |choice, votes|
|
|
99
|
+
local_votes[choice] = if multiplicators.key?(choice)
|
|
100
|
+
votes * multiplicators[choice]
|
|
101
|
+
else
|
|
102
|
+
votes
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
local_votes
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def local_quotients(local_votes, no_of_seats, allowed_parties)
|
|
109
|
+
local_quotients = []
|
|
110
|
+
local_votes.each_pair do |choice, new_value|
|
|
111
|
+
next unless allowed_parties.empty? || allowed_parties.include?(choice)
|
|
112
|
+
@denominators_class.get(no_of_seats).each do |d|
|
|
113
|
+
local_quotients << [choice, new_value.to_f / d]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
local_quotients
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def local_seats(no_of_seats, local_last_result, multiplicators,
|
|
120
|
+
allowed_parties)
|
|
121
|
+
local_votes = local_votes(local_last_result, multiplicators)
|
|
122
|
+
local_quotients = local_quotients(local_votes, no_of_seats,
|
|
123
|
+
allowed_parties)
|
|
124
|
+
sorted_quotients = local_quotients.sort { |a, b| b.last <=> a.last }
|
|
125
|
+
sorted_quotients.map(&:first).slice(0, no_of_seats)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -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
|
+
module Sapor
|
|
21
|
+
#
|
|
22
|
+
# Module to format numbers.
|
|
23
|
+
#
|
|
24
|
+
module NumberFormatter
|
|
25
|
+
def three_digits_percentage(number)
|
|
26
|
+
if number >= 0.9995 || number < 0.000005
|
|
27
|
+
sprintf('%.0f', number * 100) + '%'
|
|
28
|
+
elsif number >= 0.09995
|
|
29
|
+
sprintf('%.1f', number * 100) + '%'
|
|
30
|
+
elsif number >= 0.009995
|
|
31
|
+
sprintf('%.2f', number * 100) + '%'
|
|
32
|
+
else
|
|
33
|
+
sprintf('%.3f', number * 100) + '%'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def six_char_percentage(number)
|
|
38
|
+
sprintf('%5.1f', number * 100) + '%'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_thousands_separator(integer)
|
|
42
|
+
integer.to_s.reverse.gsub(/...(?=.)/, '\&,').reverse
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
#
|
|
23
|
+
# Represents a set of options.
|
|
24
|
+
#
|
|
25
|
+
class Options
|
|
26
|
+
DEFAULT_MAX_ERROR = 0.001
|
|
27
|
+
DEFAULT_MAX_POLYCHOTOMY_ITERATIONS = 2 ** 25
|
|
28
|
+
DEFAULT_MIN_DICHOTOMIES_ITERATIONS = 0
|
|
29
|
+
DEFAULT_MIN_POLYCHOTOMY_ITERATIONS = 2 ** 20
|
|
30
|
+
|
|
31
|
+
MAX_ERROR_KEY = 'emx'
|
|
32
|
+
MAX_POLYCHOTOMY_ITERATIONS_KEY = 'pmx'
|
|
33
|
+
MIN_DICHOTOMIES_ITERATIONS_KEY = 'dmn'
|
|
34
|
+
MIN_POLYCHOTOMY_ITERATIONS_KEY = 'pmn'
|
|
35
|
+
|
|
36
|
+
FLOAT_OPTIONS = [MAX_ERROR_KEY]
|
|
37
|
+
INTEGER_OPTIONS = [MAX_POLYCHOTOMY_ITERATIONS_KEY, MIN_DICHOTOMIES_ITERATIONS_KEY, MIN_POLYCHOTOMY_ITERATIONS_KEY]
|
|
38
|
+
|
|
39
|
+
def initialize(arguments)
|
|
40
|
+
@hash = { MAX_ERROR_KEY => DEFAULT_MAX_ERROR,
|
|
41
|
+
MAX_POLYCHOTOMY_ITERATIONS_KEY => DEFAULT_MAX_POLYCHOTOMY_ITERATIONS,
|
|
42
|
+
MIN_DICHOTOMIES_ITERATIONS_KEY => DEFAULT_MIN_DICHOTOMIES_ITERATIONS,
|
|
43
|
+
MIN_POLYCHOTOMY_ITERATIONS_KEY => DEFAULT_MIN_POLYCHOTOMY_ITERATIONS }
|
|
44
|
+
arguments.each do | argument |
|
|
45
|
+
key_value = argument[1..-1].split('=')
|
|
46
|
+
key = key_value.first
|
|
47
|
+
value = key_value.last
|
|
48
|
+
if INTEGER_OPTIONS.include?(key)
|
|
49
|
+
value = value.to_i
|
|
50
|
+
elsif FLOAT_OPTIONS.include?(key)
|
|
51
|
+
value = value.to_f
|
|
52
|
+
end
|
|
53
|
+
@hash[key] = value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def max_error
|
|
58
|
+
@hash[MAX_ERROR_KEY]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def max_polychotomy_iterations
|
|
62
|
+
@hash[MAX_POLYCHOTOMY_ITERATIONS_KEY]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def min_dichotomies_iterations
|
|
66
|
+
@hash[MIN_DICHOTOMIES_ITERATIONS_KEY]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def min_polychotomy_iterations
|
|
70
|
+
@hash[MIN_POLYCHOTOMY_ITERATIONS_KEY]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/sapor/poll.rb
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
require 'yaml'
|
|
20
|
+
|
|
21
|
+
module Sapor
|
|
22
|
+
OTHER = 'Other'.freeze
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Represents a poll.
|
|
26
|
+
#
|
|
27
|
+
class Poll
|
|
28
|
+
include NumberFormatter
|
|
29
|
+
attr_reader :area, :logger, :type
|
|
30
|
+
|
|
31
|
+
AREA_KEY = 'Area'.freeze
|
|
32
|
+
|
|
33
|
+
BELGIAN_AREAS = [BelgiumBrussels.instance, BelgiumFlanders.instance,
|
|
34
|
+
BelgiumWallonia.instance, Flanders.instance,
|
|
35
|
+
Wallonia.instance].freeze
|
|
36
|
+
CATALONIAN_AREAS = [Catalonia.instance,
|
|
37
|
+
CataloniaWithJuntsPerCatalunya.instance,
|
|
38
|
+
CataloniaWithoutJuntsPelSi.instance].freeze
|
|
39
|
+
DANISH_AREAS = [Denmark.instance, DenmarkWithE.instance,
|
|
40
|
+
DenmarkWithEAndP.instance, DenmarkWithP.instance].freeze
|
|
41
|
+
EUROPEAN_UNION_AREAS = [EuropeanUnion27Austria.instance,
|
|
42
|
+
EuropeanUnion27Croatia.instance,
|
|
43
|
+
EuropeanUnion27Denmark.instance,
|
|
44
|
+
EuropeanUnion27Estonia.instance,
|
|
45
|
+
EuropeanUnion27Finland.instance,
|
|
46
|
+
EuropeanUnion27Ireland.instance,
|
|
47
|
+
EuropeanUnion27IrelandWithIa.instance,
|
|
48
|
+
EuropeanUnion27Italy.instance,
|
|
49
|
+
EuropeanUnion27Netherlands.instance,
|
|
50
|
+
EuropeanUnion27Poland.instance,
|
|
51
|
+
EuropeanUnion27Romania.instance,
|
|
52
|
+
EuropeanUnion27Slovakia.instance,
|
|
53
|
+
EuropeanUnion27Spain.instance,
|
|
54
|
+
EuropeanUnion27Sweden.instance,
|
|
55
|
+
EuropeanUnionAustria.instance,
|
|
56
|
+
EuropeanUnionBulgaria.instance,
|
|
57
|
+
EuropeanUnionCroatia.instance,
|
|
58
|
+
EuropeanUnionCyprus.instance,
|
|
59
|
+
EuropeanUnionCzechRepublic.instance,
|
|
60
|
+
EuropeanUnionDenmark.instance,
|
|
61
|
+
EuropeanUnionEstonia.instance,
|
|
62
|
+
EuropeanUnionFinland.instance,
|
|
63
|
+
EuropeanUnionFlanders.instance,
|
|
64
|
+
EuropeanUnionFrance.instance,
|
|
65
|
+
EuropeanUnionFrance2019.instance,
|
|
66
|
+
EuropeanUnionFrenchCommunityOfBelgium.instance,
|
|
67
|
+
EuropeanUnionGermany.instance,
|
|
68
|
+
EuropeanUnionGreatBritain.instance,
|
|
69
|
+
EuropeanUnionGreece.instance,
|
|
70
|
+
EuropeanUnionHungary.instance,
|
|
71
|
+
EuropeanUnionIreland.instance,
|
|
72
|
+
EuropeanUnionIrelandWithIa.instance,
|
|
73
|
+
EuropeanUnionItaly.instance,
|
|
74
|
+
EuropeanUnionLatvia.instance,
|
|
75
|
+
EuropeanUnionLithuania.instance,
|
|
76
|
+
EuropeanUnionLuxembourg.instance,
|
|
77
|
+
EuropeanUnionMalta.instance,
|
|
78
|
+
EuropeanUnionNetherlands.instance,
|
|
79
|
+
EuropeanUnionNorthernIreland.instance,
|
|
80
|
+
EuropeanUnionPoland.instance,
|
|
81
|
+
EuropeanUnionPortugal.instance,
|
|
82
|
+
EuropeanUnionRomania.instance,
|
|
83
|
+
EuropeanUnionSlovakia.instance,
|
|
84
|
+
EuropeanUnionSlovenia.instance,
|
|
85
|
+
EuropeanUnionSpain.instance,
|
|
86
|
+
EuropeanUnionSweden.instance].freeze
|
|
87
|
+
LATVIAN_AREAS = [Latvia.instance, LatviaKpv.instance, \
|
|
88
|
+
LatviaKpvPar.instance, LatviaKpvPPar.instance].freeze
|
|
89
|
+
NORWEGIAN_AREAS = [Norway.instance, NorwegianMunicipality::BERGEN,
|
|
90
|
+
NorwegianMunicipality::OSLO,
|
|
91
|
+
NorwegianMunicipality::TRONDHEIM].freeze
|
|
92
|
+
POLISH_AREAS = [Poland.instance,
|
|
93
|
+
PolandWithKoAndLWithoutNPoRAndZl.instance,
|
|
94
|
+
PolandWithKoKonfKpLAndZpWithoutKK15NPisPoPslRAndZl.instance,
|
|
95
|
+
PolandWithKoSldAndWiWithoutNPoAndZl.instance,
|
|
96
|
+
PolandWithSldAndWiWithoutZL.instance,
|
|
97
|
+
PolandWithSldWithoutZL.instance].freeze
|
|
98
|
+
PORTUGUESE_AREAS = [Portugal.instance,
|
|
99
|
+
PortugalWithAAndChWithoutPaf.instance,
|
|
100
|
+
PortugalWithAAndIlWithoutPaf.instance,
|
|
101
|
+
PortugalWithAChAndIlWithoutPaf.instance,
|
|
102
|
+
PortugalWithAWithoutPaf.instance,
|
|
103
|
+
PortugalWithChAndIlWithoutPaf.instance,
|
|
104
|
+
PortugalWithoutPaf.instance].freeze
|
|
105
|
+
AREAS_MAP = {}
|
|
106
|
+
(BELGIAN_AREAS + CATALONIAN_AREAS + DANISH_AREAS + EUROPEAN_UNION_AREAS + \
|
|
107
|
+
LATVIAN_AREAS + NORWEGIAN_AREAS + POLISH_AREAS + PORTUGUESE_AREAS + \
|
|
108
|
+
[Austria.instance, Estonia.instance, Finland.instance,
|
|
109
|
+
FinlandWithSin.instance, France.instance, Greece.instance,
|
|
110
|
+
Hungary.instance, Iceland.instance, Luxembourg.instance,
|
|
111
|
+
Netherlands.instance, Slovakia.instance, Slovenia.instance,
|
|
112
|
+
Spain.instance, Sweden.instance, Sweden20140914.instance,
|
|
113
|
+
UnitedKingdom.instance, UnitedKingdomWithBrexit.instance,
|
|
114
|
+
UnitedKingdomWithBrexitAndChuk.instance, UnitedKingdomWithTig.instance,
|
|
115
|
+
Utopia.instance]).map do |area|
|
|
116
|
+
AREAS_MAP[area.area_code] = area
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
TYPE_KEY = 'Type'.freeze
|
|
120
|
+
REFERENDUM_TYPE_VALUE = 'Referendum'.freeze
|
|
121
|
+
ELECTION_TYPE_VALUE = 'Election'.freeze
|
|
122
|
+
|
|
123
|
+
DEFAULT_CONFIDENCE_LEVEL = 0.95
|
|
124
|
+
|
|
125
|
+
def initialize(filename, metadata, results)
|
|
126
|
+
@filename = filename
|
|
127
|
+
@logger = LogFacade.create_logger
|
|
128
|
+
@area_code = metadata.delete(AREA_KEY)
|
|
129
|
+
@area = Poll.lookup_area(@area_code)
|
|
130
|
+
@type = metadata.delete(TYPE_KEY)
|
|
131
|
+
@results = interpret(results)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def analyze(options)
|
|
135
|
+
analyze_as_dichotomies(options)
|
|
136
|
+
analyze_as_polychotomy(options)
|
|
137
|
+
@logger.info('Done.')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def continue_analysis(options)
|
|
141
|
+
@logger = LogFacade.create_logger
|
|
142
|
+
@logger.info('Continuing the analysis as a polychotomy...')
|
|
143
|
+
@area = Poll.lookup_area(@area_code)
|
|
144
|
+
@analysis.revive_volatile_fields(@logger)
|
|
145
|
+
analyze_until_convergence(options)
|
|
146
|
+
@logger.info('Done.')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def confidence_interval(choice, level = DEFAULT_CONFIDENCE_LEVEL)
|
|
150
|
+
@analysis.confidence_interval(choice, level) unless @analysis.nil?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def most_probable_fraction(choice)
|
|
154
|
+
@analysis.most_probable_fraction(choice) unless @analysis.nil?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def most_probable_value(choice)
|
|
158
|
+
@analysis.most_probable_value(choice) unless @analysis.nil?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def result(choice)
|
|
162
|
+
@results[choice]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def encode_with(coder)
|
|
166
|
+
(instance_variables - [:@area, :@logger]).each do |var|
|
|
167
|
+
var = var.to_s
|
|
168
|
+
coder[var.gsub('@', '')] = eval(var)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def self.lookup_area(area_code)
|
|
173
|
+
AREAS_MAP[area_code]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def self.line_to_hash(line, current, results)
|
|
179
|
+
if line.chomp.eql?('==')
|
|
180
|
+
current = results
|
|
181
|
+
else
|
|
182
|
+
elements = line.chomp.split('=')
|
|
183
|
+
current[elements.first] = elements.last
|
|
184
|
+
end
|
|
185
|
+
current
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def self.as_hashes(lines)
|
|
189
|
+
metadata = {}
|
|
190
|
+
results = {}
|
|
191
|
+
current = metadata
|
|
192
|
+
lines.each do |line|
|
|
193
|
+
current = line_to_hash(line, current, results)
|
|
194
|
+
end
|
|
195
|
+
[metadata, results]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def self.from_lines(filename, lines)
|
|
199
|
+
hashes = as_hashes(lines)
|
|
200
|
+
metadata = hashes.first
|
|
201
|
+
results = hashes.last
|
|
202
|
+
new(filename, metadata, results)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def self.from_file(filename)
|
|
206
|
+
from_lines(filename, File.open(filename))
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def interpret(results)
|
|
210
|
+
interpreted = {}
|
|
211
|
+
results.each_pair do |key, value|
|
|
212
|
+
interpreted[key] = value.to_i
|
|
213
|
+
end
|
|
214
|
+
interpreted
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def population_size
|
|
218
|
+
@area.population_size
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def referendum?
|
|
222
|
+
@type == REFERENDUM_TYPE_VALUE
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def threshold
|
|
226
|
+
if referendum?
|
|
227
|
+
0.5
|
|
228
|
+
else
|
|
229
|
+
@area.threshold
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def analyze_until_convergence(options)
|
|
234
|
+
while should_continue_analysis?(@analysis, options)
|
|
235
|
+
@analysis.refine
|
|
236
|
+
@logger.info(@analysis.report)
|
|
237
|
+
@logger.info('Error estimate: ε ≤ ' +
|
|
238
|
+
three_digits_percentage(@analysis.error_estimate) +
|
|
239
|
+
'.')
|
|
240
|
+
@logger.info(@analysis.progress_report)
|
|
241
|
+
@analysis.write_outputs(@filename)
|
|
242
|
+
save_state
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def should_continue_analysis?(analysis, options)
|
|
247
|
+
analysis.kind_of?(Dichotomies) \
|
|
248
|
+
&& (options.min_dichotomies_iterations > analysis.size \
|
|
249
|
+
|| analysis.error_estimate > options.max_error) \
|
|
250
|
+
|| analysis.kind_of?(Polychotomy) \
|
|
251
|
+
&& options.max_polychotomy_iterations > analysis.no_of_simulations \
|
|
252
|
+
&& (options.min_polychotomy_iterations > analysis.no_of_simulations \
|
|
253
|
+
|| analysis.error_estimate > options.max_error)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def analyze_as_dichotomies(options)
|
|
257
|
+
@logger.info('Analyzing as a set of dichotomies...')
|
|
258
|
+
@analysis = Dichotomies.new(@results, population_size, threshold)
|
|
259
|
+
analyze_until_convergence(options)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def analyze_as_polychotomy(options)
|
|
263
|
+
@logger.info('Analyzing as a polychotomy...')
|
|
264
|
+
if referendum?
|
|
265
|
+
@analysis = ReferendumPolychotomy.new(@results, @area, @analysis,
|
|
266
|
+
options.max_error)
|
|
267
|
+
else
|
|
268
|
+
@analysis = RepresentativesPolychotomy.new(@results, @area, @analysis,
|
|
269
|
+
options.max_error, @logger)
|
|
270
|
+
end
|
|
271
|
+
analyze_until_convergence(options)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def save_state
|
|
275
|
+
new_yaml_file = @filename.gsub('.poll', '-state-new.yaml')
|
|
276
|
+
open(new_yaml_file, 'w') do |file|
|
|
277
|
+
file.write(to_yaml)
|
|
278
|
+
end
|
|
279
|
+
yaml_file = @filename.gsub('.poll', '-state.yaml')
|
|
280
|
+
old_yaml_file = @filename.gsub('.poll', '-state-old.yaml')
|
|
281
|
+
File.delete(old_yaml_file) if File.exist?(old_yaml_file)
|
|
282
|
+
File.rename(yaml_file, old_yaml_file) if File.exist?(yaml_file)
|
|
283
|
+
File.rename(new_yaml_file, yaml_file)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|