#!/usr/bin/env python # coding: utf-8 # # Democratic Prospects for the 2018 Midterm # # Below, I look at the prospects for each party in the 2018 election. Overall, the House actually looks pretty good for Democrats, with many Republicans up for re-election in districts that Clinton won. The Senate is a different story, with many Democrats running in states that Trump won by a considerable margin. But who knows what will happen if the election becomes a referendum on Trump. # # The data for this post come from [The DailyKos](https://docs.google.com/spreadsheets/d/1oRl7vxEJUUDWJCyrjo62cELJD2ONIVl-D9TSUKiK9jk/edit#gid=2132957126). If you're viewing this notebook on Github, view it in NBViewer [here](http://nbviewer.jupyter.org/gist/psthomas/ed54ed0586f224544c5ad83763cb7267) instead to see the interactive plots and tables. I don't have an in-depth understanding of politics, so it's possible I'm missing some good indicators for midterm success. Let me know if you have any ideas for additions. # In[2]: get_ipython().run_line_magic('matplotlib', 'inline') import pandas as pd import numpy as np import json from IPython.display import HTML # # House Seats at Risk # # The plot below shows the 2016 Deocratic presidential lead on the x-axis, and the 2016 Democratic House lead on the y-axis. Any point near the x-axis was a close race, with those just above the axis won by Democrats and those just below won by Republicans. Points in the top left quadrant are seats that are especially at risk for Democrats because Trump won these districts. # # In the bottom right are districts that were won by Clinton in the presidential race, yet still held by House Republicans. There seem to be more Republican House seats at risk in 2018, which could especially be dangerous if the election becomes a referendum on Trump. # In[3]: house_df = pd.read_csv('house_data.csv', sep=',', encoding='utf-8') house_df['demlead_house2016'] = house_df['housedem_2016'] - house_df['houserep_2016'] house_df['demlead_pres2016'] = ((house_df['clinton_2016'] - house_df['trump_2016'])/house_df['total_2016'])*100 house_df['sum'] = house_df['demlead_house2016'] + house_df['demlead_pres2016'] house_df = house_df.round(2) # In[48]: tooltip = ''' 'District: '+ d[keys.code] + '
Name: ' + d[keys.first] + ' ' + d[keys.last] + '
President: ' + d[keys.demlead_pres2016] + "%" + '
House: ' + d[keys.demlead_house2016] +"%" ''' settings = {"x_label": "Democratic Lead, President 2016 (%)", "y_label": "Democratic Lead, House 2016 (%)", "x": 'demlead_pres2016' , "y": 'demlead_house2016', "tooltip": tooltip} interactive_scatter(house_df, settings=settings) # ## Close House Seats for Democrats # # The first two numeric columns below show the Democrat's lead in the House race and Presidential race for each district. When the sum column of these two values is negative it indicates a district that had a larger margin for Trump than for the Democrat that was elected. # # Interestingly, Minnesota had a number of close seats, including the 7th district with a 35 point spread between Collin Peterson and Trump. Someone needs to ask these guys in Minnesota how they appealed to Trump voters ([Peterson](https://en.wikipedia.org/wiki/Collin_Peterson) is a Blue Dog Democrat, and both [Nolan](https://en.wikipedia.org/wiki/Rick_Nolan) and [Walz](https://en.wikipedia.org/wiki/Rick_Nolan) are moderate Democrats). I think Walz provides an especially good template for moderate Democrats to follow in contentious districts. # # I also include the [Partisan Voter Index](https://en.wikipedia.org/wiki/Cook_Partisan_Voting_Index) (pvi_2016), which combines the past two elections into an index of how the district votes relative to the country. Currently, the table is sorted by the 'sum' column, but Bokeh outputs interactive tables so feel free to sort the columns by any of the indicators. # In[58]: temp_df = house_df[(house_df['party'] == 'Democratic')].copy() temp_df.sort_values(by='sum', ascending=True, inplace=True) temp_df = temp_df[['district', 'party', 'first', 'last', 'demlead_house2016', 'demlead_pres2016', 'sum', 'pvi_2016']] interactive_table(temp_df, width=800, height=500) # ## Close House Seats for Republicans # # I do the same thing below for House Republicans, but this time a positive sum indicates a district that had a larger margin for Clinton than for the Republican elected. I also include a column showing whether or not the [Democratic Congressional Campaign Committee](http://dccc.org/) has indicated that this member of the House is a target for the 2018 election. This shows that the 'sum' column is a fairly good indicator for a midterm target. # # In[59]: #http://action.dccc.org/pdf/dccc-on-offense.pdf #59 total targets for 2018 elections dccc_target = ["AL-02", "AR-02", "AZ-02", "CA-10", "CA-21", "CA-25", "CA-39", "CA-45", "CA-48", "CA-49", "CO-03", "CO-06", "FL-18", "FL-25", "FL-26", "FL-27", "GA-06", "IA-01", "IA-03", "IL-06", "IL-13", "IL-14", "KS-02", "KS-03", "KY-06", "ME-02", "MI-07", "MI-08", "MI-11", "MN-02", "MN-03", "NC-08", "NC-09", "NC-13", "NE-02", "NJ-02", "NJ-03", "NJ-07", "NJ-11", "NY-01", "NY-11", "NY-19", "NY-22", "NY-24", "NY-27", "OH-01", "OH-07", "PA-06", "PA-07", "PA-08", "PA-16", "TX-07", "TX-23", "TX-32", "VA-02", "VA-10", "WA-03", "WA-08", "WV-02"] #Build dataframe temp_df = house_df[(house_df['party'] == 'Republican')].copy() #.loc[:,:] temp_df['dccc_target'] = temp_df['code'].isin(dccc_target) temp_df.sort_values(by='sum', ascending=False, inplace=True) temp_df = temp_df[['district', 'party', 'first', 'last', 'demlead_house2016', 'demlead_pres2016', 'sum', 'dccc_target', 'pvi_2016']] #Build Table interactive_table(temp_df, width=800, height=500) # # Senate Seats at Risk # # Next, I look at the Senate seats at risk for each party. # # Below is a plot similar plot to the one above, except it only shows the Senators up for re-election in 2018. This plot is also slightly different, as I am using the Democratic Lead in the 2012 Senate election for each candidate on the y-axis. This might not be the best indicator of the state's support for the candidate as a lot has changed since 2012, but it's the best one I can think of. # # This plot makes it clear that Senate Democrats have a tough 2018 coming up, as 9 senators are up for re-election in states that Trump won. # In[8]: # Note, demlead_lastsenate refers to the 2010 election for # senators elected in 2016 senate_df = pd.read_csv('senate_data.csv', sep=',', encoding='utf-8') senate_df['demlead_pres2016'] = ((senate_df['clinton_2016'] - senate_df['trump_2016'])/senate_df['prestotal_2016'])*100 senate_df['demlead_lastsenate'] = senate_df['lastelect_dem'] - senate_df['lastelect_rep'] senate_df['sum'] = senate_df['demlead_lastsenate'] + senate_df['demlead_pres2016'] senate_df = senate_df.round(2) # In[49]: plot_df = senate_df[senate_df['class'] == 2012] # p.circle(x='demlead_pres2016', y='demlead_lastsenate', size=8, # color='red', source=source, fill_alpha=0.4) tooltip = ''' 'Class: '+ d[keys.class] + '
State: '+ d[keys.state] + '
Name: ' + d[keys.first] + ' ' + d[keys.last] + '
President: ' + d[keys.demlead_pres2016] + "%" + '
Last Senate: ' + d[keys.demlead_lastsenate] +"%" ''' settings = {"x_label": "Democratic Lead, President 2016 (%)", "y_label": "Democratic Lead, Last Senate (%)", "x": 'demlead_pres2016' , "y": 'demlead_lastsenate', "tooltip": tooltip} interactive_scatter(plot_df, settings=settings) # ## Close Senate Seats for Democrats # # The Senate is a little different because voting happens every six years. I order this by the sum of the Democratic lead in the 2016 presidential election and the Senator's lead in their last election. A district could shift quite a bit during a Senator's term, so this sum should be taken with a grain of salt. # # These data make it clear that Senate Democrats have a very difficult election ahead in 2018. # In[60]: #Build Dataframe temp_df = senate_df[(senate_df['party'] == 'Democratic') & (senate_df['class'] == 2012) ].sort_values(by='sum', ascending=True) temp_df = temp_df[['state', 'class', 'party', 'first', 'last', 'demlead_lastsenate', 'demlead_pres2016', 'sum', 'pvi_2016']] #Build Table interactive_table(temp_df, width=800, height=500) # ## Close Senate Seat(s) for Republicans # # Relative to the Democrats, Senate Republicans have an easier 2018. Dean Heller's seat in Nevada is the only one in a state that was won by Clinton in 2016, and many of the other seats look pretty safe. # In[61]: temp_df = senate_df[(senate_df['party'] == 'Republican') & (senate_df['class'] == 2012) ].sort_values(by='sum', ascending=False) #by= 'demlead_pres2016' temp_df = temp_df[['state', 'class', 'party', 'first', 'last', 'demlead_lastsenate', 'demlead_pres2016', 'sum','pvi_2016']] #Build Table interactive_table(temp_df, width=800, height=275) # # Code for the Visualizations # # Below are the two functions for the interactive scatter and tables. I couldn't find a Python library with interactive features that I liked, so I decided to build the basics of my own. The scatter is built with d3.js, and then embedded in an iframe along with the data. I use plain javascript to add interactivity to the tables and then embed it in an iframe as well. # # I find this works pretty well, as I have complete control over the visualization, and can copy and paste the iframe wherever it's needed, data included. # In[47]: def interactive_scatter(df, settings): srcdoc = r''' Zoom + Pan
''' width=960 height=600 srcdoc = srcdoc.replace('||datainsert||', df.to_json(orient="values")) key_list = list(df) key_dict = {i: key_list.index(i) for i in key_list} srcdoc = srcdoc.replace('||keys||', json.dumps(key_dict) ) for s in settings.keys(): srcdoc = srcdoc.replace('||{0}||'.format(s), str(settings[s])) srcdoc = srcdoc.replace('"', '"') embed = HTML(''.format(srcdoc, width, height)) return embed # In[57]: def interactive_table(df, width, height): srcdoc = r'''
''' srcdoc = srcdoc.replace('||headinginsert||', json.dumps(list(df))) srcdoc = srcdoc.replace('||datainsert||', df.to_json(orient="values")) srcdoc = srcdoc.replace('"', '"') html = ''' '''.format(srcdoc, width, height) #width: 100%; margin: 25px auto; embed = HTML(html) return embed