{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Homework Assignment: Wonderlandia Election Simulation\n", "## Basics of Programming in Python (Fall'22)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Description \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In October 2022, elections were finished in Wonderlandia. At stake were 400 seats in the local parliament, and each seat is allocated through a **parallel voting**. Half of the seats are elected by party-list proportional representation (PLPR) with a minimum of 10% electoral threshold of number of votes that enable the party to get seats. The other half elected in single-member constituencies by first-past-the-post voting (plurality voting).\n", "\n", "In this assigment, we offer you to code down an election process similar to Wonderlandia's one, with different voting systems (algorithms to compute an election's winner), including:\n", "* Single-Member Plurality Voting (first-past-the-post)\n", "* Borda count\n", "* Condorcet method" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Goals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "During the Homework Assignment, you will:\n", "* prepare structurised report in Jupyter Notebook format that contain both detailed problem solving logic and its implementation with code;\n", "* practice in basics of Python (variables, sequences, control flows, etc.);\n", "* practice in stating conditions and and logical expressions from tasks of your assignment;\n", "* apply acquired programming skills for solving practical political science problem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Organizational stuff" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Separate links to submit [Part I](http://www.linksaredeleted.com/) and [Part II](http://www.linksaredeleted.com/)\n", "\n", "* You should download both parts of homework as `.ipynb` files to the corresponding section of LMS system before the deadline.\n", " * When uploading multiple solutions, the last one before the deadline will be considered final (but the previous ones can help with the appeal). \n", " * There are penalties for submitting your work after the deadline. They depend on how many hours late your submission are.\n", "* **Any type of cheating will be penalized**. For example:\n", " * Non-compilable code won't be considered for the assessment.\n", " * Any type of copy-pasting part or all of the notebook will cause 0 both to subject and object of copy-pasting.\n", " * We are not against helping each other: we are against blind reprinting.\n", "\n", "Don’t hesitate to contact me or teaching assistants if you have any questions! 🙂\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part I\n", "\n", "**Deadline: October 23, 11:59 pm**\n", "\n", "In this part, you play the role of the Election Сommittee’s analyst. You need to prepare collected bulletins for consequent counting: check ballots for spoilage, select candidates who passed the minimum threshold, and compile a report on preliminary analysis.\n", "\n", "There are 7 parties in our country - here's General List of Parties:\n", "1. Harmony Party\n", "2. Red Party \n", "3. Justice and Truth\n", "4. Adventure Alliance\n", "5. Animal Friends Party\n", "6. Go Greens!\n", "7. Yo-ho-ho Pirate Party\n", "\n", "And there are two types of bulletins:\n", "1. ones arranged in descending order of candidate priority, e.g.: \n", "`['Go Greens!', 'Animal Friends Party', 'Adventure Alliance', 'Yo-ho-ho Pirate Party', 'Justice and Truth', 'Harmony Party', 'Red Party']` - Go Greens! are the first choice here. Some ballot of this type may has different number of parties in it - it means that the voter simply did not choose this party (did not vote for it). \n", "2. with ranked positions according to General List of Parties, e.g.:\n", "`[7, 5, 2, 1, 4, 6, 3]` - Here the Adventure Alliance has the first priority for electioneer as it's position (index `[3]`) marked with 1st place. These type of ballots could contain zeroes (meaning the voter is not interested in choosing this party); hence, there should be 7 values in the ballot." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "general_list_of_parties = ['Harmony Party', 'Red Party', 'Justice and Truth', 'Adventure Alliance', 'Animal Friends Party', 'Go Greens!', 'Yo-ho-ho Pirate Party']" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "13159\n" ] } ], "source": [ "from csv import reader \n", "\n", "# read csv and delete empty rows\n", "with open('data/votes.csv') as csv_file:\n", " csv_reader = reader(csv_file)\n", " rows = list(csv_reader)\n", " votes = [x for x in rows if x != []]\n", "\n", "# convert numbers to int\n", "for b in range(len(votes)):\n", " for p in range(len(votes[b])):\n", " if votes[b][p].isdigit():\n", " votes[b][p] = int(votes[b][p])\n", "\n", "print(len(votes))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Tasks 1.1-1.2\n", "\n", "All votes are stored together into one object. Separate this object to two containers according to bulletin type: save all bulletins with names in it to `votes_names`, others - to `votes_ranks`. After aggregating, display the sentence with lengths of each sublist using **string formatting**.\n", "\n", "*example output:* `There are 1234 ballots with names and 1111 ballots with rankings.`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# 1.1-1.2 answers\n", "# create two new empty lists\n", "votes_names ___\n", "votes_ranks ___\n", "\n", "# Sort out the ballots in `votes` to new lists, one by one, according to some condition. don't forget about Tabs!\n", "for i in ___\n", "if type(i[0]) ___\n", "votes_names ___(i)\n", "else:\n", "votes_ranks ___(i)\n", "\n", "# Display the message\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Task 1.3-1.4\n", "\n", "Now we need to check our new lists for correctness. Let's begin with `votes_ranks`: display the **minimum**, **maximum** and **average** (rounded to three decimal places) length of ballot in this list.\n", "\n", "*example output:* \n", "`Minimum ballot length: 3, ` \n", "`Average ballot length: 4.205,` \n", "`Maximum ballot length: 5.`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1.3 answers\n", "\n", "# create a list that contains number of votes in each ranked ballot\n", "list_of_len ___\n", "\n", "# calculate number of votes and store it into the list\n", "for i ___\n", "list_of_len ___(___(i))\n", "\n", "# display the message\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Discuss: what this statistic says about this type of ballot?\n", "\n", "***Answer:** write your answer here* " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Task 1.5-1.7\n", "\n", "We know that some ballots in `votes_ranks` can contain zeros, which means voter did not include this particular party to her vote. This leads us to the following conclusions about the requirements for a valid bulletin:\n", "\n", "1. All ballots have the same length equal to the number of parties;\n", "1. As we have 7 parties in the race, the maximum place is 7th;\n", "2. There should be at least 1 vote in each ballot (meaning no ballots like `[0, 0, 0, 0, 0, 0, 0]`). \n", "\n", "Check all three requirements in following code cells: **delete the ballot from the list if it failed to meet one of requirements**. Additionally you need to **count how many ballots were removed** after the check and display this number with some message like *\"Number of incorrect bulletins\"* . " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1.5-1.7 answer \n", "\n", "# create incorrect ballots counter\n", "___\n", "\n", "# create empty list for cleared data\n", "votes_ranks_cleared ___\n", "\n", "# Check that all the requirements are met and add True ballots to cleared data list (you need to choose between and/or)\n", "for i in votes_ranks:\n", "if ___ and/or ___ and/or ___\n", "___ += 1\n", "else:\n", "___. ___(i)\n", "\n", "# Display the message below\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Tasks 1.8-1.11\n", "\n", " Now let's return to our `votes_names` list. There are some requirements too:\n", "\n", "1. The number of items on the ballot should not exceed the number of parties in the general list of parties;\n", "2. At least one party should be chosen by each voter.\n", "\n", "Check all three requirements in following code cells: **delete the ballot from the list if it failed to meet one of requirements**. Additionally you need to **count how many ballots were removed** after the check and display this number with some message like *\"Number of incorrect bulletins\"* (task 1.6). \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1.8-1.11 answer \n", "\n", "# create incorrect ballots counter\n", "___\n", "\n", "# create empty list for cleared data\n", "___\n", "\n", "# Check that all the requirements are met and add True ballots to cleared data list (you need to choose between and/or)\n", "___\n", " ___ ___\n", " ___\n", " ___\n", " ___\n", "\n", "# Display the message below\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**\\*Advanced task**. Complete tasks 1.8-1.11 with [list comprehensions](https://www.w3schools.com/python/python_lists_comprehension.asp)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "votes_names_cleared = []\n", "print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Task 1.12-1.13\n", "\n", "It is uncomfortable to store data in two different formats. Let's: \n", "1. convert `votes_ranks` to `votes_names` format; \n", "2. bind this new list with already existing names ballots;\n", "3. display the message of total number of valid ballots.\n", "\n", "*example output:* \n", "`Total number of valid ballots: 2500` \n", "\n", "How to convert: use `general_list_of_parties` to recall the order of parties in the ballot, e.g.: \n", "`[0,0,1,0,3,0,2]` -> `['Justice and Truth', 'Yo-ho-ho Pirate Party', 'Animal Friends Party']` \n", "\n", "*Hint: to use this, you need to combine [this piece of code](https://stackoverflow.com/a/6618543/10803427) with some condition for zeros*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1.12-1.13 answer \n", "\n", "# create a new empty list for future converted values\n", "___\n", "\n", "# fill the gaps in the code to convert the values and add them to the new list. use already cleared data to iterate through it\n", "for vote in ___:\n", " ___. ___([x for y, x in sorted(zip(vote, general_list_of_parties.copy())) if y ___]) # there couldn't be 0th position in our ranked list\n", "\n", "# bind this list with cleared votes_names list\n", "___\n", "\n", "# display the message\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Tasks 1.14-1.15\n", "\n", "Now we need to check all parties for passing the required threshold. As all ballots in our country are ranked one, we can expect many parties to be named in different ballots (the sum of probabilities wont be equal to 1, as it is in one-choice elections), so let's set a 40% passing threshold. \n", "1. Create the list of `passed_parties`; \n", "2. Calculate the share of votes for each party separately. If the party has not crossed the threshold, it won't be represented in succeed list; \n", "3. Display a list of succeed parties along with the corresponding message.\n", "\n", "example output: `['Justice and Truth', 'Animal Friends Party', 'Go Greens!', 'Yo-ho-ho Pirate Party'] passed the 40% threshold!`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1.14-1.15 answer \n", "\n", "# create a dictionary where you will count occurrences. it will store 0 for each existing party by default\n", "number_of_votes = ___\n", "for ___ in ___: \n", " number_of_votes.___({___: ___})\n", "\n", "# create an empty list of passed parties\n", "passed_parties ___\n", "\n", "# calculate total votes for each party - add 1 to the corresponding key of dictionary if the party in the ballot (remember how to detect if there is an element in the list)\n", "for vote in ___:\n", " for ___ in vote:\n", " number_of_votes[___] += vote. ___(___)\n", "\n", "# calculate the share of each party: if passed, append to the list of passed_parties (replace the absolute number of votes by the relative number of votes (fractions))\n", "for ___ in number_of_votes:\n", " number_of_votes[___] = number_of_votes[___] / ___\n", " if number_of_votes[___] ___:\n", " passed_parties. ___(___)\n", "\n", "# display the message\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**\\*Advanced task**. Complete tasks 1.14-1.15 with dict and list comprehensions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Task 1.16\n", "\n", "Preparations are almost complete. It remains to eliminate the parties that did not pass from the ballots.\n", "\n", "1. Create a list of failed parties and display it\n", "2. Remove these parties from ballots.\n", "\n", "*example output:* \n", "`['Harmony Party', 'Red Party', 'Adventure Alliance'] failed to pass the 5% threshold`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# task 1.16\n", "\n", "# create am empty list of failed parties\n", "failed_parties ___\n", "\n", "# append failed parties to the list (failed means not passed one)\n", "for party ___ general_list_of_parties:\n", " if party not in ___:\n", " failed_parties. ___(party)\n", "\n", "# display the message \n", "___\n", "\n", "# create an empty list of final votes\n", "final_votes ___\n", "\n", "# add votes without votes for failed parties to this list\n", "for vote in votes_names:\n", " new_vote = [party for party in vote if party not in failed_parties]\n", " final_votes. ___(new_vote)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, when all the votes are cleared and prepared, we are ready to start the count!\n", "\n", "This is the end of Part I of the Homework Assignment. Make sure that all code cells in your notebook are executable and without Errors, and submit your homework via [SmartLMS](https://edu.hse.ru/mod/assign/view.php?id=613078). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part II\n", "\n", "**Deadline: October 30, 11:59 pm**\n", "\n", "For the second part of your homework, you need to calculate the winner of election with different voting techniques. All descriptions were taken from the ***Voting Methods*** paper from [Stanford Encyclopedia of Philosophy Archive](https://plato.stanford.edu/archives/fall2019/entries/voting-methods/#toc).\n", "\n", "We will store each winner to the `final_results` dictionary to eventually compare all methods between one another." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Don't forget to run this code line!\n", "final_results = dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Single-Member Plurality Voting (first-past-the-post)\n", "\n", "Plurality rule (also called First-Past-the-Post rule) is the simplest method of votes counting, which is widely used despite its known problems.\n", "\n", "> Description: \n", ">\n", "> Each voter selects one candidate (or none if voters can abstain), and the candidate(s) with the most votes win." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To count with first-past-the-post voting method, recall which object stores counts of favourite candidates. \n", "1. Count number of 1st votes all over the ballots we have. Store results in some dictionary with party name as key and number of votes as value; \n", "2. Rearrange the dictionary with counted votes in a descending order by number of votes; \n", "4. Save the winner to `final_results` dictionary; \n", "3. Print a short news message announcing the results of the election (use string formatting to display name and number of votes, no copypasta here): \n", "\n", "*example output:* \n", "`The results of the last election: ` \n", "`According to the first-past-the-post voting system, Yo-ho-ho Pirate Party won with 9999 votes!`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Count 1st votes\n", "firsts = ___\n", "\n", "# create a dictionary with party as key and number of 1st votes as value\n", "pluralism = ___\n", "\n", "# rearrange the dict in descending order\n", "pluralism = sorted(pluralism.items(), key=lambda item: item[1], reverse=True)\n", "\n", "# Save the name of the winner party to the `final_result` dict\n", "final_results['pluralism'] = ___\n", "\n", "# Display the message\n", "___" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pluralism" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Borda Count\n", "\n", "Borda Counting is the most popular instance of ranking methods. Despite they are much more expensive than selecting a single candidate, they demonstrate more fair calculations.\n", "\n", "> Description: \n", ">\n", ">Each voter provides a ranking of the candidates. Then, a score (the Borda score) is assigned to each candidate by a voter as follows: If there are n candidates, give n−1 points to the candidate ranked first, n−2 points to the candidate ranked second,…, 1 point to the candidate ranked second to last and 0 points to candidate ranked last." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To complete Borda count, you need:\n", "1. Create new dict to store Borda scores;\n", "2. Increase the score of party according to its position in ballot; \n", "3. Rearrange the dictionary with counted votes in a descending order by number of votes; \n", "4. Save the Borda winner to `final_results` dictionary; \n", "6. Print a short news message announcing the results of the election (use string formatting to display name and number of votes, no copypasta here either): \n", "\n", "*example output:* \n", "`The results of the last election: ` \n", "`According to the Borda system, Yo-ho-ho Pirate Party won with 9999 votes!`\n", "\n", "*Note: as here we range all the candidates from the ballot, we need to determine how many parties were included by voter. This variable could also be used as a descending counter for Borda scores.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a dict for borda scores: each party has 0 by default\n", "borda = ___\n", "\n", "# Calculate number of votes for each party according to Borda rule: create a descending counter k for each ballot and update the scores in borda dict\n", "for ___\n", " k = ___\n", " for ___\n", " ___\n", " ___ -= 1\n", "\n", "# Sort this dict by value in descending order\n", "borda = sorted(borda.items(), key=lambda item: item[1], reverse=True)\n", "\n", "# Save the result\n", "final_results['borda'] = ___\n", "\n", "# Display the message\n", "___" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "borda" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Condorcet Count\n", "\n", "The idea of Condorcet voting is to make a set of one-on-one electons. The candidate that beat all others in one-by-one wins the election.\n", "\n", "> Description: \n", ">\n", "> Given a ranking from each voter, the majority relation orders the candidates in terms of how they perform in one-on-one elections. A candidate is called the Condorcet winner in an election scenario if she has the maximum of the majority ordering for that election scenario. The Condorcet loser is the candidate that is the minimum of the majority ordering." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's wrap up the idea of Condorcet winner in programming logic:\n", "\n", "1. Create new dict to store all pairwise comparisons;\n", "2. Calculate the share of ballots in which one candidate beats antoher, and store the share of such cases to corresponding slot.\n", "3. Save the Condorcet winner to `final_results` dictionary; \n", "4. Print a short news message announcing the results of the election (use string formatting to display name of party, no copypasta here either): \n", "\n", "*example output:* \n", "`The results of the last election: ` \n", "`According to the Condorcet system, Yo-ho-ho Pirate Party won!`\n", "\n", "*Note:*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Create an empty list of parvise combinations\n", "combinations ___\n", "\n", "# Store all possible pairs in this list as set of 2 party names\n", "for party1 in ___:\n", " for party2 in ___:\n", " combination = ___ # you need store pairs as sets of 2 values to avoid pair of party with itself\n", " # condition: skip current iteration if there's only one party in set or the combination already exists in list of combinations\n", " if ___ or ___:\n", " continue\n", " # append current combination to the list of combinations\n", " ___(___)\n", "\n", "# just to check you have correct number of pairs, use the formula n!/(2!*(n-2)!), where n is number of passed parties and ! is factorial\n", "combinations" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final_votes[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# a smith set is set of parties that beats all other parties in elections by Condorcet\n", "smith = set(passed_parties)\n", "\n", "# now you need to calculate the winner in each pair of parties\n", "for combination in ___\n", " combination = ___ # one cannot index a set, so you need to re-convert it \n", "\n", " # create a counter with 0 value\n", " counter = 0\n", "\n", " # calculate which party of the pair has the biggest priority for each ballot\n", " for vote in final_votes:\n", " # if the party1 exists in the ballot, determine its position\n", " if combination[0] in vote:\n", " candidate_0_score = vote[::-1].index(___)\n", " # otherwise its score is 0\n", " else:\n", " candidate_0_score = ___\n", " # if the party2 exists in the ballot, determine its position\n", " if combination[1] in vote:\n", " candidate_1_score = vote[::-1].index(___)\n", " # otherwise its score is 0\n", " else:\n", " candidate_1_score = ___\n", "\n", " # now we move counter correspondingly to the result of pairwise comparison\n", " if ___ > ___\n", " counter ___\n", " elif ___ > ___\n", " counter ___\n", " # skip current iteration if both parties are not on the ballot (score_1 == score_2 == 0) \n", " else:\n", " ___\n", "\n", " # display the pairwise winner with correct counter conditions (as we moved it both sides, it could be positive and negative)\n", " if ___\n", " print(f\"{combination[___]} beats {combination[___]}\")\n", " # remove loosing party from smith set is exists\n", " if combination[___] in smith:\n", " smith.remove(combination[___]) \n", " # repeat for the opposite condition\n", " elif ___\n", " print(f\"{___} beats {___}\")\n", " if combination[___] in smith:\n", " smith.remove(combination[___]) \n", " # what if there is no winner between parties (total scores are equal)?\n", " ___\n", " print(f\"___\")\n", "\n", "# store Condorcet winner to final result (remember that sets have no indexes, so you need to reconvert it if you want to extract particular element)\n", "final_results['condorcet'] = ___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Summary\n", "\n", "**\\*Advanced task:** to wrap-up the assignment, display the `final_results` dictionary of the winners by different methods. Which method seemed the fairest to you? What are the advantages and disadvantages of each method? Using the scientific literature, write a short, detailed answer (200 words minimum). Don't forget to cite references." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***Answer:** write your answer here* " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the end of Part II of the Homework Assignment. Make sure that all code cells in your notebook are executable and without Errors, and submit your homework via [LMS](http://www.linksaredeleted.com/). " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.10.4 64-bit", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "db967a82a5395c9886e36287b4fc3d4cdf41cf28b68e7cdaae12bdbf290d326d" } } }, "nbformat": 4, "nbformat_minor": 2 }