In [64]:
import numpy as np
import matplotlib.pyplot as pl
import openpyxl
import dateutil
import datetime
import pickle
import pystan
import seaborn as sns
import os
from hashlib import md5
import matplotlib
%matplotlib notebook

In [65]:
matplotlib.get_backend()
print(pystan.__version__)


2.9.0.0

Análisis de los sondeos electorales 2016

Vamos a plantearnos como ejercicio analizar los sondeos electorales. Para ello cogemos como datos los sondeos recopilados en la página web de la Wikipedia para las elecciones de 2015 y para las de 2016. Consideramos sólo los partidos PP, PSOE, IU, Podemos y Ciudadanos. El resto de partidos se consideran metidos dentro de un mismo saco, que vienen siendo alrededor del 15% en cada sondeo.

Para modelar la evolución temporal de los sondeos para cada partido, usamos un modelado con variables latentes. La idea es que los resultados de los sondeos son sólo una estimación ruidosa y sesgada de la intención de voto real de los votantes. Por tanto, considamos que $\theta_i(t_j)$ es la subyacente intención de voto para el partido $i$ en el tiempo $t_j$. Esta variable se observa de forma ruidosa para cada empresa $k$ que realiza el sondeo, y además incluye un cierto sesgo. Por tanto, nuestro modelo de observación es:

$$ y_{ik}(t_j) \sim N(\theta_i(t_j) + b_{ik}, \sigma_{k}(t_j)^2) $$

donde $b_{ik}$ es el bias que introduce la empresa de sondeos $k$ sobre el partido $i$ y $\sigma_{ik}^2$ es la varianza del sondeo realizado en el tiempo $t_j$ por la empresa $k$. Para los resultados de las elecciones, elegimos una varianza muy pequeña y no incluimos los sesgos, porque es el único momento en el que tenemos acceso directo a las intenciones subyacentes:

$$ y_{ik}(\mathrm{elecciones}) \sim N(\theta_i(t_j), \sigma_{k}(\mathrm{elecciones})^2). $$

Es obvio que la suma de porcentajes de cada partido debe sumar 1 en cada sondeo, por lo cual debemos asegurarnos de que

$$ \sum_i \theta_i(t_j) = 1. $$

Además, es lógico asumir que, dadas suficientes casas de sondeo, los biases sumen 0 para cada partido:

$$ \sum_k b_{ik} = 0. $$

Por último, asumimos un proceso autoregresivo Markoviano para modelar la variación temporal de la intención de voto real para cada partido. Es decir, en cada sondeo, la intención de voto estará dada por una variable Gaussiana con media igual a la intención de voto anterior, y una varianza que debemos estimar para cada partido:

$$ \theta_i(t_j) \sim N(\theta_i(t_{j-1}), \sigma_i^2) $$

Como es un parámetro de escala, definimos un prior poco informativo sobre $\sigma_i$:

$$ \sigma_i \sim IG(0.01,0.01). $$

Por último, debemos definir un prior para $\theta_i(t_0)$, para el cual escogemos una distribución de Dirichlet con $\alpha=1$:

$$ \theta_i(t_0) \sim \mathrm{Dir}(\alpha). $$

Modelo

Definimos algunas funciones útiles para el parsing de la base de datos.


In [66]:
def stan_cache(model_code, model_name=None, **kwargs):
    """Use just as you would `stan`"""
    code_hash = md5(model_code.encode('ascii')).hexdigest()
    if model_name is None:
        cache_fn = 'cached-model-{}.pkl'.format(code_hash)
    else:
        cache_fn = 'cached-{}-{}.pkl'.format(model_name, code_hash)
    try:
        sm = pickle.load(open(cache_fn, 'rb'))
    except:
        sm = pystan.StanModel(model_code=model_code)
        with open(cache_fn, 'wb') as f:
            pickle.dump(sm, f)
    else:
        print("Using cached StanModel")
    return sm.sampling(**kwargs)

def toenglish(s):
    spanish = ['ene', 'abr', 'ago', 'dic', 'de mayo de']
    english = ['jan', 'apr', 'aug', 'dec', 'may']
    for (j, month) in enumerate(spanish):
        s = s.replace(month, english[j])
    return s

def getPercentage(s):
    if (s[0] not in ['0','1','2','3','4','5','6','7','8','9']):
        return 0
    else:
        if (s.find('%') != -1):
            return float(s.split('%')[0].replace(',','.')) / 100.0
        else:
            return float(s.split('\n')[0].replace(',','.')) / 100.0

def getSigma(s):
    left = s.find('(')
    right = s.find(')')    
    if (s[left+1:right] in ['?', '-']):
        return 0.03
    else:
        return 1.0 / np.sqrt(float(s[left+1:right]))

def weeksDifference(d1, d2):
    monday1 = (d1 - datetime.timedelta(days=d1.weekday()))
    monday2 = (d2 - datetime.timedelta(days=d2.weekday()))

    return int((monday2 - monday1).days / 7)

Leemos el fichero de datos obtenido de la Wikipedia y hacemos el parsing. Escogemos sólo aquellos sondeos en los que hay información para todos los partidos. Podríamos también escoger aquellos parciales porque el modelado Bayesiano los podría aceptar.


