First, lets assemble some data. This is the votes by party and constituency for the 2015 GE (from http://www.electoralcommission.org.uk/our-work/our-research/electoral-data)
import pandas as pd
GERes=pd.DataFrame.from_csv("2015-UK-general-election-data-results-WEB/RESULTS FOR ANALYSIS.csv")
This is the referendum result as a function of constituency (the actual wards were not constituencies, so this has been estimated: https://docs.google.com/spreadsheets/d/1wTK5dV2_YjCMsUYlwg0l48uWWf44sKgG8uFVMv5OWlA/)
RefRes=pd.DataFrame.from_csv("LeaveVoteByConstit.csv")
This only has UK (not NI) constituencies, but that's ok.
And the polling data for the major parties since the last GE (from http://ukpollingreport.co.uk/voting-intention-2)
import requests
url = "http://ukpollingreport.co.uk/voting-intention-2"
response = requests.get(url)
PollDat=pd.read_html(response.text,header=0)[0]
I'm using gaussian processes to estimate the future polling of the Tories compared to Labour. You'll need George 1.0-dev for this bit (https://github.com/dfm/george/tree/1.0-dev) Defining some functions for a gaussian process fit below:
import matplotlib
from datetime import datetime
dats=[datetime.strptime(PollDat['Survey End Date'][n],'%Y-%m-%d') for n in range(1,PollDat.shape[0])]
mpldats=matplotlib.dates.date2num(dats)
def nll(p,gp,y,prior=[]):
#inverted LnLikelihood function for GP training
gp.set_vector(p)
if prior!=[]:
#p=a,tau,wn
if np.any((prior[:,0] > p) + (p > prior[:,1])):
prob= -np.inf
gp.set_vector(p)
prob= gp.lnlikelihood(y, quiet=True)
return -prob if np.isfinite(prob) else 1e25
else:
ll = gp.lnlikelihood(y, quiet=True)
return -ll if np.isfinite(ll) else 1e25
def grad_nll(p,gp,y,prior=[]):
#Gradient of the objective function for TrainGP
gp.set_vector(p)
return -gp.grad_lnlikelihood(y, quiet=True)
from datetime import datetime as dt
import time
def toYearFraction(date):
def sinceEpoch(date): # returns seconds since epoch
return time.mktime(date.timetuple())
s = sinceEpoch
year = date.year
startOfThisYear = dt(year=year, month=1, day=1)
startOfNextYear = dt(year=year+1, month=1, day=1)
yearElapsed = s(date) - s(startOfThisYear)
yearDuration = s(startOfNextYear) - s(startOfThisYear)
fraction = yearElapsed/yearDuration
return date.year + fraction
def GPSmooth(lc,a=1.0,tau=0.1,plot=False,modeloutts=None,ExpSq=True,mean=True):
import george
import scipy.optimize as op
wn=np.median(np.diff(lc[:,1]))
if ExpSq:
kernel=a*(george.kernels.ExpSquaredKernel(tau))
else:
kernel = a * george.kernels.Matern32Kernel(tau)
if mean:
gp = george.GP(kernel, mean=np.nanmean(lc[:,1]), fit_mean=True, white_noise=np.log(wn**2), fit_white_noise=True)
else:
gp = george.GP(kernel, fit_mean=False, white_noise=np.log(wn**2), fit_white_noise=True)
gp.compute(lc[:,0],lc[:,2])
results = op.minimize(nll, gp.get_vector(),args=(gp,lc[:,1]), jac=grad_nll, method="L-BFGS-B")
gp.set_vector(results.x)
gp.compute(lc[:,0],lc[:,2])
#Predicting function for all times
ypredall,varpredall=gp.predict(lc[:,1], lc[:,0])
smoothlc=np.copy(lc)#lclam[:,1]/ypredall
smoothlc[:,1]/=ypredall
if plot:
p.figure(1)
p.errorbar(lc[:,0],lc[:,1],yerr=lc[:,2],ecolor='#CCCCCC',color='b',fmt='.')
fillx = np.arange(lc[0,0], lc[-1,0], np.min(np.diff(lc[:,0])))
mu, var = gp.predict(lc[:,1], fillx, return_var=True)
std = np.sqrt(var)
p.fill_between(fillx, mu+std, mu-std, color="g", alpha=0.5)
if modeloutts is not None:
return smoothlc,gp,gp.predict(lc[:,1],modeloutts,return_var=True)
else:
return smoothlc,gp
Plotting four different models: SqExp, SqExp+mean-function, Matern32, Matern32+mean-function:
import pylab as plt
import seaborn as sns
import numpy as np
%matplotlib inline
plt.figure(7,figsize=(16,6))
tyrs=(mpldats+np.random.normal(mpldats,np.tile(0.25,len(mpldats))))/365.0
tyrs=np.array([toYearFraction(d) for d in dats[:]])
cols=[0,2,4,3,1]
for n in range(5):
col=PollDat.columns.values[2+n]
pollc=np.column_stack((tyrs,np.random.normal(PollDat[col],np.tile(2.0,len(PollDat[col])))[1:],np.sqrt(PollDat[col])[1:]))
pollc=pollc[pollc[:,0].argsort()]
pollc=pollc[np.sum(pollc,axis=1)/np.sum(pollc,axis=1)==1.0]
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2010,2017.65,0.1))
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2010,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2010,2017.65,0.1),predict[0],'-',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2010,2017.65,0.1),mean=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2010,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2010,2017.65,0.1),predict[0],'--',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2010,2017.65,0.1),ExpSq=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2010,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2010,2017.65,0.1),predict[0],'-.',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2010,2017.65,0.1),ExpSq=False,mean=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2010,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2010,2017.65,0.1),predict[0],':',color=sns.color_palette()[cols[n]],alpha=0.8)
plt.ylim(0,50)
plt.plot([2017.433,2017.433],[0,50],'--k')
plt.savefig("GE2017_polling.png")
Now just plotting it from the 2015GE...
plt.figure(7,figsize=(16,6))
tyrs=(mpldats+np.random.normal(mpldats,np.tile(0.25,len(mpldats))))/365.0
tyrs=np.array([toYearFraction(d) for d in dats[:]])
cols=[0,2,4,3,1]
for n in range(5):
col=PollDat.columns.values[2+n]
pollc=np.column_stack((tyrs,np.random.normal(PollDat[col],np.tile(2.0,len(PollDat[col])))[1:],np.sqrt(PollDat[col])[1:]))
pollc=pollc[pollc[:,0].argsort()]
pollc=pollc[pollc[:,0]>2015.35]
pollc=pollc[pollc[:,0].argsort()]
pollc=pollc[np.sum(pollc,axis=1)/np.sum(pollc,axis=1)==1.0]
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2015,2017.65,0.1))
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2015,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2015,2017.65,0.1),predict[0],'-',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2015,2017.65,0.1),mean=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2015,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2015,2017.65,0.1),predict[0],'--',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2015,2017.65,0.1),ExpSq=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2015,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2015,2017.65,0.1),predict[0],'-.',color=sns.color_palette()[cols[n]],alpha=0.8)
_,gp,predict=GPSmooth(pollc,modeloutts=np.arange(2015,2017.65,0.1),ExpSq=False,mean=False)
plt.plot(pollc[:,0],pollc[:,1],'.',color=sns.color_palette()[cols[n]],alpha=0.5)
plt.fill_between(np.arange(2015,2017.65,0.1), predict[0] - np.sqrt(predict[1]), predict[0] + np.sqrt(predict[1]),color=sns.color_palette()[cols[n]], alpha=0.25)
plt.plot(np.arange(2015,2017.65,0.1),predict[0],':',color=sns.color_palette()[cols[n]],alpha=0.8)
plt.ylim(0,50)
plt.plot([2017.433,2017.433],[0,50],'--k')
plt.savefig("GE2017_polling_2016on.png")
Now we probably have to make the constituencies match between GERes and RefRes...
Right. My model is going to be based on the 2015 election with some tweaks:
def RefShift(R16):
#Given the referendum result in a consteuncy, what is the likely vote shift?
# Using a completely untested number - every 5% above 50/50 (for remain) produces a 1.5% shift to LDs
# while every 5% below 50/50 gives a 0.75% shift to Labout and a 1% shift to the Tories.
norm5=(R16['Figure to use']-0.5)/0.05
return {"lab":0.0075*norm5,"con":0.01*norm5,"ld":-0.015*norm5}
Extrapolting the Exponential Squared /mean function kernel to June 8th. Using the ratio of 2015.5 with 2017.43 as a direct factor on the increase in the vote share.
This makes the assumption that the pollsters fixed their polls after the last GE (which was obviously WAY bluer than expected), hence the post-GE polling was a better representation of the at-GE proportions.
If I use the pre-GE polling as the baseline, the Tories are going to get an even bigger boost.
extrapdic={}
for n in range(5):
col=PollDat.columns.values[2+n]
extraplc=np.column_stack((tyrs,np.random.normal(PollDat[col],np.tile(2.0,len(PollDat[col])))[1:],np.sqrt(PollDat[col])[1:]))
extraplc=extraplc[extraplc[:,0].argsort()]
extraplc=extraplc[np.sum(extraplc,axis=1)/np.sum(extraplc,axis=1)==1.0]
_,extrapgp,extrappredict=GPSmooth(extraplc,modeloutts=[2015.5,2017.43])
extrapdic[col.split(' ')[0]]=extrappredict[0][1]/extrappredict[0][0]
extrapdic[col.split(' ')[0]+"_err"]=np.sqrt((np.sqrt(extrappredict[1][1])/extrappredict[0][1])**2+(np.sqrt(extrappredict[1][0])/extrappredict[0][0])**2)
# That gives the relative vote share shares as shown below:
extrapdic
{u'CON': 1.0949513929353956, u'CON_err': 0.058751698716420026, u'Grn': 0.98392937989797158, u'Grn_err': 0.21404835545919165, u'LAB': 0.79804564660579858, u'LAB_err': 0.063995523397283441, u'LD': 1.5210356442409263, u'LD_err': 0.090672359449200976, u'UKIP': 0.88632256484636174, u'UKIP_err': 0.19563744627392765}
Running the numbers on a per-constituency basis across the UK:
RefRes.sort_index(inplace=True)
print "this"
this
#Finding where the NI constituencies are in the index
NIconsts=np.arange(1,650)[~np.in1d(np.arange(1,650),RefRes.index.values)]
def RunElection():
#Function with which to run an election
scores=np.zeros((650,6))
#Correlated UK-wide vote changes up to 3%
corrinc=np.random.normal(np.tile(0,6),np.tile(3,6))
#Looping through UK constituencies
for row in RefRes.iterrows():
G15=GERes.loc[row[0]]
R16=row[1]
refshift=RefShift(R16) #Adding shift from LD->Con in leave areas and vice versa in remain areas
#Finding votes from major parties:
votes=np.zeros(6)
tot=int(G15[' Total number of valid votes counted '].replace(',',''))
#Labour
if 'Lab Co-op' in G15.index:#Some regions have a "Lab Co-op" coalition. Adding these to the Lab score
G15['Lab']=np.nanmax([G15['Lab Co-op'],G15['Lab']])
if 'Lab' in G15.index and not pd.isnull(G15.Lab).any():
votes[0]=G15.Lab*np.random.normal(extrapdic['LAB'],extrapdic['LAB_err'])+tot*np.random.normal(refshift['lab'],abs(refshift['lab']/2.0))
votes[0]+=np.random.normal(0.0,3.0)#Adding extra 3% of local uncertainty for all parties
votes[0]+=corrinc[0]
votes[0]=abs(votes[0])
#Tories
if 'C' in G15.index and not pd.isnull(G15.C).any():
votes[1]=G15.C*np.random.normal(extrapdic['CON'],extrapdic['CON_err'])+tot*np.random.normal(refshift['con'],abs(refshift['con']/2.0))
votes[1]+=np.random.normal(0.0,3.0)#Adding extra 3% of local uncertainty for all parties
votes[1]+=corrinc[1]
votes[1]=abs(votes[1])
#Lib Dems
if 'LD' in G15.index and not pd.isnull(G15.LD).any():
votes[2]=G15.LD*np.random.normal(extrapdic['LD'],extrapdic['LD_err'])+tot*np.random.normal(refshift['ld'],abs(refshift['ld']/2.0))
votes[2]+=np.random.normal(0.0,3.0)#Adding extra 3% of local uncertainty for all parties
votes[2]+=corrinc[2]
votes[2]=abs(votes[2])
#SNP
if 'SNP' in G15.index and not pd.isnull(G15.SNP).any():
votes[3]=G15.SNP*np.random.normal(1.0,0.05)
votes[3]+=np.random.normal(0.0,3.0)#Adding extra 3% of local uncertainty for all parties
votes[3]+=corrinc[3]
votes[3]=abs(votes[3])
#UKIP
if 'UKIP' in G15.index and not pd.isnull(G15.UKIP).any():
votes[4]=G15.UKIP*np.random.normal(extrapdic['UKIP'],extrapdic['UKIP_err'])
votes[4]+=np.random.normal(0.0,3.0)#Adding extra 3% of local uncertainty for all parties
votes[4]+=corrinc[4]
votes[4]=abs(votes[4])
#Grn=G15.LD*np.random.normal(extrapdic['Grn'],extrapdic['Grn_err'])
oths=G15.index.values[~np.in1d(G15.index.values,np.array(['Lab','Lab Co-op','C','LD','SNP','UKIP']))][8:-2]
for o in oths:
count=G15[o]
if ~(pd.isnull(count))*~(np.isnan(count))*(count>votes[5]):
votes[5]=count
votes[5]+=np.random.normal(0.0,3.0)#Adding extra 3% of uncertainty for all parties
votes[5]+=corrinc[5]
votes[5]=abs(votes[5])
scores[row[0]-1,:]=votes
#Doing NI constituencies:
for n in NIconsts:
votes=np.zeros(6)
G15=GERes.loc[n]
votes[5]=np.max(G15.iloc[8:-2])
scores[n-1,:]=votes
votes[5]+=np.random.normal(0.0,5.0)
#Finding total winners:
names=["Lab","Con","LD","SNP","UKIP","Other"]
winners=np.argmax(scores,axis=1)
hist=[np.sum(winners==i) for i in range(6)]
return hist, scores
Doing this on 100 elections:
hists=np.zeros((100,6))
contscores=np.zeros((100,650,6))
for n in range(100):
h,s=RunElection()
hists[n,:]=h
contscores[n,:,:]=s
/Users/hughosborn/anaconda2/lib/python2.7/site-packages/ipykernel/__main__.py:19: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
names=["Labour","Tories","Liberal Democrats","SNP","UKIP","Others"]
print "Likely vote shares of the major parties (and 1-sigma ranges) are: "
[names[h]+str(np.percentile(hists[:,h],[16,50,84]))+"\n" for h in range(6)]
Likely vote shares of the major parties (and 1-sigma ranges) are:
['Labour[ 168. 171. 174.]\n', 'Tories[ 367. 369.5 374. ]\n', 'Liberal Democrats[ 34. 35. 37.]\n', 'SNP[ 48. 49. 50.]\n', 'UKIP[ 0. 1. 1.]\n', 'Others[ 24. 24. 24.]\n']
Plotting these:
plt.figure(19,figsize=(5,10))
cols=[2,0,4,5,3,1]
names=["Labour","Tories","Liberal Democrats","SNP","UKIP","Others"]
for h in range(6):
if h==5:
plt.text(0.35,5+np.median(hists[:,h]),names[h]+": "+str(np.median(hists[:,h])))
sns.distplot(hists[:,h],vertical=True,color=sns.color_palette()[cols[h]])
else:
plt.text(0.15,5+np.median(hists[:,h]),names[h]+": "+str(np.median(hists[:,h])))
sns.distplot(np.random.normal(hists[:,h],np.tile(2,len(hists[:,h]))),vertical=True,color=sns.color_palette()[cols[h]])
#plt.xscale('log')
plt.ylim(0,400)
liblabsnp=hists[:,0]+hists[:,2]+hists[:,3]
sns.distplot(liblabsnp,vertical=True,color='#AAAAAA')
plt.text(0.15,5+np.median(liblabsnp),"Lab/Lib/SNP Coalition: "+str(np.median(liblabsnp)),color='#999999')
plt.xlim(0,0.5)
plt.title("GE 2017 Model")
plt.xlabel("Probability")
plt.ylabel("Number of seats")
plt.savefig("GEmodelOutput")
medcont=np.median(contscores,axis=0)
GERes.loc[np.where(medcont[:,5]>np.nanmax(medcont[:,:5],axis=1))[0]+1,['Constituency Name','C','Lab','Lab Co-op','LD','PC','SNP','DUP']]
Constituency Name | C | Lab | Lab Co-op | LD | PC | SNP | DUP | |
---|---|---|---|---|---|---|---|---|
Press Association ID Number | ||||||||
13.0 | Antrim East | 549.0 | NaN | NaN | NaN | NaN | NaN | 12103.0 |
14.0 | Antrim North | 368.0 | NaN | NaN | NaN | NaN | NaN | 18107.0 |
15.0 | Antrim South | 415.0 | NaN | NaN | NaN | NaN | NaN | 10993.0 |
16.0 | Arfon | 3521.0 | 8122.0 | NaN | 718.0 | 11790.0 | NaN | NaN |
45.0 | Belfast East | 1121.0 | NaN | NaN | NaN | NaN | NaN | 19575.0 |
46.0 | Belfast North | NaN | NaN | NaN | NaN | NaN | NaN | 19096.0 |
47.0 | Belfast South | 582.0 | NaN | NaN | NaN | NaN | NaN | 8654.0 |
48.0 | Belfast West | 34.0 | NaN | NaN | NaN | NaN | NaN | 2773.0 |
98.0 | Brighton Pavilion | 12448.0 | 14904.0 | NaN | 1525.0 | NaN | NaN | NaN |
108.0 | Buckingham | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
131.0 | Carmarthen East & Dinefwr | 8336.0 | 9541.0 | NaN | 928.0 | 15140.0 | NaN | NaN |
202.0 | Down North | 1593.0 | NaN | NaN | NaN | NaN | NaN | 8487.0 |
203.0 | Down South | 318.0 | NaN | NaN | NaN | NaN | NaN | 3486.0 |
217.0 | Dwyfor Meirionnydd | 6550.0 | 3904.0 | NaN | 1153.0 | 11811.0 | NaN | NaN |
249.0 | Fermanagh & South Tyrone | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
255.0 | Foyle | 132.0 | NaN | NaN | NaN | NaN | NaN | 4573.0 |
347.0 | Lagan Valley | 654.0 | NaN | NaN | NaN | NaN | NaN | 19055.0 |
376.0 | Londonderry East | 422.0 | NaN | NaN | NaN | NaN | NaN | 14663.0 |
419.0 | Newry & Armagh | 210.0 | NaN | NaN | NaN | NaN | NaN | NaN |
542.0 | Strangford | 2167.0 | NaN | NaN | NaN | NaN | NaN | 15053.0 |
584.0 | Tyrone West | 169.0 | NaN | NaN | NaN | NaN | NaN | 6747.0 |
585.0 | Ulster Mid | 120.0 | NaN | NaN | NaN | NaN | NaN | 5465.0 |
586.0 | Upper Bann | 201.0 | NaN | NaN | NaN | NaN | NaN | 15430.0 |
647.0 | Ynys Mon | 7393.0 | 10871.0 | NaN | 751.0 | 10642.0 | NaN | NaN |
That all looks ok.
Lets have a look at the tightest likely election results:
for m in range(650):
if (np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2])<1000:
print GERes.loc[m+1,'Constituency Name']+" \t \t - "+names[np.argmax(medcont[m])]+" to win by "+str(int(np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2]))+" votes"
#GERes.loc[np.array(tights)+1,['Constituency Name','C','Lab','Lab Co-op','LD','PC','SNP','DUP']]
Argyll & Bute - Liberal Democrats to win by 578 votes Batley & Spen - Labour to win by 110 votes Berwickshire, Roxburgh & Selkirk - Tories to win by 587 votes Birmingham Erdington - Labour to win by 928 votes Birmingham Yardley - Liberal Democrats to win by 428 votes Colchester - Tories to win by 684 votes Coventry North West - Tories to win by 755 votes Dudley North - Tories to win by 538 votes Ellesmere Port & Neston - Labour to win by 63 votes Exeter - Labour to win by 624 votes Great Grimsby - Labour to win by 780 votes Hammersmith - Labour to win by 184 votes Hyndburn - Tories to win by 953 votes Inverness, Nairn, Badenoch & Strathspey - Liberal Democrats to win by 216 votes Luton South - Labour to win by 783 votes Mansfield - Tories to win by 38 votes Newport East - Labour to win by 881 votes Newport West - Tories to win by 896 votes Oxford West & Abingdon - Tories to win by 318 votes Stoke-On-Trent North - Labour to win by 215 votes Torbay - Liberal Democrats to win by 17 votes Walsall South - Labour to win by 271 votes Wolverhampton North East - Labour to win by 972 votes Workington - Labour to win by 61 votes Worsley & Eccles South - Labour to win by 729 votes Yeovil - Tories to win by 558 votes
And some slightly less close (1000-2000)
for m in range(650):
if 1000<(np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2])<2000:
print GERes.loc[m+1,'Constituency Name']+" \t \t - "+names[np.argmax(medcont[m])]+" to win by "+str(int(np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2]))+" votes"
Alyn & Deeside - Tories to win by 1480 votes Berwick-Upon-Tweed - Tories to win by 1806 votes Bishop Auckland - Tories to win by 1150 votes Blackpool South - Tories to win by 1561 votes Bolton North East - Tories to win by 1064 votes Brecon & Radnorshire - Tories to win by 1311 votes Bristol East - Tories to win by 1007 votes Bristol South - Labour to win by 1867 votes Bury South - Tories to win by 1355 votes Cheadle - Liberal Democrats to win by 1702 votes Clacton - Tories to win by 1672 votes Clwyd South - Tories to win by 1541 votes Coventry South - Tories to win by 1847 votes Darlington - Tories to win by 1892 votes Delyn - Tories to win by 1440 votes Hartlepool - Labour to win by 1856 votes Leeds North East - Labour to win by 1341 votes Manchester Withington - Liberal Democrats to win by 1889 votes Nottingham South - Labour to win by 1906 votes Oldham East & Saddleworth - Labour to win by 1310 votes Penistone & Stocksbridge - Labour to win by 1322 votes Portsmouth South - Tories to win by 1922 votes Scunthorpe - Tories to win by 1722 votes Sedgefield - Labour to win by 1658 votes Slough - Labour to win by 1188 votes Southampton Test - Tories to win by 1147 votes Stalybridge & Hyde - Labour to win by 1616 votes Stoke-On-Trent Central - Labour to win by 1833 votes Tynemouth - Labour to win by 1449 votes Wells - Tories to win by 1618 votes Wirral South - Tories to win by 1042 votes Wrexham - Tories to win by 1854 votes Ynys Mon - Others to win by 1911 votes York Central - Labour to win by 1486 votes
winnerstable=[]
for m in range(650):
winnerstable+=[[GERes.loc[m+1,'Constituency Name']+"\t"+names[np.argmax(medcont[m])]+" win by "+str(int(np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2]))+" votes",int(np.sort(medcont[m])[-1]-np.sort(medcont[m])[-2])]]
winnerstable=np.array(winnerstable)
winnerstable=winnerstable[winnerstable[:,1].astype(int).argsort()]
fout=open("ConstWinners2.txt","w")
fout.write(winnerstable[0,0]+"\n")
fout.close()
for line in winnerstable[1:,0]:
fout=open("ConstWinners2.txt","a")
fout.write(line+"\n")
fout.close()
winnerstablefile=[]
fout=open("ConstWinners2.txt","r")
for line in fout:
winnerstablefile+=[line]
fout.close()