Add files via upload
This commit is contained in:
parent
49aa431222
commit
48f57fbbd0
@ -6,7 +6,7 @@
|
||||
"source": [
|
||||
"# Tracking Trump: Electoral Votes\n",
|
||||
"\n",
|
||||
"[Morning Consult](https://morningconsult.com) has a page called [Tracking Trump](https://morningconsult.com/tracking-trump/) that summarizes the presidential approval polls on a state-by-state basis, and tells you how many states Trump has a net positive or net negative approval rating. But I don't care about the number of *states*; I care about the number of *electoral votes*. This notebook computes that for me."
|
||||
"**[Morning Consult](https://morningconsult.com)** has a page called **[Tracking Trump](https://morningconsult.com/tracking-trump/)** that summarizes the presidential approval polls on a state-by-state basis, and tells you the number of states in which Trump has a net positive or net negative approval rating. But if you're thinking about the 2020 election, you don't care about the number of *states*, you care about the number of *electoral votes*. Let's do some computation to figure that out."
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -17,35 +17,39 @@
|
||||
"source": [
|
||||
"import urllib.request\n",
|
||||
"import re\n",
|
||||
"from collections import namedtuple, defaultdict\n",
|
||||
"import collections \n",
|
||||
"\n",
|
||||
"Row = namedtuple('Row', 'state delta jan17app jan17dis jan17err app dis err')\n",
|
||||
"State = collections.namedtuple('State', 'name ev app dis')\n",
|
||||
"\n",
|
||||
"def read_data(url='https://morningconsult.com/tracking-trump/'):\n",
|
||||
" \"Fetch data from the website and return a list of `Row` objects.\"\n",
|
||||
"EVs = dict(AL=9, AK=3, AZ=11, AR=6, CA=55, CO=9, CT=7, DE=3, DC=3, FL=29, \n",
|
||||
" GA=16, HI=4, ID=4, IL=20, IN=11, IA=6, KS=6, KY=8, LA=8, ME=4, \n",
|
||||
" MD=10, MA=11, MI=16, MN=10, MS=6, MO=10, MT=3, NE=5, NV=6, NH=4, \n",
|
||||
" NJ=14, NM=5, NY=29, NC=15, ND=3, OH=18, OK=7, OR=7, PA=20, RI=4, \n",
|
||||
" SC=9, SD=3, TN=11, TX=38, UT=6, VT=3, VA=13, WA=12, WV=5, WI=10, WY=3)\n",
|
||||
"\n",
|
||||
"def parse_page(url):\n",
|
||||
" \"Fetch data from the website and parse into a list of `State`s.\"\n",
|
||||
" with urllib.request.urlopen(url) as response:\n",
|
||||
" html = response.read().decode('utf-8')\n",
|
||||
" rows = re.findall(r'<tr(.*?)</tr>', html, re.S)\n",
|
||||
" return [parse_row(row) for row in rows[1:]]\n",
|
||||
" \n",
|
||||
"def parse_row(row):\n",
|
||||
" \"Parse an html string into a `Row` object.\"\n",
|
||||
" state, *nums = re.findall('>([^>]*?)</td', row.replace('%', ''))\n",
|
||||
" return Row(state, *map(int, nums))\n",
|
||||
" \"Parse an html string into a `State`.\"\n",
|
||||
" # Rows are: |name|delta|Jan17 app|Jan 17 dis|Jan 17 err|now app|now dis|now err|\n",
|
||||
" name, *_, app, dis, _ = re.findall('>([^>]*?)</td', row.replace('%', ''))\n",
|
||||
" return State(name, EVs[name], int(app), int(dis))\n",
|
||||
"\n",
|
||||
"def net(row): \"Approval minus disapproval.\"; return row.app - row.dis \n",
|
||||
"def net(state): \"Net approval minus disapproval.\"; return state.app - state.dis \n",
|
||||
"def undecided(state): \"Percent of undecided voters\"; return 100 - state.app - state.dis\n",
|
||||
"def allocation(amount, Δ): return amount if Δ > 0 else amount/2 if Δ == 0 else 0\n",
|
||||
"\n",
|
||||
"rows = read_data()\n",
|
||||
"states = parse_page('https://morningconsult.com/tracking-trump/')\n",
|
||||
"\n",
|
||||
"def EV(swing=0, rows=rows):\n",
|
||||
" \"How many electoral votes would Trump win today, given a swing.\"\n",
|
||||
" return sum(votes[row.state] for row in rows if net(row) + swing > 0)\n",
|
||||
"\n",
|
||||
"votes = dict(AL=9, AK=3, AZ=11, AR=6, CA=55, CO=9, CT=7, DE=3, DC=3, FL=29, \n",
|
||||
" GA=16, HI=4, ID=4, IL=20, IN=11, IA=6, KS=6, KY=8, LA=8, ME=4, \n",
|
||||
" MD=10, MA=11, MI=16, MN=10, MS=6, MO=10, MT=3, NE=5, NV=6, NH=4, \n",
|
||||
" NJ=14, NM=5, NY=29, NC=15, ND=3, OH=18, OK=7, OR=7, PA=20, RI=4, \n",
|
||||
" SC=9, SD=3, TN=11, TX=38, UT=6, VT=3, VA=13, WA=12, WV=5, WI=10, WY=3)"
|
||||
"def EV(swing=0, states=states):\n",
|
||||
" \"Total electoral votes in net positive states (plus 1/2 net zero), after applying swing.\"\n",
|
||||
" return sum(allocation(state.ev, net(state) + swing) \n",
|
||||
" for state in states)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -56,7 +60,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"164"
|
||||
"126.5"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
@ -72,9 +76,9 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This says that Trump has a net positive approval (i.e. more approval than disapproval) in states with a total of **164** electoral votes. You need **270** to win.\n",
|
||||
"This says that Trump has a net positive approval in states with a total of **126.5** electoral votes. It is a fraction because I allocate half the electoral votes for states with a net zero approval. You need **270** to win. The 126.5 number is down from 164 in Jan 2019, and 448 in Jan 2017.\n",
|
||||
"\n",
|
||||
"But of course these are approval polls, not ballots, and don't translate directly to votes. Things can change; the election is a long ways away, we don't know who's running, we don't know if there are third party candidate(s), and we don't know if there is systematic bias in the polling data. In the table below, I list the number of electoral votes Trump would get assuming he gets an increase of net approval of 0 to 9 percentage points across the board in every state. We see he would need a **7** percent upswing from the polling data in order to win."
|
||||
"But of course these are approval polls, not ballots, and don't translate directly to votes. Things can change; the election is a long ways away, we don't know who's running, we don't know if there are third party candidate(s), and we don't know if there is systematic bias in the polling data. In the table below, I list the number of electoral votes Trump would get assuming the vote is determined by the current net approval plus a swing of between 0 and 11 percentage points across the board in every state. We see he would need an **11** percent upswing from the polling data in order to put together wore than 270 electoral votes, and a 6 point swing to exceed the total Clinton got in 2016."
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -85,16 +89,18 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{0: 164,\n",
|
||||
" 1: 164,\n",
|
||||
" 2: 164,\n",
|
||||
" 3: 164,\n",
|
||||
" 4: 255,\n",
|
||||
" 5: 255,\n",
|
||||
" 6: 255,\n",
|
||||
" 7: 279,\n",
|
||||
" 8: 279,\n",
|
||||
" 9: 294}"
|
||||
"{0: 126.5,\n",
|
||||
" 1: 148,\n",
|
||||
" 2: 161.0,\n",
|
||||
" 3: 174,\n",
|
||||
" 4: 196.0,\n",
|
||||
" 5: 218,\n",
|
||||
" 6: 230.0,\n",
|
||||
" 7: 242,\n",
|
||||
" 8: 247.5,\n",
|
||||
" 9: 253,\n",
|
||||
" 10: 269.5,\n",
|
||||
" 11: 288.0}"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
@ -103,14 +109,14 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"{m: EV(m) for m in range(10)}"
|
||||
"{swing: EV(swing) for swing in range(12)}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here are the states sorted by current net approval:"
|
||||
"Now we show each state, sorted by net approval, with their number of votes, net approval, and the three approval percentages: positive, negative, undecided:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -122,73 +128,72 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"DC: 3 EV, net -62 (+17 -79 ?4)\n",
|
||||
"VT: 3 EV, net -31 (+33 -64 ?3)\n",
|
||||
"CA: 55 EV, net -29 (+33 -62 ?5)\n",
|
||||
"MA: 11 EV, net -29 (+33 -62 ?5)\n",
|
||||
"HI: 4 EV, net -28 (+34 -62 ?4)\n",
|
||||
"MD: 10 EV, net -28 (+34 -62 ?4)\n",
|
||||
"NY: 29 EV, net -23 (+36 -59 ?5)\n",
|
||||
"WA: 12 EV, net -23 (+36 -59 ?5)\n",
|
||||
"DC: 3 EV, net -65 (+16 -81 ?3)\n",
|
||||
"VT: 3 EV, net -35 (+31 -66 ?3)\n",
|
||||
"MA: 11 EV, net -31 (+33 -64 ?3)\n",
|
||||
"CA: 55 EV, net -30 (+33 -63 ?4)\n",
|
||||
"MD: 10 EV, net -30 (+33 -63 ?4)\n",
|
||||
"HI: 4 EV, net -29 (+34 -63 ?3)\n",
|
||||
"WA: 12 EV, net -26 (+35 -61 ?4)\n",
|
||||
"CT: 7 EV, net -24 (+36 -60 ?4)\n",
|
||||
"NY: 29 EV, net -24 (+36 -60 ?4)\n",
|
||||
"IL: 20 EV, net -23 (+37 -60 ?3)\n",
|
||||
"OR: 7 EV, net -22 (+37 -59 ?4)\n",
|
||||
"IL: 20 EV, net -21 (+37 -58 ?5)\n",
|
||||
"CT: 7 EV, net -20 (+38 -58 ?4)\n",
|
||||
"NM: 5 EV, net -19 (+38 -57 ?5)\n",
|
||||
"RI: 4 EV, net -19 (+38 -57 ?5)\n",
|
||||
"NJ: 14 EV, net -18 (+39 -57 ?4)\n",
|
||||
"CO: 9 EV, net -16 (+40 -56 ?4)\n",
|
||||
"DE: 3 EV, net -16 (+40 -56 ?4)\n",
|
||||
"MN: 10 EV, net -14 (+41 -55 ?4)\n",
|
||||
"MI: 16 EV, net -12 (+41 -53 ?6)\n",
|
||||
"WI: 10 EV, net -12 (+42 -54 ?4)\n",
|
||||
"NV: 6 EV, net -11 (+42 -53 ?5)\n",
|
||||
"IA: 6 EV, net -10 (+43 -53 ?4)\n",
|
||||
"NH: 4 EV, net -19 (+39 -58 ?3)\n",
|
||||
"NJ: 14 EV, net -19 (+39 -58 ?3)\n",
|
||||
"RI: 4 EV, net -19 (+39 -58 ?3)\n",
|
||||
"CO: 9 EV, net -18 (+39 -57 ?4)\n",
|
||||
"MN: 10 EV, net -18 (+39 -57 ?4)\n",
|
||||
"NM: 5 EV, net -18 (+39 -57 ?4)\n",
|
||||
"WI: 10 EV, net -16 (+40 -56 ?4)\n",
|
||||
"DE: 3 EV, net -15 (+41 -56 ?3)\n",
|
||||
"MI: 16 EV, net -15 (+40 -55 ?5)\n",
|
||||
"IA: 6 EV, net -14 (+41 -55 ?4)\n",
|
||||
"NV: 6 EV, net -13 (+42 -55 ?3)\n",
|
||||
"ME: 4 EV, net -11 (+43 -54 ?3)\n",
|
||||
"PA: 20 EV, net -10 (+43 -53 ?4)\n",
|
||||
"VA: 13 EV, net -10 (+43 -53 ?4)\n",
|
||||
"AZ: 11 EV, net -8 (+44 -52 ?4)\n",
|
||||
"NH: 4 EV, net -8 (+44 -52 ?4)\n",
|
||||
"ME: 4 EV, net -6 (+45 -51 ?4)\n",
|
||||
"PA: 20 EV, net -6 (+45 -51 ?4)\n",
|
||||
"FL: 29 EV, net -3 (+46 -49 ?5)\n",
|
||||
"GA: 16 EV, net -3 (+46 -49 ?5)\n",
|
||||
"NC: 15 EV, net -3 (+46 -49 ?5)\n",
|
||||
"OH: 18 EV, net -3 (+46 -49 ?5)\n",
|
||||
"VA: 13 EV, net -3 (+46 -49 ?5)\n",
|
||||
"KS: 6 EV, net +2 (+49 -47 ?4)\n",
|
||||
"MO: 10 EV, net +3 (+49 -46 ?5)\n",
|
||||
"UT: 6 EV, net +3 (+49 -46 ?5)\n",
|
||||
"AK: 3 EV, net +4 (+49 -45 ?6)\n",
|
||||
"MT: 3 EV, net +4 (+50 -46 ?4)\n",
|
||||
"TX: 38 EV, net +4 (+49 -45 ?6)\n",
|
||||
"IN: 11 EV, net +8 (+52 -44 ?4)\n",
|
||||
"NE: 5 EV, net +8 (+52 -44 ?4)\n",
|
||||
"ND: 3 EV, net +9 (+52 -43 ?5)\n",
|
||||
"SC: 9 EV, net +9 (+52 -43 ?5)\n",
|
||||
"AR: 6 EV, net +11 (+53 -42 ?5)\n",
|
||||
"ID: 4 EV, net +11 (+53 -42 ?5)\n",
|
||||
"LA: 8 EV, net +13 (+54 -41 ?5)\n",
|
||||
"OK: 7 EV, net +13 (+54 -41 ?5)\n",
|
||||
"KY: 8 EV, net +15 (+55 -40 ?5)\n",
|
||||
"SD: 3 EV, net +15 (+55 -40 ?5)\n",
|
||||
"TN: 11 EV, net +16 (+56 -40 ?4)\n",
|
||||
"MS: 6 EV, net +17 (+56 -39 ?5)\n",
|
||||
"AL: 9 EV, net +21 (+58 -37 ?5)\n",
|
||||
"WV: 5 EV, net +26 (+61 -35 ?4)\n",
|
||||
"WY: 3 EV, net +32 (+64 -32 ?4)\n"
|
||||
"OH: 18 EV, net -6 (+45 -51 ?4)\n",
|
||||
"UT: 6 EV, net -6 (+45 -51 ?4)\n",
|
||||
"FL: 29 EV, net -4 (+46 -50 ?4)\n",
|
||||
"NC: 15 EV, net -4 (+46 -50 ?4)\n",
|
||||
"GA: 16 EV, net -2 (+47 -49 ?4)\n",
|
||||
"MO: 10 EV, net -2 (+47 -49 ?4)\n",
|
||||
"NE: 5 EV, net +0 (+48 -48 ?4)\n",
|
||||
"TX: 38 EV, net +0 (+48 -48 ?4)\n",
|
||||
"AK: 3 EV, net +1 (+48 -47 ?5)\n",
|
||||
"KS: 6 EV, net +1 (+49 -48 ?3)\n",
|
||||
"MT: 3 EV, net +1 (+49 -48 ?3)\n",
|
||||
"IN: 11 EV, net +4 (+50 -46 ?4)\n",
|
||||
"ND: 3 EV, net +4 (+50 -46 ?4)\n",
|
||||
"SD: 3 EV, net +6 (+51 -45 ?4)\n",
|
||||
"SC: 9 EV, net +8 (+52 -44 ?4)\n",
|
||||
"AR: 6 EV, net +10 (+53 -43 ?4)\n",
|
||||
"OK: 7 EV, net +10 (+53 -43 ?4)\n",
|
||||
"TN: 11 EV, net +12 (+54 -42 ?4)\n",
|
||||
"MS: 6 EV, net +13 (+54 -41 ?5)\n",
|
||||
"KY: 8 EV, net +14 (+55 -41 ?4)\n",
|
||||
"ID: 4 EV, net +15 (+56 -41 ?3)\n",
|
||||
"LA: 8 EV, net +15 (+55 -40 ?5)\n",
|
||||
"AL: 9 EV, net +20 (+58 -38 ?4)\n",
|
||||
"WV: 5 EV, net +24 (+60 -36 ?4)\n",
|
||||
"WY: 3 EV, net +30 (+63 -33 ?4)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def undecided(row): return 100 - row.app - row.dis\n",
|
||||
"\n",
|
||||
"for r in sorted(rows, key=net):\n",
|
||||
" print('{}: {:2d} EV, net {:+3d} (+{} -{} ?{})'\n",
|
||||
" .format(r.state, votes[r.state], net(r), r.app, r.dis, undecided(r)))"
|
||||
"for s in sorted(states, key=net):\n",
|
||||
" print(f'{s.name}: {s.ev:2d} EV, net {net(s):+3d} (+{s.app} -{s.dis} ?{undecided(s)})')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here are all the states with more than 5% undecided. This is evidence that most people have made up their mind."
|
||||
"Note: Michigan, Wisconsin, and Pennsylvania (which Trump won in 2016) are all double-digit negative now. How is Trump doing in the states that border the proposed wall? Surprisingly poorly: -18 in New Mexico, -8 in Arizona, net zero in Texas, and (not surprisingly), -30 in California.\n",
|
||||
"\n",
|
||||
"Below are all the states with more than 5% undecided: the empty set. This is evidence that most people have made up their mind. (Although that could change as a clear challenger emerges from the field.)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -199,7 +204,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'AK': 6, 'MI': 6, 'TX': 6}"
|
||||
"set()"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
@ -208,8 +213,20 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"{r.state: undecided(r)\n",
|
||||
" for r in rows if undecided(r) > 5}"
|
||||
"{s.name for s in states if undecided(s) > 5}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here's a table tracking the numbers over time; I'll update each month, but if I forget, you can run this notebook yourself to see the latest numbers.\n",
|
||||
"\n",
|
||||
"|Date|Trump EV|Swing needed|5%+ undecided|\n",
|
||||
"|----|--------|------------|------|\n",
|
||||
"|Feb 19 | 126.5 | 11% | 0 |\n",
|
||||
"|Jan 19 | 164 | 7% | 3 |\n",
|
||||
"|Jan 17 | 448 | -10% | 51 |"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user