In [89]:
wb = openpyxl.load_workbook("data/sondeos.xlsx")
ws = wb.active

empresas = ['GAD3', 'Encuestamos', 'GESOP', 'Metroscopia', 'Celeste-Tel','Demoscopia Servicios', 'Simple Lógica', 'CIS', 
            'TNS Demoscopia', 'Invymark', 'NC Report', 'El Espanol', 'DYM', 'Sondaxe', 'Sigma Dos', 'Resultados de las elecciones']

empresaSondeoAll = []
sondeosAll = []
dateAll = []
sigmaAll = []

wbnew = openpyxl.Workbook()

# grab the active worksheet
wsnew = wbnew.active
wsnew.append(['Encuestador','Fecha','Muestra','PP','PSOE','PODEMOS','Cs','IU'])

otrosPartidos = ['F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'R']

nSondeos = 302
loopSondeos = 0
for i in range(130):
    empresa = ws['A{0}'.format(i+2)].value
    for (loop, emp) in enumerate(empresas):
        if (empresa.find(emp) != -1):
            empresaSondeo = loop

    if (empresaSondeo == len(empresas)-1):
        sigma = 0.0001
    else:
        sigma = getSigma(empresa)
        
    PP = getPercentage(ws['C{0}'.format(i+2)].value)
    PSOE = getPercentage(ws['D{0}'.format(i+2)].value)
    IU = getPercentage(ws['E{0}'.format(i+2)].value)
    PODEMOS = getPercentage(ws['P{0}'.format(i+2)].value)
    CS = getPercentage(ws['Q{0}'.format(i+2)].value)

    total = PP + PSOE + IU + PODEMOS + CS
    otros = 1.0 - total    

    tmp = ws['B{0}'.format(i+2)].value
    if (isinstance(tmp, datetime.date)):
        date = tmp
    else:
        date = dateutil.parser.parse(toenglish(ws['B{0}'.format(i+2)].value.split('-')[-1].lower()))    

    tmp = date.year + (date.month-1.0) / 12.0

    sondeo = [PP, PSOE, IU+PODEMOS, CS, otros]
    
    if (0 not in sondeo):
        
        loopSondeos += 1

        sondeosAll.append(sondeo)
        sigmaAll.append(sigma)
    
        dateAll.append(date)
        empresaSondeoAll.append(empresaSondeo+1)

        print ("{0} - {1} {7} - s={8:4.2f} : PP={2:4.2f} - PSOE={3:4.2f} - UP={4:4.2f} - CS={5:4.2f} - Resto={6:4.2f}".format(i, 
            empresas[empresaSondeo], PP*100, PSOE*100, (IU+PODEMOS)*100, CS*100, otros*100, date, sigma*100))
        
        wsnew.append([empresas[empresaSondeo],'{0}/{1}/{2}'.format(date.day,date.month,date.year-2000),1.0/sigma**2,PP*100,PSOE*100,(IU+PODEMOS)*100,CS*100,0.0])       
        
wbnew.save('new.xlsx')
sondeosAll = np.array(sondeosAll)

nSondeos, nPartidos = sondeosAll.shape
nEmpresas = len(empresas)

print ("Número de sondeos incluidos : {0} de {1} posibles".format(loopSondeos,120))

# Compute week of every poll
weekAll = []
for i in range(nSondeos):
    weekAll.append(weeksDifference(dateAll[nSondeos-1], dateAll[i]) + 1)

nDates = max(weekAll)

# Reverse all lists
sondeosAll = sondeosAll[::-1]
empresaSondeoAll.reverse()
weekAll.reverse()
sigmaAll.reverse()

dictionary = {'NPartidos': nPartidos, 'NSondeos': nSondeos, 'NEmpresas': nEmpresas-1, 'NDates': nDates, 'empresa': empresaSondeoAll, 'sondeos': sondeosAll, 
'date': weekAll, 'sigmaSondeo': sigmaAll, 'alpha': np.ones(nPartidos)*1.0}


0 - El Espanol 2016-05-13 00:00:00 - s=3.16 : PP=30.80 - PSOE=23.70 - UP=23.70 - CS=11.00 - Resto=10.80
1 - GAD3 2016-04-29 00:00:00 - s=3.54 : PP=29.30 - PSOE=22.60 - UP=22.30 - CS=13.90 - Resto=11.90
2 - Metroscopia 2016-04-28 00:00:00 - s=2.89 : PP=29.00 - PSOE=20.30 - UP=24.70 - CS=16.00 - Resto=10.00
3 - Invymark 2016-04-29 00:00:00 - s=2.89 : PP=28.70 - PSOE=19.40 - UP=25.50 - CS=15.40 - Resto=11.00
4 - El Espanol 2016-04-23 00:00:00 - s=3.16 : PP=27.30 - PSOE=20.20 - UP=24.50 - CS=17.00 - Resto=11.00
5 - CIS 2016-04-10 00:00:00 - s=2.00 : PP=27.40 - PSOE=21.60 - UP=23.10 - CS=15.60 - Resto=12.30
6 - Invymark 2016-04-15 00:00:00 - s=3.00 : PP=28.60 - PSOE=20.40 - UP=24.60 - CS=15.80 - Resto=10.60
7 - NC Report 2016-04-14 00:00:00 - s=3.24 : PP=30.70 - PSOE=21.20 - UP=26.80 - CS=14.50 - Resto=6.80
8 - El Espanol 2016-04-02 00:00:00 - s=3.16 : PP=27.50 - PSOE=19.30 - UP=24.60 - CS=16.90 - Resto=11.70
9 - TNS Demoscopia 2016-04-14 00:00:00 - s=3.16 : PP=29.40 - PSOE=22.30 - UP=23.50 - CS=15.80 - Resto=9.00
10 - CIS 2016-01-11 00:00:00 - s=2.00 : PP=28.80 - PSOE=20.50 - UP=16.90 - CS=13.30 - Resto=20.50
11 - GAD3 2016-01-21 00:00:00 - s=3.53 : PP=30.10 - PSOE=21.30 - UP=22.90 - CS=13.40 - Resto=12.30
12 - GAD3 2016-01-20 00:00:00 - s=3.16 : PP=31.40 - PSOE=23.50 - UP=22.90 - CS=11.80 - Resto=10.40
13 - Metroscopia 2016-01-17 00:00:00 - s=4.47 : PP=29.00 - PSOE=21.10 - UP=25.70 - CS=16.60 - Resto=7.60
14 - Invymark 2016-01-11 00:00:00 - s=4.47 : PP=30.80 - PSOE=20.80 - UP=25.10 - CS=12.60 - Resto=10.70
15 - Resultados de las elecciones 2015-12-20 00:00:00 - s=0.01 : PP=28.72 - PSOE=22.01 - UP=24.33 - CS=13.93 - Resto=11.01
16 - GESOP 2015-12-19 00:00:00 - s=3.24 : PP=26.60 - PSOE=20.10 - UP=25.50 - CS=15.30 - Resto=12.50
17 - GESOP 2015-12-19 00:00:00 - s=3.54 : PP=26.60 - PSOE=20.80 - UP=24.50 - CS=15.50 - Resto=12.60
18 - GESOP 2015-12-18 00:00:00 - s=3.78 : PP=25.80 - PSOE=21.40 - UP=24.20 - CS=16.00 - Resto=12.60
19 - GESOP 2015-12-17 00:00:00 - s=3.78 : PP=26.20 - PSOE=21.00 - UP=24.10 - CS=15.90 - Resto=12.80
20 - GESOP 2015-12-16 00:00:00 - s=3.92 : PP=25.40 - PSOE=20.60 - UP=24.10 - CS=16.30 - Resto=13.60
21 - GESOP 2015-12-15 00:00:00 - s=3.78 : PP=25.40 - PSOE=20.90 - UP=23.80 - CS=17.20 - Resto=12.70
22 - GAD3 2015-12-14 00:00:00 - s=1.77 : PP=28.60 - PSOE=21.00 - UP=21.80 - CS=17.60 - Resto=11.00
23 - Encuestamos 2015-12-14 00:00:00 - s=2.24 : PP=26.90 - PSOE=22.80 - UP=22.60 - CS=17.00 - Resto=10.70
24 - Encuestamos 2015-12-14 00:00:00 - s=0.85 : PP=24.50 - PSOE=21.30 - UP=23.70 - CS=16.50 - Resto=14.00
25 - GESOP 2015-12-12 00:00:00 - s=3.54 : PP=25.20 - PSOE=20.80 - UP=18.40 - CS=18.00 - Resto=17.60
26 - GAD3 2015-12-11 00:00:00 - s=1.17 : PP=28.30 - PSOE=21.20 - UP=21.80 - CS=18.10 - Resto=10.60
27 - Metroscopia 2015-12-10 00:00:00 - s=1.89 : PP=25.30 - PSOE=21.00 - UP=24.10 - CS=18.20 - Resto=11.40
28 - DYM 2015-12-09 00:00:00 - s=3.14 : PP=26.70 - PSOE=17.00 - UP=25.40 - CS=23.20 - Resto=7.70
29 - Encuestamos 2015-12-07 00:00:00 - s=2.36 : PP=26.70 - PSOE=24.90 - UP=21.30 - CS=16.20 - Resto=10.90
30 - Celeste-Tel 2015-12-04 00:00:00 - s=2.89 : PP=28.20 - PSOE=23.10 - UP=20.00 - CS=18.80 - Resto=9.90
31 - Metroscopia 2015-11-29 00:00:00 - s=2.89 : PP=22.70 - PSOE=22.50 - UP=22.30 - CS=22.60 - Resto=9.90
32 - Celeste-Tel 2015-11-27 00:00:00 - s=2.89 : PP=28.10 - PSOE=23.90 - UP=18.90 - CS=18.60 - Resto=10.50
33 - Encuestamos 2015-11-24 00:00:00 - s=2.36 : PP=26.60 - PSOE=25.40 - UP=22.20 - CS=15.10 - Resto=10.70
34 - Demoscopia Servicios 2015-11-26 00:00:00 - s=2.58 : PP=28.30 - PSOE=21.20 - UP=19.60 - CS=19.90 - Resto=11.00
35 - Celeste-Tel 2015-11-23 00:00:00 - s=2.89 : PP=27.50 - PSOE=23.90 - UP=18.50 - CS=18.90 - Resto=11.20
36 - Simple Lógica 2015-11-17 00:00:00 - s=2.40 : PP=26.50 - PSOE=20.40 - UP=21.40 - CS=21.90 - Resto=9.80
37 - Encuestamos 2015-11-17 00:00:00 - s=2.36 : PP=26.20 - PSOE=25.70 - UP=22.20 - CS=15.00 - Resto=10.90
38 - CIS 2015-11-16 00:00:00 - s=0.76 : PP=28.60 - PSOE=20.80 - UP=19.30 - CS=19.00 - Resto=12.30
39 - GAD3 2015-11-20 00:00:00 - s=2.89 : PP=28.50 - PSOE=22.80 - UP=20.00 - CS=16.40 - Resto=12.30
40 - TNS Demoscopia 2015-11-09 00:00:00 - s=3.16 : PP=26.30 - PSOE=19.60 - UP=19.70 - CS=20.20 - Resto=14.20
41 - Demoscopia Servicios 2015-11-07 00:00:00 - s=2.36 : PP=29.00 - PSOE=22.30 - UP=18.70 - CS=18.10 - Resto=11.90
42 - GAD3 2015-11-04 00:00:00 - s=3.00 : PP=28.10 - PSOE=22.40 - UP=17.80 - CS=17.70 - Resto=14.00
43 - TNS Demoscopia 2015-11-02 00:00:00 - s=3.16 : PP=26.70 - PSOE=19.30 - UP=19.80 - CS=19.80 - Resto=14.40
44 - Metroscopia 2015-11-02 00:00:00 - s=2.67 : PP=23.50 - PSOE=21.00 - UP=23.30 - CS=22.50 - Resto=9.70
45 - DYM 2015-10-26 00:00:00 - s=3.16 : PP=27.00 - PSOE=18.20 - UP=19.90 - CS=20.30 - Resto=14.60
46 - TNS Demoscopia 2015-10-26 00:00:00 - s=3.16 : PP=26.00 - PSOE=20.50 - UP=14.60 - CS=19.20 - Resto=19.70
47 - Encuestamos 2015-10-24 00:00:00 - s=2.36 : PP=23.50 - PSOE=26.30 - UP=23.10 - CS=15.20 - Resto=11.90
48 - Invymark 2015-10-19 00:00:00 - s=2.89 : PP=28.60 - PSOE=23.90 - UP=16.50 - CS=18.30 - Resto=12.70
49 - GAD3 2015-10-19 00:00:00 - s=1.96 : PP=27.70 - PSOE=21.80 - UP=19.20 - CS=17.60 - Resto=13.70
50 - TNS Demoscopia 2015-10-12 00:00:00 - s=3.16 : PP=26.90 - PSOE=21.00 - UP=18.50 - CS=17.80 - Resto=15.80
51 - CIS 2015-10-12 00:00:00 - s=2.00 : PP=29.10 - PSOE=25.30 - UP=15.50 - CS=14.70 - Resto=15.40
52 - Invymark 2015-10-05 00:00:00 - s=2.89 : PP=27.60 - PSOE=24.60 - UP=18.10 - CS=17.20 - Resto=12.50
53 - Metroscopia 2015-10-01 00:00:00 - s=2.36 : PP=23.40 - PSOE=23.50 - UP=19.70 - CS=21.50 - Resto=11.90
54 - TNS Demoscopia 2015-10-05 00:00:00 - s=3.16 : PP=27.00 - PSOE=21.90 - UP=19.30 - CS=16.50 - Resto=15.30
55 - NC Report 2015-10-02 00:00:00 - s=3.33 : PP=33.80 - PSOE=24.80 - UP=15.40 - CS=11.70 - Resto=14.30
56 - Encuestamos 2015-09-23 00:00:00 - s=2.50 : PP=26.20 - PSOE=26.10 - UP=24.00 - CS=11.00 - Resto=12.70
57 - TNS Demoscopia 2015-09-14 00:00:00 - s=3.10 : PP=29.00 - PSOE=22.00 - UP=20.70 - CS=13.20 - Resto=15.10
58 - Metroscopia 2015-09-11 00:00:00 - s=2.36 : PP=23.40 - PSOE=24.60 - UP=23.60 - CS=16.10 - Resto=12.30
59 - Demoscopia Servicios 2015-09-11 00:00:00 - s=2.36 : PP=31.70 - PSOE=25.60 - UP=22.10 - CS=10.50 - Resto=10.10
60 - Demoscopia Servicios 2015-09-07 00:00:00 - s=3.02 : PP=31.60 - PSOE=27.10 - UP=16.60 - CS=9.90 - Resto=14.80
61 - TNS Demoscopia 2015-09-07 00:00:00 - s=3.02 : PP=28.60 - PSOE=22.50 - UP=19.90 - CS=12.70 - Resto=16.30
62 - TNS Demoscopia 2015-08-31 00:00:00 - s=2.99 : PP=28.50 - PSOE=22.80 - UP=20.40 - CS=12.30 - Resto=16.00
63 - NC Report 2015-08-29 00:00:00 - s=3.33 : PP=32.10 - PSOE=24.90 - UP=16.50 - CS=10.40 - Resto=16.10
64 - NC Report 2015-08-27 00:00:00 - s=3.16 : PP=28.30 - PSOE=25.90 - UP=18.00 - CS=11.30 - Resto=16.50
65 - TNS Demoscopia 2015-08-24 00:00:00 - s=3.00 : PP=27.30 - PSOE=22.60 - UP=20.80 - CS=13.20 - Resto=16.10
66 - TNS Demoscopia 2015-08-17 00:00:00 - s=3.03 : PP=27.00 - PSOE=22.90 - UP=21.00 - CS=12.90 - Resto=16.20
67 - Simple Lógica 2015-08-11 00:00:00 - s=3.13 : PP=29.90 - PSOE=21.40 - UP=21.90 - CS=17.10 - Resto=9.70
68 - TNS Demoscopia 2015-08-10 00:00:00 - s=3.04 : PP=26.40 - PSOE=22.40 - UP=21.50 - CS=13.40 - Resto=16.30
69 - TNS Demoscopia 2015-08-07 00:00:00 - s=3.02 : PP=31.40 - PSOE=27.60 - UP=16.50 - CS=9.90 - Resto=14.60
70 - Encuestamos 2015-08-23 00:00:00 - s=2.58 : PP=27.50 - PSOE=26.00 - UP=25.10 - CS=8.60 - Resto=12.80
71 - TNS Demoscopia 2015-08-03 00:00:00 - s=3.05 : PP=25.70 - PSOE=22.90 - UP=21.90 - CS=13.60 - Resto=15.90
72 - TNS Demoscopia 2015-07-23 00:00:00 - s=2.36 : PP=28.80 - PSOE=24.20 - UP=24.50 - CS=11.10 - Resto=11.40
73 - TNS Demoscopia 2015-07-27 00:00:00 - s=3.16 : PP=26.80 - PSOE=23.00 - UP=21.60 - CS=13.70 - Resto=14.90
74 - Metroscopia 2015-07-22 00:00:00 - s=3.16 : PP=23.10 - PSOE=23.50 - UP=23.70 - CS=16.00 - Resto=13.70
75 - Metroscopia 2015-07-24 00:00:00 - s=2.36 : PP=30.50 - PSOE=25.10 - UP=22.60 - CS=10.60 - Resto=11.20
76 - TNS Demoscopia 2015-07-20 00:00:00 - s=3.28 : PP=27.10 - PSOE=22.60 - UP=21.50 - CS=13.50 - Resto=15.30
77 - TNS Demoscopia 2015-07-13 00:00:00 - s=3.16 : PP=26.10 - PSOE=22.20 - UP=21.90 - CS=13.60 - Resto=16.20
78 - GAD3 2015-07-08 00:00:00 - s=3.16 : PP=29.10 - PSOE=25.50 - UP=18.60 - CS=12.10 - Resto=14.70
79 - Invymark 2015-07-06 00:00:00 - s=2.89 : PP=27.70 - PSOE=23.30 - UP=23.90 - CS=11.50 - Resto=13.60
80 - Encuestamos 2015-07-20 00:00:00 - s=2.58 : PP=26.00 - PSOE=25.40 - UP=25.20 - CS=9.20 - Resto=14.20
81 - CIS 2015-07-09 00:00:00 - s=2.01 : PP=28.20 - PSOE=24.90 - UP=19.40 - CS=11.10 - Resto=16.40
82 - Simple Lógica 2015-07-09 00:00:00 - s=3.10 : PP=26.20 - PSOE=23.10 - UP=20.30 - CS=18.40 - Resto=12.00
83 - Simple Lógica 2015-07-07 00:00:00 - s=3.02 : PP=30.70 - PSOE=27.40 - UP=18.10 - CS=10.20 - Resto=13.60
84 - Metroscopia 2015-07-02 00:00:00 - s=3.16 : PP=23.00 - PSOE=22.50 - UP=25.50 - CS=15.00 - Resto=14.00
85 - NC Report 2015-06-27 00:00:00 - s=3.33 : PP=31.20 - PSOE=24.40 - UP=16.90 - CS=11.60 - Resto=15.90
86 - Invymark 2015-06-22 00:00:00 - s=2.89 : PP=25.90 - PSOE=23.00 - UP=24.90 - CS=13.20 - Resto=13.00
87 - Encuestamos 2015-06-20 00:00:00 - s=2.63 : PP=25.70 - PSOE=25.20 - UP=25.70 - CS=9.80 - Resto=13.60
88 - Encuestamos 2015-06-26 00:00:00 - s=2.36 : PP=29.20 - PSOE=24.40 - UP=23.00 - CS=11.80 - Resto=11.60
89 - Encuestamos 2015-06-17 00:00:00 - s=2.36 : PP=27.00 - PSOE=26.10 - UP=24.30 - CS=10.30 - Resto=12.30
90 - TNS Demoscopia 2015-06-15 00:00:00 - s=3.16 : PP=23.80 - PSOE=22.10 - UP=23.60 - CS=15.50 - Resto=15.00
91 - NC Report 2015-06-06 00:00:00 - s=3.33 : PP=29.70 - PSOE=24.20 - UP=17.00 - CS=11.60 - Resto=17.50
92 - NC Report 2015-06-05 00:00:00 - s=3.02 : PP=29.60 - PSOE=26.80 - UP=17.60 - CS=10.30 - Resto=15.70
93 - Metroscopia 2015-06-02 00:00:00 - s=2.24 : PP=24.50 - PSOE=23.00 - UP=25.60 - CS=13.00 - Resto=13.90
94 - Invymark 2015-05-25 00:00:00 - s=2.89 : PP=25.40 - PSOE=22.00 - UP=24.40 - CS=15.20 - Resto=13.00
95 - Encuestamos 2015-05-15 00:00:00 - s=3.16 : PP=23.00 - PSOE=24.30 - UP=25.50 - CS=14.90 - Resto=12.30
96 - Encuestamos 2015-05-08 00:00:00 - s=3.02 : PP=28.70 - PSOE=26.20 - UP=17.80 - CS=12.00 - Resto=15.30
97 - Encuestamos 2015-04-28 00:00:00 - s=3.16 : PP=22.90 - PSOE=24.00 - UP=25.70 - CS=15.80 - Resto=11.60
98 - NC Report 2015-04-20 00:00:00 - s=3.33 : PP=28.60 - PSOE=23.80 - UP=19.70 - CS=12.00 - Resto=15.90
99 - NC Report 2015-04-22 00:00:00 - s=3.16 : PP=22.00 - PSOE=21.00 - UP=21.00 - CS=19.40 - Resto=16.60
100 - CIS 2015-04-12 00:00:00 - s=2.01 : PP=25.60 - PSOE=24.30 - UP=21.30 - CS=13.80 - Resto=15.00
101 - GESOP 2015-04-13 00:00:00 - s=3.16 : PP=23.50 - PSOE=19.10 - UP=23.80 - CS=17.70 - Resto=15.90
102 - Simple Lógica 2015-04-13 00:00:00 - s=3.07 : PP=21.30 - PSOE=21.10 - UP=25.40 - CS=20.80 - Resto=11.40
103 - Simple Lógica 2015-04-09 00:00:00 - s=3.00 : PP=26.60 - PSOE=19.70 - UP=25.30 - CS=16.60 - Resto=11.80
104 - Metroscopia 2015-04-09 00:00:00 - s=3.16 : PP=20.80 - PSOE=21.90 - UP=27.10 - CS=19.40 - Resto=10.80
105 - Metroscopia 2015-04-07 00:00:00 - s=3.02 : PP=29.50 - PSOE=26.60 - UP=17.90 - CS=12.10 - Resto=13.90
106 - NC Report 2015-04-02 00:00:00 - s=3.33 : PP=29.70 - PSOE=23.50 - UP=20.10 - CS=10.50 - Resto=16.20
107 - Encuestamos 2015-03-26 00:00:00 - s=3.33 : PP=23.70 - PSOE=23.60 - UP=25.50 - CS=14.70 - Resto=12.50
108 - Invymark 2015-03-23 00:00:00 - s=3.00 : PP=26.40 - PSOE=20.30 - UP=23.80 - CS=14.80 - Resto=14.70
109 - Simple Lógica 2015-03-13 00:00:00 - s=3.07 : PP=23.00 - PSOE=18.70 - UP=26.10 - CS=19.70 - Resto=12.50
110 - Simple Lógica 2015-03-09 00:00:00 - s=3.16 : PP=22.00 - PSOE=19.10 - UP=26.30 - CS=18.80 - Resto=13.80
111 - Simple Lógica 2015-03-06 00:00:00 - s=3.02 : PP=30.90 - PSOE=26.40 - UP=17.10 - CS=11.70 - Resto=13.90
112 - Metroscopia 2015-03-04 00:00:00 - s=3.16 : PP=18.60 - PSOE=20.20 - UP=28.10 - CS=18.40 - Resto=14.70
113 - Invymark 2015-02-26 00:00:00 - s=3.00 : PP=27.80 - PSOE=21.50 - UP=27.40 - CS=6.40 - Resto=16.90
114 - NC Report 2015-02-14 00:00:00 - s=3.00 : PP=29.30 - PSOE=22.80 - UP=28.00 - CS=3.30 - Resto=16.60
115 - NC Report 2015-02-12 00:00:00 - s=2.36 : PP=29.60 - PSOE=20.10 - UP=28.70 - CS=7.30 - Resto=14.30
116 - NC Report 2015-02-11 00:00:00 - s=3.16 : PP=22.50 - PSOE=19.50 - UP=27.60 - CS=13.40 - Resto=17.00
117 - Simple Lógica 2015-02-09 00:00:00 - s=3.07 : PP=26.80 - PSOE=17.80 - UP=33.00 - CS=8.50 - Resto=13.90
118 - Simple Lógica 2015-02-06 00:00:00 - s=3.02 : PP=31.90 - PSOE=24.40 - UP=22.80 - CS=6.20 - Resto=14.70
119 - Metroscopia 2015-02-04 00:00:00 - s=3.16 : PP=20.90 - PSOE=18.30 - UP=34.20 - CS=12.20 - Resto=14.40
120 - Invymark 2015-02-02 00:00:00 - s=3.00 : PP=26.10 - PSOE=20.10 - UP=30.30 - CS=3.80 - Resto=19.70
121 - CIS 2015-01-12 00:00:00 - s=2.00 : PP=27.30 - PSOE=22.20 - UP=29.10 - CS=3.10 - Resto=18.30
122 - Sigma Dos 2015-01-19 00:00:00 - s=3.16 : PP=27.10 - PSOE=21.40 - UP=31.10 - CS=5.00 - Resto=15.40
123 - GAD3 2015-01-16 00:00:00 - s=3.16 : PP=29.30 - PSOE=19.20 - UP=24.80 - CS=6.30 - Resto=20.40
124 - Sigma Dos 2015-01-14 00:00:00 - s=2.36 : PP=29.40 - PSOE=19.40 - UP=30.90 - CS=4.60 - Resto=15.70
125 - Simple Lógica 2015-01-14 00:00:00 - s=3.12 : PP=24.50 - PSOE=18.60 - UP=35.40 - CS=5.80 - Resto=15.70
126 - Metroscopia 2015-01-08 00:00:00 - s=3.16 : PP=19.20 - PSOE=23.50 - UP=33.50 - CS=8.10 - Resto=15.70
127 - Metroscopia 2015-01-06 00:00:00 - s=3.16 : PP=24.60 - PSOE=19.00 - UP=31.20 - CS=5.00 - Resto=20.20
128 - GESOP 2014-12-15 00:00:00 - s=3.00 : PP=26.10 - PSOE=19.80 - UP=29.70 - CS=3.80 - Resto=20.60
129 - DYM 2014-12-13 00:00:00 - s=3.00 : PP=26.10 - PSOE=16.70 - UP=35.20 - CS=4.20 - Resto=17.80
Número de sondeos incluidos : 130 de 120 posibles

Leemos el modelo que hemos definido y hacemos el sampling.


In [85]:
date.year-2000


Out[85]:
14

In [60]:
f = open('model.stan', 'r')
model = f.read()
f.close()

In [61]:
out = stan_cache(model, model_name='elecciones', data=dictionary, chains=1)


Using cached StanModel

Resultados

Extraemos algunas de las cantidades interesantes para hacer algunas representaciones.


In [62]:
thetaChain = out.extract('theta')['theta']
theta = np.percentile(thetaChain, [50.0, 50.0-68/2.0, 50.0+68/2], axis=0)
houseChain = out.extract('house')['house']
house = np.percentile(houseChain, [50.0, 50.0-68/2.0, 50.0+68/2], axis=0)
sigmaChain = out.extract('sigma')['sigma']
sigma = np.percentile(sigmaChain, [50.0, 50.0-68/2.0, 50.0+68/2], axis=0)

Sondeos y variación subyacente

En esta representación vemos la variación de las estimaciones de voto en puntos para cada una de las encuestas que hemos considerado y la estimación subyacente que hemos inferido. Como se ve, la varianza de la estimación subyacente cambia con el tiempo, siendo muy pequeña en la fecha de las elecciones, marcada con una línea vertical.


In [63]:
colors = ["blue", "red", "violet", "orange", "yellow"]
labelsPartidos = ['PP', 'PSOE', 'PODEMOS+IU', 'Cs', 'Otros']
labelsEmpresas = ['GAD3', 'Encuest', 'GESOP', 'Metroscopia', 'Celeste',' Demosc. S.', 'S. Lógica', 'CIS', 'TNS Demos.', 
                  'Invymark', 'NC Rep.', 'Netquest']
f, ax = pl.subplots(figsize=(13,8))
for i in range(5):
    ax.plot(weekAll, sondeosAll[:,i], '.', color=sns.xkcd_rgb[colors[i]], linewidth=2)
    ax.plot(np.arange(max(weekAll))+1, theta[0,:,i], color=sns.xkcd_rgb[colors[i]], linewidth=2)
    ax.fill_between(np.arange(max(weekAll))+1, theta[1,:,i], theta[2,:,i], color=sns.xkcd_rgb[colors[i]], alpha=0.3)
ax.set_xlabel('Semana')
ax.set_ylabel('Fracción de votos')
ax.axvline(49)
pl.savefig("sondeos.png")


Estimación de voto actual

A continuación pongo la estimación de voto actual y sus intervalos de confianza.


In [64]:
f, ax = pl.subplots()
sns.boxplot(data=thetaChain[:,-1,:], ax=ax)
ax.set_xticks(np.arange(5))
ax.set_xticklabels(labelsPartidos, rotation=90)
boxes = ax.artists
for (i, box) in enumerate(boxes):
    box.set_facecolor(sns.xkcd_rgb[colors[i]])
pl.savefig("estimacionActual.png")


A continuación represento la evolución temporal del voto por cada partido político, incluyendo las incertidumbres estimadas para cada sondeo.


In [65]:
labelsPartidos = ['PP', 'PSOE', 'PODEMOS+IU', 'Cs', 'Otros']
f, ax = pl.subplots(nrows=2, ncols=3, figsize=(10,8))
ax = ax.flatten()
for i in range(5):
    ax[i].plot(weekAll, sondeosAll[:,i], '.', color=sns.xkcd_rgb[colors[i]], linewidth=2)
    ax[i].set_ylim([0,0.35])
    ax[i].errorbar(weekAll, sondeosAll[:,i], fmt='none', yerr=sigmaAll, color=sns.xkcd_rgb[colors[i]], linewidth=2, ecolor=sns.xkcd_rgb[colors[i]])
    ax[i].plot(np.arange(max(weekAll))+1, theta[0,:,i], color=sns.xkcd_rgb[colors[i]], linewidth=2)
    ax[i].fill_between(np.arange(max(weekAll))+1, theta[1,:,i], theta[2,:,i], color=sns.xkcd_rgb[colors[i]], alpha=0.3)
    ax[i].set_xlabel('Semana')
    ax[i].set_xlabel('Porcentaje de votos')
    ax[i].set_title(labelsPartidos[i])
pl.tight_layout()
pl.savefig("porPartido.png")


Sesgos por casa encuestadora

Represento a continuación las estimaciones de sesgo para cada una de las empresas encuestadoras, lo que muestra algún efecto curioso en Demoscopia Servicios que sería interesante analizar y entender por qué pasa. Nótese que un sesgo positivo implica que esa empresa subestima los votos y hace falta corregirlo por esa cantidad para obtener las observaciones.


In [66]:
f, ax = pl.subplots(ncols=3, nrows=2, figsize=(12,10))
ax = ax.flatten()
for i in range(5):
    sns.boxplot(data=houseChain[:,:,i], ax=ax[i])
    ax[i].set_xticks(np.arange(12))
    ax[i].set_xticklabels(labelsEmpresas, rotation=90)
    ax[i].set_title(labelsPartidos[i])
pl.tight_layout()
pl.savefig("bias.png")


Variabilidad

Por último, aquí muestro la variabilidad intrínseca de la estimación subyacente de voto para cada partido. Es lógico que Podemos y Cs tengan una variabilidad mayor por su largo crecimiento en el último año.


In [67]:
f, ax = pl.subplots(figsize=(10,6))
sns.boxplot(data=sigmaChain, ax=ax)
ax.set_xticks(np.arange(5))
ax.set_xticklabels(labelsPartidos, rotation=90)
boxes = ax.artists
for (i, box) in enumerate(boxes):
    box.set_facecolor(sns.xkcd_rgb[colors[i]])
pl.savefig("variabilidad.png", facecolor=f.get_facecolor(), edgecolor='none')


Push de la página web


In [80]:
f = open('elecciones2015/index.html','r')
lines = f.readlines()
f.close()

In [81]:
fecha = datetime.datetime.now()
lines[29] = """<a id="welcome-to-github-pages" class="anchor" href="#welcome-to-github-pages" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Resultado {0}/{1}/{2}</h3>\n""".format(fecha.day,fecha.month,fecha.year)

theta = np.percentile(thetaChain, [50.0, 50.0-95/2.0, 50.0+95/2], axis=0)

lines[33] = """<td style='height: 50px; background: #02B3F4; color: white; font-size: 25px; border-radius: 1px 10px 0px 0px;'>{0:4.1f}-{1:4.1f}-{2:4.1f}</td>\n""".format(theta[1,-1,0]*100,theta[0,-1,0]*100,theta[2,-1,0]*100)
lines[34] = """<td style='height: 50px; background: #8E1744; color: white; font-size: 25px; border-radius: 1px 10px 0px 0px;'>{0:4.1f}-{1:4.1f}-{2:4.1f}</td>\n""".format(theta[1,-1,2]*100,theta[0,-1,2]*100,theta[2,-1,2]*100)
lines[35] = """<td style='height: 50px; background: #FF0202; color: white; font-size: 25px; border-radius: 1px 10px 0px 0px;'>{0:4.1f}-{1:4.1f}-{2:4.1f}</td>\n""".format(theta[1,-1,1]*100,theta[0,-1,1]*100,theta[2,-1,1]*100)
lines[36] = """<td style='height: 50px; background: #FF800E; color: white; font-size: 25px; border-radius: 1px 10px 0px 0px;'>{0:4.1f}-{1:4.1f}-{2:4.1f}</td>\n""".format(theta[1,-1,3]*100,theta[0,-1,3]*100,theta[2,-1,3]*100)

In [82]:
f = open('elecciones2015/index.html','w')
f.writelines(lines)
f.close()

In [83]:
os.system('./update.sh')


Out[83]:
0

In [ ]: