Skip to content

Commit 4545ca6

Browse files
committed
remove the inessentials. let's not write a complex tutorial on taming complexity, people
1 parent fd9067a commit 4545ca6

File tree

2 files changed

+52
-161
lines changed

2 files changed

+52
-161
lines changed

elex1/election_results.py

+24-76
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,34 @@
1-
#!/usr/bin/env python
21
"""
3-
A monstrosity of an election results script. Generates statewide results for races,
4-
based on county results.
2+
A monstrosity of an election results script. Calculates total votes for
3+
races and candidates, and determines if there is a winner in each race.
54
6-
This module bundles together way too much functionality and is near impossible to test,
7-
beyond eye-balling results.
5+
This module bundles together way too much functionality and is near impossible
6+
to test, beyond eye-balling results.
87
98
USAGE:
109
1110
python election_results.py
1211
13-
1412
OUTPUT:
1513
16-
Generates summary_results.csv
17-
14+
summary_results.csv
1815
1916
"""
2017
import csv
21-
import datetime
2218
import urllib
23-
from decimal import Decimal, getcontext
2419
from operator import itemgetter
2520
from collections import defaultdict
2621
from os.path import abspath, dirname, join
27-
from urllib import urlretrieve
2822

29-
# Set precision for all Decimals
30-
getcontext().prec = 2
3123

32-
# Download CSV of fake Virginia election results from GDocs
24+
# Download CSV of fake Virginia election results to root of project
3325
url = "https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=csv"
34-
35-
# Download the file to the root project directory /path/to/refactorin101/
36-
# NOTE: This will only download the file if it doesn't already exist
37-
# This approach is simplified for demo purposes. In a real-life application,
38-
# you'd likely have a considerable amount of additional code
39-
# to appropriately handle HTTP timeouts, 404s, and other real-world scenarios.
40-
# For example, you might retry a request several times after a timeout, and then
41-
# send an email alert that the site is non-responsive.
4226
filename = join(dirname(dirname(abspath(__file__))), 'fake_va_elec_results.csv')
4327
urllib.urlretrieve(url, filename)
4428

4529
# Create reader for ingesting CSV as array of dicts
4630
reader = csv.DictReader(open(filename, 'rb'))
4731

48-
# Normally, accessing a non-existent dictionary key would raise a KeyError.
4932
# Use defaultdict to automatically create non-existent keys with an empty dictionary as the default value.
5033
# See https://pydocs2cn.readthedocs.org/en/latest/library/collections.html#defaultdict-objects
5134
results = defaultdict(dict)
@@ -54,96 +37,63 @@
5437
for row in reader:
5538
# Parse name into first and last
5639
row['last_name'], row['first_name'] = [name.strip() for name in row['candidate'].split(',')]
57-
58-
# Standardize party abbreviations
59-
party = row['party'].strip().upper()
60-
if party.startswith('GOP'):
61-
party_clean = 'REP'
62-
elif party.startswith('DEM'):
63-
party_clean = 'DEM'
64-
else:
65-
party_clean = party
66-
row['party_clean'] = party_clean
67-
68-
# Standardize Office and district
69-
office = row['office']
70-
if 'Rep' in office:
71-
row['office_clean'] = 'U.S. House of Representatives'
72-
row['district'] = int(office.split('-')[-1])
73-
else:
74-
row['office_clean'] = office.strip()
75-
row['district'] = ''
76-
7740
# Convert total votes to an integer
7841
row['votes'] = int(row['votes'])
7942

80-
# Store county-level results by office/district pair, then by candidate key
81-
# Create unique candidate key from party and name, in case multiple candidates have same
43+
# Store county-level results by office/district pair, then by candidate party and raw name
8244
race_key = (row['office'], row['district'])
45+
# Create unique candidate key from party and name, in case multiple candidates have same
8346
cand_key = (row['party'], row['candidate'])
8447
# Below, setdefault initializes empty dict and list for the respective keys if they don't already exist.
8548
race = results[race_key]
8649
race.setdefault(cand_key, []).append(row)
8750

