Skip to content

Instantly share code, notes, and snippets.

@jnturton
Last active August 15, 2016 10:55
Show Gist options
  • Save jnturton/0dd63258691be6bf04a04a8cef9cebef to your computer and use it in GitHub Desktop.
Save jnturton/0dd63258691be6bf04a04a8cef9cebef to your computer and use it in GitHub Desktop.
Shapley Values for the City of Johannesburg 2016 muncipal election
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"_Note: GitHub does not render LaTeX embedded in Jupyter Notebooks at the time of writing. Please paste the Github URL of this document into http://nbviewer.jupyter.org/ if your view is missing mathematics._\n",
"\n",
"### Introduction\n",
"\n",
"[This news article is the reason for the existence of this notebook.](http://www.bdlive.co.za/opinion/columnists/2016/08/11/putting-a-value-to-potential-political-coalitions-in-johannesburg).\n",
"\n",
"\n",
"As a brief recap: the 2016 South African municipal elections left the City of Joahnnesburg metro \"hung\" without any single political party holding a >50% majority of the 270 available seats. This has led these parties to consider coaltion governments. This notebook calculates a game-theoretic quantity, the Shapley Value, for each party."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import pandas as pd\n",
"import sys\n",
"import math\n",
"import functools\n",
"from itertools import chain, combinations\n",
"import cProfile"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The election results data are publically available online. They're displayed below sorted in descending order of seats and then by party name."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING *** file size (24448) not 512 + multiple of sector size (512)\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Party Name</th>\n",
" <th>Total Valid Votes</th>\n",
" <th>Total Valid Votes / Quota</th>\n",
" <th>Total Party Seats</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>AFRICAN NATIONAL CONGRESS</td>\n",
" <td>1121948</td>\n",
" <td>121.016935</td>\n",
" <td>121</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>DEMOCRATIC ALLIANCE</td>\n",
" <td>966192</td>\n",
" <td>104.216589</td>\n",
" <td>104</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>ECONOMIC FREEDOM FIGHTERS</td>\n",
" <td>279195</td>\n",
" <td>30.114874</td>\n",
" <td>30</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>INKATHA FREEDOM PARTY</td>\n",
" <td>43336</td>\n",
" <td>4.674361</td>\n",
" <td>5</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>AFRICAN INDEPENDENT CONGRESS</td>\n",
" <td>37870</td>\n",
" <td>4.084780</td>\n",
" <td>4</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>AFRICAN CHRISTIAN DEMOCRATIC PARTY</td>\n",
" <td>7475</td>\n",
" <td>0.806278</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>AL JAMA-AH</td>\n",
" <td>6707</td>\n",
" <td>0.723439</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>CONGRESS OF THE PEOPLE</td>\n",
" <td>4549</td>\n",
" <td>0.490670</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>PATRIOTIC ALLIANCE</td>\n",
" <td>3838</td>\n",
" <td>0.413979</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>UNITED DEMOCRATIC MOVEMENT</td>\n",
" <td>6570</td>\n",
" <td>0.708661</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>VRYHEIDSFRONT PLUS</td>\n",
" <td>8480</td>\n",
" <td>0.914680</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>AFRICAN PEOPLE'S CONVENTION</td>\n",
" <td>2653</td>\n",
" <td>0.286161</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>AFRICAN PEOPLE'S SOCIALIST PARTY</td>\n",
" <td>464</td>\n",
" <td>0.050049</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>AGANG SOUTH AFRICA</td>\n",
" <td>477</td>\n",
" <td>0.051451</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
" <td>AZANIAN PEOPLE'S ORGANISATION</td>\n",
" <td>2091</td>\n",
" <td>0.225542</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>BOLSHEVIKS PARTY OF SOUTH AFRICA</td>\n",
" <td>57</td>\n",
" <td>0.006148</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>16</th>\n",
" <td>BUILDING A COHESIVE SOCIETY</td>\n",
" <td>276</td>\n",
" <td>0.029770</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>17</th>\n",
" <td>INTERNATIONAL REVELATION CONGRESS</td>\n",
" <td>548</td>\n",
" <td>0.059109</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>18</th>\n",
" <td>OPERATION KHANYISA MOVEMENT</td>\n",
" <td>1791</td>\n",
" <td>0.193183</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>PAN AFRICANIST CONGRESS OF AZANIA</td>\n",
" <td>3203</td>\n",
" <td>0.345486</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>20</th>\n",
" <td>PATRIOTIC ASSOCIATION OF SOUTH AFRICA</td>\n",
" <td>1328</td>\n",
" <td>0.143242</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>21</th>\n",
" <td>PEOPLE'S CIVIC ORGANISATION</td>\n",
" <td>232</td>\n",
" <td>0.025024</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>22</th>\n",
" <td>PREM PEOPLES AGENDA</td>\n",
" <td>118</td>\n",
" <td>0.012728</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>23</th>\n",
" <td>THE SOCIALIST PARTY OF AZANIA</td>\n",
" <td>623</td>\n",
" <td>0.067199</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>24</th>\n",
" <td>TRULY ALLIANCE</td>\n",
" <td>2514</td>\n",
" <td>0.271168</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25</th>\n",
" <td>UBUNTU PARTY</td>\n",
" <td>442</td>\n",
" <td>0.047676</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Party Name Total Valid Votes \\\n",
"0 AFRICAN NATIONAL CONGRESS 1121948 \n",
"1 DEMOCRATIC ALLIANCE 966192 \n",
"2 ECONOMIC FREEDOM FIGHTERS 279195 \n",
"3 INKATHA FREEDOM PARTY 43336 \n",
"4 AFRICAN INDEPENDENT CONGRESS 37870 \n",
"5 AFRICAN CHRISTIAN DEMOCRATIC PARTY 7475 \n",
"6 AL JAMA-AH 6707 \n",
"7 CONGRESS OF THE PEOPLE 4549 \n",
"8 PATRIOTIC ALLIANCE 3838 \n",
"9 UNITED DEMOCRATIC MOVEMENT 6570 \n",
"10 VRYHEIDSFRONT PLUS 8480 \n",
"11 AFRICAN PEOPLE'S CONVENTION 2653 \n",
"12 AFRICAN PEOPLE'S SOCIALIST PARTY 464 \n",
"13 AGANG SOUTH AFRICA 477 \n",
"14 AZANIAN PEOPLE'S ORGANISATION 2091 \n",
"15 BOLSHEVIKS PARTY OF SOUTH AFRICA 57 \n",
"16 BUILDING A COHESIVE SOCIETY 276 \n",
"17 INTERNATIONAL REVELATION CONGRESS 548 \n",
"18 OPERATION KHANYISA MOVEMENT 1791 \n",
"19 PAN AFRICANIST CONGRESS OF AZANIA 3203 \n",
"20 PATRIOTIC ASSOCIATION OF SOUTH AFRICA 1328 \n",
"21 PEOPLE'S CIVIC ORGANISATION 232 \n",
"22 PREM PEOPLES AGENDA 118 \n",
"23 THE SOCIALIST PARTY OF AZANIA 623 \n",
"24 TRULY ALLIANCE 2514 \n",
"25 UBUNTU PARTY 442 \n",
"\n",
" Total Valid Votes / Quota Total Party Seats \n",
"0 121.016935 121 \n",
"1 104.216589 104 \n",
"2 30.114874 30 \n",
"3 4.674361 5 \n",
"4 4.084780 4 \n",
"5 0.806278 1 \n",
"6 0.723439 1 \n",
"7 0.490670 1 \n",
"8 0.413979 1 \n",
"9 0.708661 1 \n",
"10 0.914680 1 \n",
"11 0.286161 0 \n",
"12 0.050049 0 \n",
"13 0.051451 0 \n",
"14 0.225542 0 \n",
"15 0.006148 0 \n",
"16 0.029770 0 \n",
"17 0.059109 0 \n",
"18 0.193183 0 \n",
"19 0.345486 0 \n",
"20 0.143242 0 \n",
"21 0.025024 0 \n",
"22 0.012728 0 \n",
"23 0.067199 0 \n",
"24 0.271168 0 \n",
"25 0.047676 0 "
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jhb_results_all = pd.read_excel(\"http://www.elections.org.za/content/LGEPublicReports/402/Seat%20Calculation%20Detail/GP/JHB.xls\",\n",
" skiprows=22,\n",
" header=0,\n",
" skipfooter=1,\n",
" parse_cols=[0, 3, 5, 17])\n",
"\n",
"jhb_results_all.sort_values(['Total Party Seats', 'Party Name'], ascending=[False, True]).reset_index(drop=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For a coalitional game of $N$ players with pay-off function $v:2^{N} \\rightarrow \\mathbb R$, the Shapley Value for player $i$ is\n",
"\n",
"$$\\phi_i(N,v) = \\frac{1}{|N|!} \\sum_{S\\subseteq N\\setminus\\{i\\}} |S|!(|N|-|S|-1)! \\left ( v(S \\cup \\{i\\}) - v(S) \\right ).$$\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Moving back to the terminilogy of the elections, we note the following.\n",
"1. For a coalition $S$, $v(S) = 1$ if $S$ holds a majority of the available seats and $0$ if not.\n",
"2. A party with zero seats has a Shapley Value of zero.\n",
"3. A party with zero seats does not have any effect on the Shapley Values of the other parties.\n",
"4. Two parties with same number of seats have the same Shapley Value.\n",
"\n",
"This means that we have _at most_ 7 distinct Shapley Values amongst the 26 parties, and can afford to restrict the difficult part of the calculation to the pool of 11 parties who won at least one seat. The Python script to follow has a computational complexity of at least $O(N \\times 2^{N+1})$ so don't try it on large games. "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"num_parties = None\n",
"total_seats = None\n",
"seats_for_majority = None\n",
"np_seat_counts = None\n",
"\n",
"\n",
"def init(seat_counts):\n",
" global num_parties\n",
" global total_seats\n",
" global seats_for_majority\n",
" global np_seat_counts\n",
" \n",
" num_parties = len(seat_counts)\n",
" total_seats = sum(seat_counts)\n",
" seats_for_majority = math.floor(total_seats / 2 + 1)\n",
" np_seat_counts = list(seat_counts)\n",
" shapley_value.cache_clear()\n",
" num_seats.cache_clear()\n",
"\n",
" \n",
"def powerset(s):\n",
" \"\"\"\n",
" Generator that yields the powerset of s\n",
" \"\"\"\n",
" for subset in chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)):\n",
" yield frozenset(subset) # frozenset is hashable, a requirement on function args by lru_cache\n",
"\n",
"\n",
"@functools.lru_cache(maxsize=50)\n",
"def memo_counting_function(num_coalition_parties, num_parties):\n",
" \"\"\"\n",
" Returns the counting function factor from the Shapley Value formula: |S|!(|N|-|S|-1)!\n",
" Memoised because of frequent reuse.\n",
" \"\"\"\n",
" return math.factorial(num_coalition_parties) * math.factorial(num_parties - num_coalition_parties - 1)\n",
"\n",
"\n",
"\n",
"@functools.lru_cache(maxsize=1000000)\n",
"def num_seats(coalition):\n",
" \"\"\"\n",
" Recursively calcuates the total number of seats held by a coalition of parties.\n",
" While this is not a good way to sum in general, here we will be called to sum \n",
" every subset from the total set of parties which will result in frequent reuse\n",
" of previously calculated sums via the memoising decorater.\n",
" \"\"\"\n",
" if len(coalition) == 0:\n",
" return 0\n",
" else:\n",
" x = next(iter(coalition))\n",
" return np_seat_counts[x] + num_seats(coalition.difference([x]))\n",
"\n",
" \n",
"@functools.lru_cache(maxsize=50)\n",
"def shapley_value(seats_won):\n",
" \"\"\"\n",
" The Shapley Value calulation. Loops over the powerset of all parties other than\n",
" the one considered and calculates the marginal contribtuion of the party in each instance.\n",
" Memoised because of the symmetry axiom satisfied by the Shapley Value. \n",
" \"\"\"\n",
" sum_marginal_contribs = 0\n",
" \n",
" # it is sufficient to consider _any_ party with the same number of seats as the argument\n",
" party_index = np_seat_counts.index(seats_won)\n",
" other_party_indices = list(range(num_parties))\n",
" other_party_indices.remove(party_index)\n",
"\n",
" for coalition_others in powerset(other_party_indices):\n",
" num_seats_without_party = num_seats(coalition_others)\n",
" num_seats_with_party = num_seats(coalition_others.union([party_index]))\n",
"\n",
" party_contrib = 1 if num_seats_with_party \\\n",
" >= seats_for_majority and num_seats_without_party < seats_for_majority else 0\n",
"\n",
" sum_marginal_contribs += party_contrib * \\\n",
" memo_counting_function(len(coalition_others), num_parties)\n",
"\n",
" return sum_marginal_contribs / math.factorial(num_parties)\n",
"\n",
"\n",
"def ith_shapley_value(party_index):\n",
" return shapley_value(np_seat_counts[party_index])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's start by running the toy-model calculation discussed in the news article."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[0.5, 0.16666666666666666, 0.16666666666666666, 0.16666666666666666]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"init([40,25,15,15])\n",
"list(map(ith_shapley_value, range(len(np_seat_counts))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's run the calculation for the parties that won at least one seat in the Johannesburg election. The City of Johannesburg 2016/2017 budget figure was taken from http://www.joburg.org.za/images/stories/2016/March/2016-17%20budget%20book.pdf."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Party Name</th>\n",
" <th>Total Party Seats</th>\n",
" <th>Shapley Value</th>\n",
" <th>Shapley Value (ZAR)</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>AFRICAN NATIONAL CONGRESS</td>\n",
" <td>121</td>\n",
" <td>0.341919</td>\n",
" <td>R15,410,124,284.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>DEMOCRATIC ALLIANCE</td>\n",
" <td>104</td>\n",
" <td>0.303030</td>\n",
" <td>R13,657,421,818.18</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>ECONOMIC FREEDOM FIGHTERS</td>\n",
" <td>30</td>\n",
" <td>0.303030</td>\n",
" <td>R13,657,421,818.18</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>INKATHA FREEDOM PARTY</td>\n",
" <td>5</td>\n",
" <td>0.008586</td>\n",
" <td>R386,960,284.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>AFRICAN INDEPENDENT CONGRESS</td>\n",
" <td>4</td>\n",
" <td>0.008586</td>\n",
" <td>R386,960,284.85</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>AFRICAN CHRISTIAN DEMOCRATIC PARTY</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>AL JAMA-AH</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>CONGRESS OF THE PEOPLE</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>PATRIOTIC ALLIANCE</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>UNITED DEMOCRATIC MOVEMENT</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>VRYHEIDSFRONT PLUS</td>\n",
" <td>1</td>\n",
" <td>0.005808</td>\n",
" <td>R261,767,251.52</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>AFRICAN PEOPLE'S CONVENTION</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>AFRICAN PEOPLE'S SOCIALIST PARTY</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>AGANG SOUTH AFRICA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
" <td>AZANIAN PEOPLE'S ORGANISATION</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>BOLSHEVIKS PARTY OF SOUTH AFRICA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>16</th>\n",
" <td>BUILDING A COHESIVE SOCIETY</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>17</th>\n",
" <td>INTERNATIONAL REVELATION CONGRESS</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>18</th>\n",
" <td>OPERATION KHANYISA MOVEMENT</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>PAN AFRICANIST CONGRESS OF AZANIA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>20</th>\n",
" <td>PATRIOTIC ASSOCIATION OF SOUTH AFRICA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>21</th>\n",
" <td>PEOPLE'S CIVIC ORGANISATION</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>22</th>\n",
" <td>PREM PEOPLES AGENDA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>23</th>\n",
" <td>THE SOCIALIST PARTY OF AZANIA</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>24</th>\n",
" <td>TRULY ALLIANCE</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25</th>\n",
" <td>UBUNTU PARTY</td>\n",
" <td>0</td>\n",
" <td>0.000000</td>\n",
" <td>R0.00</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Party Name Total Party Seats Shapley Value \\\n",
"0 AFRICAN NATIONAL CONGRESS 121 0.341919 \n",
"1 DEMOCRATIC ALLIANCE 104 0.303030 \n",
"2 ECONOMIC FREEDOM FIGHTERS 30 0.303030 \n",
"3 INKATHA FREEDOM PARTY 5 0.008586 \n",
"4 AFRICAN INDEPENDENT CONGRESS 4 0.008586 \n",
"5 AFRICAN CHRISTIAN DEMOCRATIC PARTY 1 0.005808 \n",
"6 AL JAMA-AH 1 0.005808 \n",
"7 CONGRESS OF THE PEOPLE 1 0.005808 \n",
"8 PATRIOTIC ALLIANCE 1 0.005808 \n",
"9 UNITED DEMOCRATIC MOVEMENT 1 0.005808 \n",
"10 VRYHEIDSFRONT PLUS 1 0.005808 \n",
"11 AFRICAN PEOPLE'S CONVENTION 0 0.000000 \n",
"12 AFRICAN PEOPLE'S SOCIALIST PARTY 0 0.000000 \n",
"13 AGANG SOUTH AFRICA 0 0.000000 \n",
"14 AZANIAN PEOPLE'S ORGANISATION 0 0.000000 \n",
"15 BOLSHEVIKS PARTY OF SOUTH AFRICA 0 0.000000 \n",
"16 BUILDING A COHESIVE SOCIETY 0 0.000000 \n",
"17 INTERNATIONAL REVELATION CONGRESS 0 0.000000 \n",
"18 OPERATION KHANYISA MOVEMENT 0 0.000000 \n",
"19 PAN AFRICANIST CONGRESS OF AZANIA 0 0.000000 \n",
"20 PATRIOTIC ASSOCIATION OF SOUTH AFRICA 0 0.000000 \n",
"21 PEOPLE'S CIVIC ORGANISATION 0 0.000000 \n",
"22 PREM PEOPLES AGENDA 0 0.000000 \n",
"23 THE SOCIALIST PARTY OF AZANIA 0 0.000000 \n",
"24 TRULY ALLIANCE 0 0.000000 \n",
"25 UBUNTU PARTY 0 0.000000 \n",
"\n",
" Shapley Value (ZAR) \n",
"0 R15,410,124,284.85 \n",
"1 R13,657,421,818.18 \n",
"2 R13,657,421,818.18 \n",
"3 R386,960,284.85 \n",
"4 R386,960,284.85 \n",
"5 R261,767,251.52 \n",
"6 R261,767,251.52 \n",
"7 R261,767,251.52 \n",
"8 R261,767,251.52 \n",
"9 R261,767,251.52 \n",
"10 R261,767,251.52 \n",
"11 R0.00 \n",
"12 R0.00 \n",
"13 R0.00 \n",
"14 R0.00 \n",
"15 R0.00 \n",
"16 R0.00 \n",
"17 R0.00 \n",
"18 R0.00 \n",
"19 R0.00 \n",
"20 R0.00 \n",
"21 R0.00 \n",
"22 R0.00 \n",
"23 R0.00 \n",
"24 R0.00 \n",
"25 R0.00 "
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jhb_results = jhb_results_all[jhb_results_all['Total Party Seats'] > 0].reset_index(drop=True)\n",
"init(jhb_results['Total Party Seats'])\n",
"\n",
"for i in range(num_parties):\n",
" jhb_results.set_value(i, 'Shapley Value', ith_shapley_value(i))\n",
" \n",
"# recombine with zero-seat parties, calculate Shapley ZAR value and sort for output\n",
"out = pd.concat([jhb_results,jhb_results_all[jhb_results_all['Total Party Seats'] == 0]])\n",
"out['Shapley Value'].fillna(0, inplace=True)\n",
"\n",
"jhb_budget = 45069492000 # ZAR in 2016-17\n",
"\n",
"out['Shapley Value (ZAR)'] = (out['Shapley Value'] * jhb_budget).map('R{:,.2f}'.format)\n",
"out.sort_values(by=['Total Party Seats', 'Party Name'], ascending=[False,True], inplace=True)\n",
"\n",
"# check that total Shapley Value = 1 to within machine precision\n",
"assert abs(sum(out['Shapley Value']) - 1.0) <= sys.float_info.epsilon\n",
"\n",
"out[['Party Name', 'Total Party Seats', 'Shapley Value', 'Shapley Value (ZAR)']].reset_index(drop=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"© James Turton 2016\n",
"[MIT License](https://opensource.org/licenses/MIT)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.5.2+"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment