Add files via upload

This commit is contained in:
Peter Norvig 2019-02-15 12:45:43 -08:00 committed by GitHub
parent 48f57fbbd0
commit ed719ca866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,9 +4,37 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tracking Trump: Electoral Votes\n",
"<div align=right><i>Peter Norvig</i></div>\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 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."
"# Tracking Trump: Electoral Votes Edition\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 summarized the number of states in which Trump currently has a net positive or net negative approval rating (that is, his current percentage of approval minus his percentage of disapproval). 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*. So I computed that.\n",
"\n",
"# TL;DR for politics nerds\n",
"\n",
"As of Feb 6th 2019, Trump has a net positive approval in states that total **105** electoral votes, net negative for **390** electoral votes, and tied for **43** electoral votes.\n",
"\n",
"Michigan, Wisconsin, and Pennsylvania (which Trump won in 2016) are all double-digit negative now. In the key swing states of Ohio and Florida, he is -6 and -4, respectively. How is he doing in the states that border the proposed wall? Surprisingly poorly: -18 in New Mexico, -8 in Arizona, tied in Texas (which is supposed to be a Republican stronghold), and (not surprisingly), -30 in California.\n",
"\n",
"But of course these are just 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 else will be on the ballot and what their approval levels will be, there might be key economic or geopolitical events, and we don't know if there is systematic bias in the polling data.\n",
"\n",
"Nevertheless, the net approval is correlated to some degree with votes. An important concept is the **swing needed** to win, that is, if we make the (unwarranted) assumption that net approval translates directly to votes, how much of a positive swing in net approval percentage across the board in all states would Truump need to get to the necessary 270?. Today he would need an **11%** swing, whereas at his inauguration date he had good approval numbers, and would have needed a 10% negative swing to slip below 270.\n",
"\n",
"A large swing is more likely when there are many voters who are currently undecided. So I track the number of states in which more than 5% of the poll respondants were undecided. Today there are no such states, which is evidence that most people have made up their minds. (At the inauguration all 50 states plus DC had more than 5% undecided; many people had not made up their minds then.)\n",
"\n",
"\n",
"The table below summarizes the results over time. From now on I will update this when Morning Consult updates (monthly), but so far I only have results for Jan and Feb 2019, and for the Jan 2017 inauguration. \n",
"\n",
"\n",
"|Date |+ EV |- EV |= EV|Swing needed|5%+ undecided|\n",
"|---------|-----|-----|----|------------|-------------|\n",
"|Feb 2019 | 105 | 390 | 43 | 11% | 0 |\n",
"|Jan 2019 | 164 | 374 | 0 | 7% | 3 |\n",
"|Jan 2017 | 448 | 90 | 0 | -10%| 51|\n",
"\n",
"\n",
"\n",
"# The details for data science nerds"
]
},
{
@ -17,9 +45,9 @@
"source": [
"import urllib.request\n",
"import re\n",
"import collections \n",
"from collections import namedtuple, Counter\n",
"\n",
"State = collections.namedtuple('State', 'name ev app dis')\n",
"State = namedtuple('State', 'name ev app dis')\n",
"\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",
@ -42,14 +70,20 @@
"\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",
"def sign(x): \"Positive, negative, or tied.\"; return '+' if x > 0 else '-' if x < 0 else '='\n",
"\n",
"states = parse_page('https://morningconsult.com/tracking-trump/')\n",
"\n",
"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)"
" \"Total electoral votes that are net positive, negative, or tied, after applying swing.\"\n",
" return Counter(vote for state in states for vote in state.ev * sign(net(state) + swing))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can compute the number of electoral votes Trump has positive, negative, or tied net approval numbers:"
]
},
{
@ -60,7 +94,7 @@
{
"data": {
"text/plain": [
"126.5"
"Counter({'+': 105, '-': 390, '=': 43})"
]
},
"execution_count": 2,
@ -76,9 +110,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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 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."
"And repeat the computation for a range of across-the-board swings in Trump's favor:"
]
},
{
@ -89,18 +121,23 @@
{
"data": {
"text/plain": [
"{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}"
"{0: Counter({'+': 105, '-': 390, '=': 43}),\n",
" 1: Counter({'+': 148, '-': 390}),\n",
" 2: Counter({'+': 148, '-': 364, '=': 26}),\n",
" 3: Counter({'+': 174, '-': 364}),\n",
" 4: Counter({'+': 174, '-': 320, '=': 44}),\n",
" 5: Counter({'+': 218, '-': 320}),\n",
" 6: Counter({'+': 218, '-': 296, '=': 24}),\n",
" 7: Counter({'+': 242, '-': 296}),\n",
" 8: Counter({'+': 242, '=': 11, '-': 285}),\n",
" 9: Counter({'+': 253, '-': 285}),\n",
" 10: Counter({'+': 253, '-': 252, '=': 33}),\n",
" 11: Counter({'+': 286, '-': 248, '=': 4}),\n",
" 12: Counter({'+': 290, '-': 248}),\n",
" 13: Counter({'+': 290, '-': 242, '=': 6}),\n",
" 14: Counter({'+': 296, '-': 236, '=': 6}),\n",
" 15: Counter({'+': 302, '-': 217, '=': 19}),\n",
" 16: Counter({'+': 321, '-': 207, '=': 10})}"
]
},
"execution_count": 3,
@ -109,14 +146,14 @@
}
],
"source": [
"{swing: EV(swing) for swing in range(12)}"
"{swing: EV(swing) for swing in range(17)}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we show each state, sorted by net approval, with their number of votes, net approval, and the three approval percentages: positive, negative, undecided:"
"Below is each state, sorted by net approval, with their number of electoral votes, net approval, and the three approval percentages: positive, negative, undecided:"
]
},
{
@ -128,72 +165,72 @@
"name": "stdout",
"output_type": "stream",
"text": [
"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",
"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",
"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"
"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",
"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",
"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": [
"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)})')"
" print(f'{s.name}: {s.ev:2d} EV, net {net(s):+3d} (+:{s.app} -:{s.dis} ?:{undecided(s)})')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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.)"
"\n",
"Below are all the states with more than 5% undecided: the empty set. "
]
},
{
@ -213,20 +250,7 @@
}
],
"source": [
"{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 |"
"{s for s in states if undecided(s) > 5}"
]
}
],