%matplotlib inline
import pandas as pd
import numpy as np
from bs4 import BeautifulSoup
import urllib
from collections import OrderedDict
from math import ceil, floor
from operator import itemgetter
import matplotlib.pyplot as plt
from os.path import isfile
Yes, I could have also scraped the number of electors per state from the election pages. I decided to type them in, instead.
# list of state abbreviations
states = ['AL','AK','AZ','AR','CA','CO','CT','DC','DE','FL',
'GA','HI','ID','IL','IN','IA','KS','KY','LA','ME',
'MD','MA','MI','MN','MS','MO','MT','NE','NV','NH',
'NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI',
'SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY', 'US']
def append_sum(ec, ignore_senate_ec_votes=False):
subtract = 100 if ignore_senate_ec_votes else 0
ec.append(sum(ec)-subtract)
def build_ec_votes_df(ignore_senate_ec_votes=False):
ev = pd.DataFrame.from_dict(
{1992: [9, 3, 8, 6, 54, 8, 8, 3, 3, 25,
13, 4, 4, 22, 12, 7, 6, 8, 9, 4,
10, 12, 18, 10, 7, 11, 3, 5, 4, 4,
15, 5, 33, 14, 3, 21, 8, 7, 23, 4,
8, 3, 11, 32, 5, 3, 13, 11, 5, 11, 3, 538],
2004: [9, 3, 10, 6, 55, 9, 7, 3, 3, 27,
15, 4, 4, 21, 11, 7, 6, 8, 9, 4,
10, 12, 17, 10, 6, 11, 3, 5, 5, 4,
15, 5, 31, 15, 3, 20, 7, 7, 21, 4,
8, 3, 11, 34, 5, 3, 13, 11, 5, 10, 3, 538],
2012: [9, 3, 11, 6, 55, 9, 7, 3, 3, 29,
16, 4, 4, 20, 11, 6, 6, 8, 8, 4,
10, 11, 16, 10, 6, 10, 3, 5, 6, 4,
14, 5, 29, 15, 3, 18, 7, 7, 20, 4,
9, 3, 11, 38, 6, 3, 13, 12, 5, 10, 3, 538],
'states': states})
ev.set_index(keys='states', inplace=True)
if ignore_senate_ec_votes:
for series in ev.columns:
ev[series] -= 2
ev[series][-1] -= 100
ev[1996]=ev[1992]
ev[2000]=ev[1992]
ev[2016]=ev[2012]
ev[2008]=ev[2004]
ev.sort_index(axis=1, inplace=True)
return ev
ec_votes = build_ec_votes_df()
ec_votes.tail()
1992 | 1996 | 2000 | 2004 | 2008 | 2012 | 2016 | |
---|---|---|---|---|---|---|---|
states | |||||||
WA | 11 | 11 | 11 | 11 | 11 | 12 | 12 |
WV | 5 | 5 | 5 | 5 | 5 | 5 | 5 |
WI | 11 | 11 | 11 | 10 | 10 | 10 | 10 |
WY | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
US | 538 | 538 | 538 | 538 | 538 | 538 | 538 |
def get_headings(head_row, num_candidates):
"""
Parse the table header row for the candidate names.
head_row - the header row element containing cells with the candidate names
num_candidates - the number of candidate columns in the "Results_by_state" table, including "Other"
returns: list of length num_candidates with the candidate names
"""
headings = []
for cell in head_row.find_all('th', attrs={'colspan': '3'}):
children = list(cell.children)
if len(children) == 3:
headings.append((children[0].strip(), children[2].strip()))
else:
headings.append((children[0], None))
headings = headings[:num_candidates]
headings.append(('Total', None))
return headings
def get_table_rows(year, num_candidates):
"""
Scrape the U.S. Presidential election Wikipedia page for the results by state. header rows of the
year - string containing 4-digit election year
num_candidates - the number of candidate columns in the "Results_by_state" table, including "Other"
returns: a tuple; first element is a list of length num_candidates with the candidate names; second
element is a list of the table row elements needing to be parsed for the vote counts
"""
page = BeautifulSoup(urllib.request.urlopen(
'https://en.wikipedia.org/wiki/United_States_presidential_election,_'+str(year)).read(), "html.parser")
header = page.find('span', id='Results_by_state').parent
table_container = header.find_next_sibling('div')
table = table_container.table
if table is None:
table_container = table_container.find_next_sibling('div')
table = table_container.table
head_row = table.find('tr')
all_rows = list(table.find_all('tr'))
vote_rows = all_rows[2:]
return get_headings(head_row, num_candidates), vote_rows
def clean_and_convert(count_text):
"""
Clean up vote count text, and return an actual number.
count_text - the text from a vote count table cell
returns: int representing number of votes (N/A or blank imply zero)
"""
clean = count_text.strip().replace(',','').replace('.', '').replace('-','0').replace(
'N/A', '0').replace('★', '').replace('–', '').replace('−', '')
return int(clean) if len(clean) else 0
def file_for_year(year):
return 'state-vote-data-{}.csv'.format(year)
def read_in_data(year, num_candidates, total_column):
"""
Scrape the U.S. Presidential election Wikipedia for the given year. Requires a couple of numbers describing the
shape of the table.
year - string containing 4-digit election year
num_candidates - the number of candidate columns in the "Results_by_state" table, including "Other"
total_column - the column number that contains the total votes for each state/district, counting from zero
returns: a Pandas DataFrame, indexed by state/district, with columns for each candidate, total vote, and
state total electoral college votes
"""
filename = file_for_year(year)
if isfile(filename):
return pd.read_csv(filename, index_col=0)
headings, vote_rows = get_table_rows(year, num_candidates)
data = OrderedDict()
columns_with_vote_counts = list(range(2, 3*num_candidates, 3))
columns_with_vote_counts.append(total_column)
abbr_column = total_column + 1
vote_rows = list(zip(vote_rows, ['td'] * len(vote_rows)))
vote_rows[-1] = (vote_rows[-1][0], 'th') # last row uses <th> instead of <td>
for row, cell_type in vote_rows:
cells = list(row.find_all(cell_type))
try:
abbr = cells[abbr_column].string.strip()
except IndexError:
print('{}, {}, {}'.format(year, abbr_column, cells))
return None
if len(abbr)==2 or len(abbr)==6:
abbr = abbr[:2]
data[abbr] = [clean_and_convert(cells[i].text) for i in columns_with_vote_counts]
abbreviations = []
for h in headings:
abbreviations.append(''.join([s[0] for s in h[0].split()]))
vote_data = pd.DataFrame.from_dict(data, orient='index')
vote_data.columns=abbreviations
return vote_data
def check_totals(data, year):
state_totals_valid = True
for idx, state in data.iterrows():
calculated_sum = state.iloc[:-1].sum()
reported_sum = state['T']
if calculated_sum != reported_sum:
state_totals_valid = False
print('Uh Oh! {} in year {} data had reported vote total {}, but I calculated {}!'.format(
idx, year, reported_sum, calculated_sum))
print(state)
print()
candidate_totals_valid = True
trans = data.transpose()
for idx, candidate in trans.iterrows():
calculated_sum = candidate.iloc[:-1].sum()
reported_sum = candidate['US']
if calculated_sum != reported_sum:
if candidate_totals_valid:
print("For our purposes, errors in candidates' national totals can't affect the ")
print("EC allocation calculations. Only errors in total votes cast in each state ")
print("can affect the results.")
print()
candidate_totals_valid = False
print('Uh Oh! {} in year {} data had reported vote total {}, but I calculated {}!'.format(
idx, year, reported_sum, calculated_sum))
if not candidate_totals_valid:
print()
return state_totals_valid and candidate_totals_valid
vote_data = OrderedDict()
shapes = [(1996, 6, 22), (2000, 8, 28), (2004, 7, 25), (2008, 7, 25), (2012, 5, 19), (2016, 6, 22)]
for year, num_candidates, total_column in shapes:
vote_data[year] = read_in_data(year, num_candidates, total_column)
if vote_data and check_totals(vote_data[year], year):
print('{} Validated'.format(year))
print('--------------')
print(vote_data[year].loc['US'])
print()
1996 Validated -------------- BC 47400125 BD 39198755 RP 8085402 RN 685297 HB 485798 O 420024 T 96275401 Name: US, dtype: int64 2000 Validated -------------- GWB 50456002 AG 50999897 RN 2882955 PB 448895 HB 384431 HP 98020 JH 83714 O 51186 T 105405100 Name: US, dtype: int64 2004 Validated -------------- GWB 62040610 JK 59028444 RN 465151 MB 397265 MP 143630 DC 119859 O 99887 T 122294846 Name: US, dtype: int64 2008 Validated -------------- BO 69498516 JM 59948323 RN 739034 BB 523715 CB 199750 CM 161797 O 242685 T 131313820 Name: US, dtype: int64 Uh Oh! US in year 2012 data had reported vote total 129085410, but I calculated 129085407! BO 65915795 MR 60933504 GJ 1275971 JS 469627 O 490510 T 129085410 Name: US, dtype: int64 For our purposes, errors in candidates' national totals can't affect the EC allocation calculations. Only errors in total votes cast in each state can affect the results. Uh Oh! BO in year 2012 data had reported vote total 65915795, but I calculated 65915794! Uh Oh! T in year 2012 data had reported vote total 129085410, but I calculated 129085406! Uh Oh! US in year 2016 data had reported vote total 136669276, but I calculated 136670976! HC 65853514 DT 62984828 GJ 4489341 JS 1457218 EM 731991 O 1154084 T 136669276 Name: US, dtype: int64 For our purposes, errors in candidates' national totals can't affect the EC allocation calculations. Only errors in total votes cast in each state can affect the results. Uh Oh! HC in year 2016 data had reported vote total 65853514, but I calculated 65853516! Uh Oh! DT in year 2016 data had reported vote total 62984828, but I calculated 62984825! Uh Oh! GJ in year 2016 data had reported vote total 4489341, but I calculated 4489221! Uh Oh! JS in year 2016 data had reported vote total 1457218, but I calculated 1457216! Uh Oh! EM in year 2016 data had reported vote total 731991, but I calculated 731788! Uh Oh! O in year 2016 data had reported vote total 1154084, but I calculated 1152671! Uh Oh! T in year 2016 data had reported vote total 136669276, but I calculated 136669237!
def export_data(year, df):
filename = 'state-vote-data-{}.csv'.format(year)
df.to_csv(filename, sep=',')
for year, data in vote_data.items():
export_data(year, data)
def partial_result_from_dict(data, category):
"""
Prepare part of the final result from the given data, adding a category column to the index.
data - a dict-like object mapping states/districts to counts with the partial result,
e.g., {'state abbr': int_count, ... }
category - a string labelling the type of data, which is placed in a MultiIndex
"""
result = pd.DataFrame.from_dict(data, orient='index')
result.index.name = 'State'
result['Category'] = category
result.set_index('Category', append=True, inplace=True)
return result
def fair_efficient(df, ev):
"""
Compute a DataFrame representing fair and efficient allocation of Electoral College votes, and the
number of wasted popular votes if fair and efficient allocation is used.
df - a Pandas DataFrame, indexed by state/district, with columns for each candidate, total vote, and
state total electoral college votes
ev - a Pandas Series, indexed by state/district, containing the number of electoral votes each state gets to allocate
returns: a DataFrame multi-indexed by state/district and data category with candidate columns; data category = 'EC'
gives Electoral College allocations ; data category = 'Wasted' gives wasted popular votes, i.e., votes that
didn't end up counting towards an Elector
"""
ec_votes = OrderedDict()
wasted = OrderedDict()
for idx, st in df.iterrows():
if idx == 'US':
continue
c = OrderedDict(st.loc[:'O'])
e = OrderedDict()
r = {}
E = ev[idx]
V = st.loc['T']
if V == 0:
V = st.iloc[:-1].sum()
for candidate, pop_votes in c.items():
e[candidate] = floor(E * pop_votes / V)
r[candidate] = ceil(pop_votes - V * e[candidate] / E)
r = OrderedDict(sorted(r.items(), key=itemgetter(1), reverse=True))
remainder = E - sum(e.values())
for candidate in r.keys():
if candidate != 'O': # not mappable to a single candidate
e[candidate] += 1
remainder -= 1
r[candidate] = 0
if remainder == 0:
break
ec_votes[st.name] = e
wasted[st.name] = r
ec_votes = partial_result_from_dict(ec_votes, 'EC')
wasted = partial_result_from_dict(wasted, 'Wasted')
result = pd.concat([ec_votes, wasted], sort=True)
result.sort_index(level=['State', 'Category'], inplace=True)
return result
def wta(df, ev):
"""
Compute a DataFrame representing winner-take-all allocation (WTA) of Electoral College votes, and the
number of wasted popular votes if WTA allocation is used.
df - a Pandas DataFrame, indexed by state/district, with columns for each candidate, total vote, and
state total electoral college votes
ev - a Pandas Series, indexed by state/district, containing the number of electoral votes each state gets to allocate
returns: a DataFrame multi-indexed by state/district and data category with candidate columns; data category = 'EC'
gives Electoral College allocations ; data category = 'Wasted' gives wasted popular votes, i.e., votes that
didn't end up counting towards an Elector
"""
ec_votes = OrderedDict()
wasted = OrderedDict()
for idx, st in df.iterrows():
if idx == 'US':
continue
c = st.loc[:'O']
e = pd.Series(0, c.index)
r = c.copy()
E = ev[idx]
V = st.loc['T']
if V == 0:
V = st.iloc[:-1].sum()
idxmax = c.idxmax()
e[idxmax] = E
r[idxmax] = 0
r[idxmax] = c.max() - r.max() - 1
ec_votes[st.name] = e
wasted[st.name] = r
ec_votes = partial_result_from_dict(ec_votes, 'EC')
wasted = partial_result_from_dict(wasted, 'Wasted')
result = pd.concat([ec_votes, wasted], sort=True)
result.sort_index(level=['State', 'Category'], inplace=True)
return result
def calc_ec_allocations(methods, year, data, elec_votes):
"""
Calculate a set of Electoral College allocations for comparison purposes.
methods - an OrderedDict of functions for calculating allocations, with string keys used for labelling results
year - 4-digit election year (int)
data - a Pandas DataFrame, indexed by state/district, with columns for each candidate, total vote, and
state total electoral college votes
elec_votes - a Pandas DataFrame, indexed by state/district, with columns for each presidential election year,
containing each state's number of electoral college votes to allocate
returns: a 2-tuple; the first element is an OrderedDict using the same keys as methods, with DataFrames containing
the full state-by-state allocation results; the second elment is a summary DataFrame distilling all methods
for the nation as a whole, i.e., tells you what the final EC vote counts come to
"""
full_results = OrderedDict()
cols = []
total = data['T']['US']
for key, fn in methods.items():
result = fn(data, elec_votes[year])
full_results[key] = result
ec = result.loc[(slice(None), ['EC']),:].sum()
cols.append(ec)
wasted = result.loc[(slice(None), ['Wasted']),:].sum()
cols.append(wasted)
effgap = (wasted.sum() - 2 * wasted)/total
# effgap.loc[ec == 0] = np.nan
cols.append(effgap)
summary = pd.concat(cols, axis=1)
data_labels = ['Electors', 'Wasted Votes', 'Eff. Gap']
col_labels = [(m, dl) for m in methods.keys() for dl in data_labels]
summary.columns = pd.MultiIndex.from_tuples(col_labels)
return full_results, summary
def print_ec_summary(year, data, elec_votes, show_state_data=False):
"""
Print a useful summary of the calculations. By default, suppresses outputing the full dataset.
year - 4-digit election year (int)
data - a Pandas DataFrame, indexed by state/district, with columns for each candidate, total vote, and
state total electoral college votes
elec_votes - a Pandas DataFrame, indexed by state/district, with columns for each presidential election year, containing
each state's number of electoral college votes to allocate
show_state_data - whether to show the full computed results by state/district; defaults to False
"""
pd.options.display.max_rows = 102
print(year)
print('====')
methods = OrderedDict([('WTA', wta), ('FnE', fair_efficient)])
full_results, summary = calc_ec_allocations(methods, year, data, elec_votes)
print(summary)
print()
for key in methods.keys():
print('{} Total Wasted Votes: {}'.format(key, summary[key,'Wasted Votes'].sum()))
print()
if show_state_data:
for key, result in full_results.items():
print('{} Results by State:'.format(key))
print(result)
print()
Wasted votes are defined as votes for a candidate that didn't count towards allocating any electors. Efficiency gap is a measure of unfair skew for a candidate. I invert the usual sign, and calculate it as all other wasted votes minus a particular candates vote, all divided by the total votes cast. A positive number, and indicates that the candidate had fewer wasted votes than the sum of the other candidates' wasted votes. Negative means unfavorable. Magnitude matters. A fair allocation of Electors should keep all efficiency gaps under 7% to 10%.
for year, data in vote_data.items():
print_ec_summary(year, data, ec_votes, show_state_data = (year == 2016))
1996 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BC 379 20694429 0.179459 267 808881 0.027152 BD 159 28295406 0.021558 224 905165 0.025152 HB 0 485798 0.599268 0 485798 0.033863 O 0 420024 0.600634 0 420024 0.035230 RN 0 685297 0.595124 1 499751 0.033573 RP 0 8085402 0.441396 46 1112185 0.020851 WTA Total Wasted Votes: 58666356 FnE Total Wasted Votes: 4231804 2000 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap AG 267 28317499 0.040316 262 863577 0.020549 GWB 271 28617825 0.034618 263 765498 0.022410 HB 0 384431 0.570330 0 384431 0.029641 HP 0 98020 0.575764 0 98020 0.035075 JH 0 83714 0.576036 0 83714 0.035347 O 0 51186 0.576653 0 51186 0.035964 PB 0 448895 0.569107 0 448895 0.028418 RN 0 2882955 0.522922 13 1197844 0.014207 WTA Total Wasted Votes: 60884525 FnE Total Wasted Votes: 3893165 2004 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap DC 0 119859 0.562456 0 119859 0.024696 GWB 286 35491836 -0.016015 280 858574 0.012615 JK 252 32307540 0.036061 258 1175528 0.007432 MB 0 397265 0.557919 0 397265 0.020159 MP 0 143630 0.562067 0 143630 0.024307 O 0 99887 0.562782 0 99887 0.025022 RN 0 465151 0.556809 0 465151 0.019049 WTA Total Wasted Votes: 69025168 FnE Total Wasted Votes: 3259894 2008 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BB 0 523715 0.573062 0 523715 0.024099 BO 364 30576671 0.115335 289 979528 0.017156 CB 0 199750 0.577996 0 199750 0.029033 CM 0 161797 0.578574 0 161797 0.029611 JM 174 43854738 -0.086899 248 1473774 0.009628 O 0 242685 0.577342 0 242685 0.028379 RN 0 739034 0.569783 1 630653 0.022470 WTA Total Wasted Votes: 76298390 FnE Total Wasted Votes: 4211902 2012 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BO 332 30066196 0.114808 276 1044675 0.015143 GJ 0 1275971 0.560873 1 1132750 0.013778 JS 0 469627 0.573366 0 469627 0.024052 MR 206 42650154 -0.080163 261 906515 0.017283 O 0 490510 0.573043 0 490510 0.023729 WTA Total Wasted Votes: 74952458 FnE Total Wasted Votes: 4044077 2016 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap DT 305 30530954 0.153510 261 437117 0.039812 EM 0 731788 0.589587 1 543217 0.038259 GJ 0 4489221 0.534601 14 2089651 0.015629 HC 233 43680181 -0.038914 261 893253 0.033137 JS 0 1457216 0.578971 1 1199369 0.028657 O 0 1152671 0.583428 0 1152671 0.029340 WTA Total Wasted Votes: 82042031 FnE Total Wasted Votes: 6315278 WTA Results by State: DT EM GJ HC JS O State Category AK EC 3 0 0 0 0 0 Wasted 46932 0 18725 116454 5735 14307 AL EC 9 0 0 0 0 0 Wasted 588707 0 44467 729547 9391 21712 AR EC 6 0 0 0 0 0 Wasted 304377 13255 29829 380494 9473 12712 AZ EC 11 0 0 0 0 0 Wasted 91233 17449 106327 1161167 34345 1476 CA EC 0 0 0 55 0 0 Wasted 4483810 39596 478500 4269977 278657 147244 CO EC 0 0 0 9 0 0 Wasted 1202484 28917 144121 136385 38437 27418 CT EC 0 0 0 7 0 0 Wasted 673215 2108 48676 224356 22841 508 DC EC 0 0 0 3 0 0 Wasted 12723 0 4906 270106 4258 6551 DE EC 0 0 0 3 0 0 Wasted 185127 706 14757 50475 6103 1518 FL EC 29 0 0 0 0 0 Wasted 112910 0 207043 4504975 64399 25736 GA EC 16 0 0 0 0 0 Wasted 211140 13017 125306 1877963 7674 1668 HI EC 0 0 0 4 0 0 Wasted 128847 0 15954 138043 12737 4508 IA EC 6 0 0 0 0 0 Wasted 147313 12366 59186 653669 11479 28348 ID EC 4 0 0 0 0 0 Wasted 219289 46476 28331 189765 8496 8132 IL EC 0 0 0 20 0 0 Wasted 2146015 11655 209596 944713 76802 1627 IN EC 11 0 0 0 0 0 Wasted 524159 0 133993 1033126 7841 2712 KS EC 6 0 0 0 0 0 Wasted 244012 6520 55406 427005 23506 947 KY EC 8 0 0 0 0 0 Wasted 574116 22780 53752 628854 13913 1879 LA EC 8 0 0 0 0 0 Wasted 398483 8547 37978 780154 14031 9684 MA EC 0 0 0 11 0 0 Wasted 1090893 2719 138018 904302 47661 50559 MD EC 0 0 0 10 0 0 Wasted 943169 9630 79605 734758 35945 35169 ME EC 0 0 0 4 0 0 Wasted 335593 1887 38105 22141 14251 356 MI EC 16 0 0 0 0 0 Wasted 10703 8177 172136 2268839 51463 19126 MN EC 0 0 0 10 0 0 Wasted 1322951 53076 112972 44764 36985 51113 MO EC 10 0 0 0 0 0 Wasted 523442 7071 97359 1071068 25419 13177 MS EC 6 0 0 0 0 0 Wasted 215582 0 14435 485131 3731 5346 MT EC 3 0 0 0 0 0 Wasted 101530 2297 28037 177709 7970 1894 NC EC 15 0 0 0 0 0 Wasted 173314 0 130126 2189316 12105 47386 ND EC 3 0 0 0 0 0 Wasted 123035 0 21434 93758 3780 8594 NE EC 5 0 0 0 0 0 Wasted 211466 0 38946 284494 8775 16051 NH EC 0 0 0 4 0 0 Wasted 345790 1064 30777 2735 6496 11643 NJ EC 0 0 0 14 0 0 Wasted 1601933 0 72477 546344 37772 13586 NM EC 0 0 0 5 0 0 Wasted 319667 5825 74541 65566 9879 3173 NV EC 0 0 0 6 0 0 Wasted 512058 0 37384 27201 0 36683 NY EC 0 0 0 29 0 0 Wasted 2819534 10373 176598 1736589 107934 50890 OH EC 18 0 0 0 0 0 Wasted 446840 12574 174498 2394164 46271 27975 OK EC 7 0 0 0 0 0 Wasted 528760 0 83481 420375 0 0 OR EC 0 0 0 7 0 0 Wasted 782403 0 94231 219702 50002 72594 PA EC 20 0 0 0 0 0 Wasted 44291 6472 146715 2926441 49941 65176 RI EC 0 0 0 4 0 0 Wasted 180543 516 14746 71981 6220 9594 SC EC 9 0 0 0 0 0 Wasted 300015 21016 49204 855373 13034 9011 SD EC 3 0 0 0 0 0 Wasted 110262 0 20850 117458 0 4064 TN EC 11 0 0 0 0 0 Wasted 652229 11991 70397 870695 15993 16026 TX EC 38 0 0 0 0 0 Wasted 807178 42366 283492 3877868 71558 8895 UT EC 6 0 0 0 0 0 Wasted 204554 243690 39608 310676 9438 12787 VA EC 0 0 0 13 0 0 Wasted 1769443 54054 118274 212029 27638 33749 VT EC 0 0 0 3 0 0 Wasted 95369 639 10078 83203 6758 23650 WA EC 0 0 0 12 0 0 Wasted 1221747 0 160879 520970 58417 133258 WI EC 10 0 0 0 0 0 Wasted 22747 11855 106674 1382536 31072 38729 WV EC 5 0 0 0 0 0 Wasted 300576 1104 23004 188794 8075 4075 WY EC 3 0 0 0 0 0 Wasted 118445 0 13287 55973 2515 9655 FnE Results by State: DT EM GJ HC JS O State Category AK EC 2 0 0 1 0 0 Wasted 0 0 18725 10252 5735 14307 AL EC 6 0 0 3 0 0 Wasted 0 0 44467 21757 9391 21712 AR EC 4 0 0 2 0 0 Wasted 0 13255 29829 3616 9473 12712 AZ EC 5 0 1 5 0 0 Wasted 82781 17449 0 0 34345 1476 CA EC 18 0 2 34 1 0 Wasted 0 39596 0 0 20810 147244 CO EC 4 0 1 4 0 0 Wasted 0 28917 0 103205 38437 27418 CT EC 3 0 0 4 0 0 Wasted 0 2108 48676 0 22841 508 DC EC 0 0 0 3 0 0 Wasted 12723 0 4906 0 4258 6551 DE EC 1 0 0 2 0 0 Wasted 37189 706 14757 0 6103 1518 FL EC 14 0 1 14 0 0 Wasted 70281 0 0 0 64399 25736 GA EC 8 0 1 7 0 0 Wasted 31738 13017 0 77768 7674 1668 HI EC 1 0 0 3 0 0 Wasted 21613 0 15954 0 12737 4508 IA EC 3 0 0 3 0 0 Wasted 17968 12366 59186 0 11479 28348 ID EC 3 0 0 1 0 0 Wasted 0 46476 28331 17202 8496 8132 IL EC 8 0 1 11 0 0 Wasted 0 11655 0 45696 76802 1627 IN EC 6 0 1 4 0 0 Wasted 65491 0 0 38596 7841 2712 KS EC 4 0 0 2 0 0 Wasted 0 6520 55406 32205 23506 947 KY EC 5 0 0 3 0 0 Wasted 378 22780 53752 0 13913 1879 LA EC 5 0 0 3 0 0 Wasted 0 8547 37978 19267 14031 9684 MA EC 4 0 0 7 0 0 Wasted 0 2719 138018 0 47661 50559 MD EC 4 0 0 6 0 0 Wasted 0 9630 79605 9061 35945 35169 ME EC 2 0 0 2 0 0 Wasted 0 1887 38105 0 14251 356 MI EC 8 0 1 7 0 0 Wasted 0 8177 0 169153 51463 19126 MN EC 5 0 0 5 0 0 Wasted 0 53076 112972 0 36985 51113 MO EC 6 0 0 4 0 0 Wasted 0 7071 97359 0 25419 13177 MS EC 4 0 0 2 0 0 Wasted 0 0 14435 82012 3731 5346 MT EC 2 0 0 1 0 0 Wasted 0 2297 28037 11994 7970 1894 NC EC 8 0 0 7 0 0 Wasted 0 0 130126 0 12105 47386 ND EC 2 0 0 1 0 0 Wasted 0 0 21434 0 3780 8594 NE EC 3 0 0 2 0 0 Wasted 0 0 38946 0 8775 16051 NH EC 2 0 0 2 0 0 Wasted 0 1064 30777 0 6496 11643 NJ EC 6 0 0 8 0 0 Wasted 0 0 72477 0 37772 13586 NM EC 2 0 1 2 0 0 Wasted 340 5825 0 65907 9879 3173 NV EC 3 0 0 3 0 0 Wasted 0 0 37384 0 0 36683 NY EC 11 0 1 17 0 0 Wasted 0 10373 0 29755 107934 50890 OH EC 9 0 1 8 0 0 Wasted 92762 12574 0 0 46271 27975 OK EC 5 0 0 2 0 0 Wasted 0 0 83481 5235 0 0 OR EC 3 0 0 4 0 0 Wasted 0 0 94231 0 50002 72594 PA EC 10 0 0 10 0 0 Wasted 0 6472 146715 0 49941 65176 RI EC 2 0 0 2 0 0 Wasted 0 516 14746 20453 6220 9594 SC EC 5 0 0 4 0 0 Wasted 0 21016 49204 0 13034 9011 SD EC 2 0 0 1 0 0 Wasted 0 0 20850 0 0 4064 TN EC 7 0 0 4 0 0 Wasted 0 11991 70397 0 15993 16026 TX EC 20 0 1 17 0 0 Wasted 0 42366 47460 0 71558 8895 UT EC 3 1 0 2 0 0 Wasted 0 55119 39608 0 9438 12787 VA EC 6 0 0 7 0 0 Wasted 0 54054 118274 0 27638 33749 VT EC 1 0 0 2 0 0 Wasted 0 639 10078 0 6758 23650 WA EC 5 0 1 6 0 0 Wasted 0 0 0 84209 58417 133258 WI EC 5 0 0 5 0 0 Wasted 0 11855 106674 0 31072 38729 WV EC 4 0 0 1 0 0 Wasted 0 1104 23004 45910 8075 4075 WY EC 2 0 0 1 0 0 Wasted 3853 0 13287 0 2515 9655
def print_without_senate_ec_slots():
ev = build_ec_votes_df(ignore_senate_ec_votes=True)
for year, data in vote_data.items():
print_ec_summary(year, data, ev, show_state_data = (year == 2016))
print('The following computes assuming the extra 2 "Senate" Electors are eliminated.')
print("from each state, i.e., it's thought experiment on the effect of the")
print('"Constitutional Gerrymander". In this case, 219 would be the requisite number')
print('of electors to win.')
print()
print_without_senate_ec_slots()
The following computes assuming the extra 2 "Senate" Electors are eliminated. from each state, i.e., it's thought experiment on the effect of the "Constitutional Gerrymander". In this case, 219 would be the requisite number of electors to win. 1996 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BC 315 20694429 0.179459 214 1685777 0.022455 BD 121 28295406 0.021558 187 658137 0.043803 HB 0 485798 0.599268 0 485798 0.047383 O 0 420024 0.600634 0 420024 0.048750 RN 0 685297 0.595124 1 492615 0.047242 RP 0 8085402 0.441396 34 1791095 0.020267 WTA Total Wasted Votes: 58666356 FnE Total Wasted Votes: 5533446 2000 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap AG 225 28317499 0.040316 216 1289541 0.026633 GWB 211 28617825 0.034618 212 1384086 0.024840 HB 0 384431 0.570330 0 384431 0.043807 HP 0 98020 0.575764 0 98020 0.049242 JH 0 83714 0.576036 0 83714 0.049513 O 0 51186 0.576653 0 51186 0.050131 PB 0 448895 0.569107 0 448895 0.042584 RN 0 2882955 0.522922 8 1646514 0.019860 WTA Total Wasted Votes: 60884525 FnE Total Wasted Votes: 5386387 2004 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap DC 0 119859 0.562456 0 119859 0.038045 GWB 224 35491836 -0.016015 225 1648889 0.013039 JK 212 32307540 0.036061 211 2017699 0.007008 MB 0 397265 0.557919 0 397265 0.033508 MP 0 143630 0.562067 0 143630 0.037656 O 0 99887 0.562782 0 99887 0.038371 RN 0 465151 0.556809 0 465151 0.032398 WTA Total Wasted Votes: 69025168 FnE Total Wasted Votes: 4892380 2008 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BB 0 523715 0.573062 0 523715 0.035268 BO 306 30576671 0.115335 234 1905783 0.014218 CB 0 199750 0.577996 0 199750 0.040202 CM 0 161797 0.578574 0 161797 0.040780 JM 130 43854738 -0.086899 201 2014234 0.012566 O 0 242685 0.577342 0 242685 0.039548 RN 0 739034 0.569783 1 630653 0.033639 WTA Total Wasted Votes: 76298390 FnE Total Wasted Votes: 5678617 2012 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap BO 278 30066196 0.114808 225 1773681 0.016018 GJ 0 1275971 0.560873 1 1132750 0.025948 JS 0 469627 0.573366 0 469627 0.036223 MR 158 42650154 -0.080163 210 1748488 0.016408 O 0 490510 0.573043 0 490510 0.035899 WTA Total Wasted Votes: 74952458 FnE Total Wasted Votes: 5615056 2016 ==== WTA FnE Electors Wasted Votes Eff. Gap Electors Wasted Votes Eff. Gap DT 245 30530954 0.153510 207 1069733 0.041028 EM 0 731788 0.589587 1 488098 0.049539 GJ 0 4489221 0.534601 10 2573663 0.019019 HC 191 43680181 -0.038914 217 1272875 0.038055 JS 0 1457216 0.578971 1 1189639 0.039273 O 0 1152671 0.583428 0 1152671 0.039814 WTA Total Wasted Votes: 82042031 FnE Total Wasted Votes: 7746679 WTA Results by State: DT EM GJ HC JS O State Category AK EC 1 0 0 0 0 0 Wasted 46932 0 18725 116454 5735 14307 AL EC 7 0 0 0 0 0 Wasted 588707 0 44467 729547 9391 21712 AR EC 4 0 0 0 0 0 Wasted 304377 13255 29829 380494 9473 12712 AZ EC 9 0 0 0 0 0 Wasted 91233 17449 106327 1161167 34345 1476 CA EC 0 0 0 53 0 0 Wasted 4483810 39596 478500 4269977 278657 147244 CO EC 0 0 0 7 0 0 Wasted 1202484 28917 144121 136385 38437 27418 CT EC 0 0 0 5 0 0 Wasted 673215 2108 48676 224356 22841 508 DC EC 0 0 0 1 0 0 Wasted 12723 0 4906 270106 4258 6551 DE EC 0 0 0 1 0 0 Wasted 185127 706 14757 50475 6103 1518 FL EC 27 0 0 0 0 0 Wasted 112910 0 207043 4504975 64399 25736 GA EC 14 0 0 0 0 0 Wasted 211140 13017 125306 1877963 7674 1668 HI EC 0 0 0 2 0 0 Wasted 128847 0 15954 138043 12737 4508 IA EC 4 0 0 0 0 0 Wasted 147313 12366 59186 653669 11479 28348 ID EC 2 0 0 0 0 0 Wasted 219289 46476 28331 189765 8496 8132 IL EC 0 0 0 18 0 0 Wasted 2146015 11655 209596 944713 76802 1627 IN EC 9 0 0 0 0 0 Wasted 524159 0 133993 1033126 7841 2712 KS EC 4 0 0 0 0 0 Wasted 244012 6520 55406 427005 23506 947 KY EC 6 0 0 0 0 0 Wasted 574116 22780 53752 628854 13913 1879 LA EC 6 0 0 0 0 0 Wasted 398483 8547 37978 780154 14031 9684 MA EC 0 0 0 9 0 0 Wasted 1090893 2719 138018 904302 47661 50559 MD EC 0 0 0 8 0 0 Wasted 943169 9630 79605 734758 35945 35169 ME EC 0 0 0 2 0 0 Wasted 335593 1887 38105 22141 14251 356 MI EC 14 0 0 0 0 0 Wasted 10703 8177 172136 2268839 51463 19126 MN EC 0 0 0 8 0 0 Wasted 1322951 53076 112972 44764 36985 51113 MO EC 8 0 0 0 0 0 Wasted 523442 7071 97359 1071068 25419 13177 MS EC 4 0 0 0 0 0 Wasted 215582 0 14435 485131 3731 5346 MT EC 1 0 0 0 0 0 Wasted 101530 2297 28037 177709 7970 1894 NC EC 13 0 0 0 0 0 Wasted 173314 0 130126 2189316 12105 47386 ND EC 1 0 0 0 0 0 Wasted 123035 0 21434 93758 3780 8594 NE EC 3 0 0 0 0 0 Wasted 211466 0 38946 284494 8775 16051 NH EC 0 0 0 2 0 0 Wasted 345790 1064 30777 2735 6496 11643 NJ EC 0 0 0 12 0 0 Wasted 1601933 0 72477 546344 37772 13586 NM EC 0 0 0 3 0 0 Wasted 319667 5825 74541 65566 9879 3173 NV EC 0 0 0 4 0 0 Wasted 512058 0 37384 27201 0 36683 NY EC 0 0 0 27 0 0 Wasted 2819534 10373 176598 1736589 107934 50890 OH EC 16 0 0 0 0 0 Wasted 446840 12574 174498 2394164 46271 27975 OK EC 5 0 0 0 0 0 Wasted 528760 0 83481 420375 0 0 OR EC 0 0 0 5 0 0 Wasted 782403 0 94231 219702 50002 72594 PA EC 18 0 0 0 0 0 Wasted 44291 6472 146715 2926441 49941 65176 RI EC 0 0 0 2 0 0 Wasted 180543 516 14746 71981 6220 9594 SC EC 7 0 0 0 0 0 Wasted 300015 21016 49204 855373 13034 9011 SD EC 1 0 0 0 0 0 Wasted 110262 0 20850 117458 0 4064 TN EC 9 0 0 0 0 0 Wasted 652229 11991 70397 870695 15993 16026 TX EC 36 0 0 0 0 0 Wasted 807178 42366 283492 3877868 71558 8895 UT EC 4 0 0 0 0 0 Wasted 204554 243690 39608 310676 9438 12787 VA EC 0 0 0 11 0 0 Wasted 1769443 54054 118274 212029 27638 33749 VT EC 0 0 0 1 0 0 Wasted 95369 639 10078 83203 6758 23650 WA EC 0 0 0 10 0 0 Wasted 1221747 0 160879 520970 58417 133258 WI EC 8 0 0 0 0 0 Wasted 22747 11855 106674 1382536 31072 38729 WV EC 3 0 0 0 0 0 Wasted 300576 1104 23004 188794 8075 4075 WY EC 1 0 0 0 0 0 Wasted 118445 0 13287 55973 2515 9655 FnE Results by State: DT EM GJ HC JS O State Category AK EC 1 0 0 0 0 0 Wasted 0 0 18725 116454 5735 14307 AL EC 4 0 0 3 0 0 Wasted 104900 0 44467 0 9391 21712 AR EC 3 0 0 1 0 0 Wasted 0 13255 29829 97836 9473 12712 AZ EC 5 0 0 4 0 0 Wasted 0 17449 106327 17539 34345 1476 CA EC 17 0 2 33 1 0 Wasted 0 39596 0 0 11080 147244 CO EC 3 0 0 4 0 0 Wasted 10950 28917 144121 0 38437 27418 CT EC 2 0 0 3 0 0 Wasted 15247 2108 48676 0 22841 508 DC EC 0 0 0 1 0 0 Wasted 12723 0 4906 0 4258 6551 DE EC 0 0 0 1 0 0 Wasted 185127 706 14757 0 6103 1518 FL EC 13 0 1 13 0 0 Wasted 82312 0 0 0 64399 25736 GA EC 7 0 1 6 0 0 Wasted 31738 13017 0 114507 7674 1668 HI EC 1 0 0 1 0 0 Wasted 0 0 15954 52423 12737 4508 IA EC 2 0 0 2 0 0 Wasted 17968 12366 59186 0 11479 28348 ID EC 1 0 0 1 0 0 Wasted 63928 46476 28331 0 8496 8132 IL EC 7 0 1 10 0 0 Wasted 0 11655 0 14938 76802 1627 IN EC 5 0 1 3 0 0 Wasted 37865 0 0 121474 7841 2712 KS EC 2 0 0 2 0 0 Wasted 78817 6520 55406 0 23506 947 KY EC 4 0 0 2 0 0 Wasted 0 22780 53752 0 13913 1879 LA EC 4 0 0 2 0 0 Wasted 0 8547 37978 103810 14031 9684 MA EC 3 0 0 6 0 0 Wasted 0 2719 138018 0 47661 50559 MD EC 3 0 0 5 0 0 Wasted 0 9630 79605 0 35945 35169 ME EC 1 0 0 1 0 0 Wasted 0 1887 38105 0 14251 356 MI EC 7 0 0 7 0 0 Wasted 0 8177 172136 0 51463 19126 MN EC 4 0 0 4 0 0 Wasted 0 53076 112972 0 36985 51113 MO EC 5 0 0 3 0 0 Wasted 0 7071 97359 17842 25419 13177 MS EC 2 0 0 2 0 0 Wasted 96036 0 14435 0 3731 5346 MT EC 1 0 0 0 0 0 Wasted 0 2297 28037 177709 7970 1894 NC EC 7 0 0 6 0 0 Wasted 0 0 130126 902 12105 47386 ND EC 1 0 0 0 0 0 Wasted 0 0 21434 93758 3780 8594 NE EC 2 0 0 1 0 0 Wasted 0 0 38946 3085 8775 16051 NH EC 1 0 0 1 0 0 Wasted 0 1064 30777 0 6496 11643 NJ EC 5 0 0 7 0 0 Wasted 0 0 72477 0 37772 13586 NM EC 1 0 0 2 0 0 Wasted 53561 5825 74541 0 9879 3173 NV EC 2 0 0 2 0 0 Wasted 0 0 37384 0 0 36683 NY EC 10 0 1 16 0 0 Wasted 0 10373 0 0 107934 50890 OH EC 8 0 1 7 0 0 Wasted 92762 12574 0 0 46271 27975 OK EC 3 0 0 2 0 0 Wasted 77341 0 83481 0 0 0 OR EC 2 0 0 3 0 0 Wasted 0 0 94231 0 50002 72594 PA EC 9 0 0 9 0 0 Wasted 0 6472 146715 0 49941 65176 RI EC 1 0 0 1 0 0 Wasted 0 516 14746 20453 6220 9594 SC EC 4 0 0 3 0 0 Wasted 0 21016 49204 0 13034 9011 SD EC 1 0 0 0 0 0 Wasted 0 0 20850 117458 0 4064 TN EC 6 0 0 3 0 0 Wasted 0 11991 70397 34686 15993 16026 TX EC 19 0 1 16 0 0 Wasted 0 42366 34347 0 71558 8895 UT EC 2 1 0 1 0 0 Wasted 0 0 39608 27819 9438 12787 VA EC 5 0 0 6 0 0 Wasted 0 54054 118274 0 27638 33749 VT EC 0 0 0 1 0 0 Wasted 95369 639 10078 0 6758 23650 WA EC 4 0 1 5 0 0 Wasted 0 0 0 84209 58417 133258 WI EC 4 0 0 4 0 0 Wasted 0 11855 106674 0 31072 38729 WV EC 2 0 0 1 0 0 Wasted 13089 1104 23004 0 8075 4075 WY EC 1 0 0 0 0 0 Wasted 0 0 13287 55973 2515 9655
def plot_wasted():
colors = {1996: 'yellow', 2000: 'red', 2004:'magenta', 2008:'blue', 2012:'cyan', 2016:'black'}
plt.title('Wasted Votes')
plt.xlabel("Fair, Efficient")
plt.ylabel("Winner-Take-All")
for year, data in vote_data.items():
result_wta = wta(data, ec_votes[year])
fe = fair_efficient(data, ec_votes[year])
few = fe.loc[(slice(None), ['Wasted']),:].sum(axis=1)
wtaw = result_wta.loc[(slice(None), ['Wasted']),:].sum(axis=1)
plt.scatter(x=few, y=wtaw, color=colors[year], label=str(year), alpha=0.7)
plt.legend()
plt.ticklabel_format(style='plain')
plot_wasted()
def plot_wasted_sum():
colors = {1996: 'yellow', 2000: 'red', 2004:'magenta', 2008:'blue', 2012:'cyan', 2016:'black'}
plt.title('Wasted Votes')
plt.xlabel("Fair, Efficient")
plt.ylabel("Winner-Take-All")
for year, data in vote_data.items():
result_wta = wta(data, ec_votes[year])
fe = fair_efficient(data, ec_votes[year])
few = fe.loc[(slice(None), ['Wasted']),:].sum(axis=1).sum()
wtaw = result_wta.loc[(slice(None), ['Wasted']),:].sum(axis=1).sum()
plt.scatter(x=few, y=wtaw, color=colors[year], label=str(year), alpha=0.7)
plt.legend()
plt.ticklabel_format(style='plain')
plot_wasted_sum()
def plot_waste_reduction_versus_votes_cast(include_years=None):
colors = {1996: 'yellow', 2000: 'red', 2004:'magenta', 2008:'blue', 2012:'cyan', 2016:'black'}
plt.title('Waste Reduction versus Votes Cast by State')
plt.xlabel("Votes Cast")
plt.ylabel("Waste Reduction [%]")
plt.xscale('log')
plt.yscale('log')
for year, data in vote_data.items():
if include_years is None or year in include_years:
result_wta = wta(data, ec_votes[year])
fe = fair_efficient(data, ec_votes[year])
few = fe.loc[(slice(None), ['Wasted']),:].sum(axis=1)
wtaw = result_wta.loc[(slice(None), ['Wasted']),:].sum(axis=1)
wr = (1.0 - few/wtaw) * 100.0
votes = data['T'].drop(data.index[[51]])
plt.scatter(x=votes, y=wr, color=colors[year], label=str(year), alpha=0.7)
plt.legend()
# plt.ticklabel_format(style='sci')
plot_waste_reduction_versus_votes_cast()
plot_waste_reduction_versus_votes_cast([2000, 2016])
plot_waste_reduction_versus_votes_cast([2008, 2012])
plot_waste_reduction_versus_votes_cast([2000, 2004])