In [1]:
%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

Scrape Wikipedia Page

Yes, I could have also scraped the number of electors per state from the election pages. I decided to type them in, instead.


In [2]:
# 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

In [3]:
ec_votes = build_ec_votes_df()
ec_votes.tail()


Out[3]:
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

In [4]:
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

In [5]:
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

In [6]:
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

In [7]:
def file_for_year(year):
    return 'state-vote-data-{}.csv'.format(year)

In [8]:
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

In [9]:
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

In [10]:
vote_data = OrderedDict()
shapes = [(1996, 6, 22), (2000, 8, 28), (2004, 7, 25), (2008, 7, 25), (2012, 5, 19), (2016, 6, 22)]

In [11]:
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!

Export data


In [12]:
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)

In [13]:
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

In [14]:
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

In [15]:
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

In [16]:
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

In [17]:
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%.


In [18]:
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


In [19]:
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


In [20]:
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()



In [21]:
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()



In [22]:
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()



In [23]:
plot_waste_reduction_versus_votes_cast([2000, 2016])



In [24]:
plot_waste_reduction_versus_votes_cast([2008, 2012])



In [25]:
plot_waste_reduction_versus_votes_cast([2000, 2004])