8851

89-
# Create a new set of summary results that includes each candidate's
90-
# statewide total votes, % of vote, winner flag, margin of victory, tie_race flag
52+
# Tally votes for Races and candidates and assign winners
9153
summary = defaultdict(dict)
9254

9355
for race_key, cand_results in results.items():
9456
all_votes = 0
95-
tie_race = ''
96-
cand_totals = []
57+
cands = []
9758
for cand_key, results in cand_results.items():
9859
# Populate a new candidate dict using one set of county results
9960
cand = {
100-
'candidate': results[0]['candidate'],
10161
'first_name': results[0]['first_name'],
10262
'last_name': results[0]['last_name'],
10363
'party': results[0]['party'],
104-
'party_clean': results[0]['party_clean'],
10564
'winner': '',
106-
'margin_of_vic': '',
10765
}
10866
# Calculate candidate total votes
109-
cand_statewide_total= sum([result['votes'] for result in results])
110-
cand['votes'] = cand_statewide_total
111-
cand_totals.append(cand)
67+
cand_total_votes = sum([result['votes'] for result in results])
68+
cand['votes'] = cand_total_votes
11269
# Add cand totals to racewide vote count
113-
all_votes += cand_statewide_total
70+
all_votes += cand_total_votes
71+
# And stash the candidate's data
72+
cands.append(cand)
11473

11574
# sort cands from highest to lowest vote count
116-
sorted_cands = sorted(cand_totals, key=itemgetter('votes'), reverse=True)
117-
118-
# Determine vote pct for each candiate
119-
for cand in sorted_cands:
120-
vote_pct = (Decimal(cand['votes']) / Decimal(all_votes)) * 100
121-
cand['vote_pct'] = "%s" % vote_pct.to_eng_string()
75+
sorted_cands = sorted(cands, key=itemgetter('votes'), reverse=True)
12276

123-
# Determine winner, if any, and assign margin of victory
77+
# Determine winner, if any
12478
first = sorted_cands[0]
12579
second = sorted_cands[1]
12680

127-
if first['votes'] == second['votes']:
128-
tie_race = 'X'
129-
else:
81+
if first['votes'] != second['votes']:
13082
first['winner'] = 'X'
131-
mov = (Decimal(first['votes'] - second['votes']) / all_votes) * 100
132-
first['margin_of_vic'] = "%s" % mov.to_eng_string()
13383

13484
# Get race metadata from one set of results
13585
result = cand_results.values()[0][0]
86+
# Add results to output
13687
summary[race_key] = {
137-
'all_votes': all_votes,
138-
'tie_race': tie_race,
13988
'date': result['date'],
140-
'office': result['office_clean'],
89+
'office': result['office'],
14190
'district': result['district'],
91+
'all_votes': all_votes,
14292
'candidates': sorted_cands,
14393
}
14494

14595

146-
# Output CSV of summary results
96+
# Write CSV of results
14797
outfile = join(dirname(abspath(__file__)), 'summary_results.csv')
14898
with open(outfile, 'wb') as fh:
14999
# We'll limit the output to cleanly parsed, standardized values
@@ -153,13 +103,10 @@
153103
'district',
154104
'last_name',
155105
'first_name',
156-
'party_clean',
106+
'party',
157107
'all_votes',
158108
'votes',
159-
'vote_pct',
160109
'winner',
161-
'margin_of_vic',
162-
'tie_race',
163110
]
164111
writer = csv.DictWriter(fh, fieldnames, extrasaction='ignore', quoting=csv.QUOTE_MINIMAL)
165112
writer.writeheader()
@@ -168,3 +115,4 @@
168115
for cand in cands:
169116
results.update(cand)
170117
writer.writerow(results)
118+

0 commit comments

Comments
 (0)