Sunday, February 10, 2019

Estimating the discontinuity

Following the change of Liberal Party leadership - from Turnbull to Morrison - I introduced a discontinuity into the model, which allows for a substantial single-day change in voter sentiment. Today, I further tweaked the model so that it estimates and reports on the size of the discontinuity.

The short answer is that on the day, the change cost the Coalition three percentage points in TPP vote share.


In the above run, I did not activate the set-up code which reduces the influence of the noiser polling houses. So three pecentage points is probably conservative.

The model code is as follows ...

// STAN: Two-Party Preferred (TPP) Vote Intention Model 
//     - Updated to allow for a discontinuity event (Turnbull -> Morrison)
//     - Updated to exclude some houses from the sum to zero constraint
//     - Updated to reduce the influence of the noisier polling houses

data {
    // data size
    int<lower=1> n_polls;
    int<lower=1> n_days;
    int<lower=1> n_houses;
    
    // assumed standard deviation for all polls
    real<lower=0> pseudoSampleSigma;
    
    // poll data
    vector<lower=0,upper=1>[n_polls] y; // TPP vote share
    int<lower=1> house[n_polls];
    int<lower=1> day[n_polls];
    vector<lower=0> [n_polls] poll_qual_adj; // poll quality adjustment
    
    // period of discontinuity event
    int<lower=1,upper=n_days> discontinuity;
    int<lower=1,upper=n_days> stability;
    
    // exclude final n houses from the house
    // effects sum to zero constraint.
    int<lower=0> n_exclude;
}

transformed data {
    // fixed day-to-day standard deviation
    real sigma = 0.0015;
    real sigma_volatile = 0.0045;
    int<lower=1> n_include = (n_houses - n_exclude);
}

parameters {
    vector[n_days] hidden_vote_share; 
    vector[n_houses] pHouseEffects;
    real disruption;
}

transformed parameters {
    vector[n_houses] houseEffect;
    houseEffect[1:n_houses] = pHouseEffects[1:n_houses] - 
        mean(pHouseEffects[1:n_include]);
}

model {
    // -- temporal model [this is the hidden state-space model]
    hidden_vote_share[1] ~ normal(0.5, 0.15); // PRIOR
    disruption ~ normal(0.0, 0.15); // PRIOR
    hidden_vote_share[2:(discontinuity-1)] ~ 
        normal(hidden_vote_share[1:(discontinuity-2)], sigma);
    hidden_vote_share[discontinuity] ~ normal(hidden_vote_share[discontinuity-1] + 
        disruption, sigma); 
    hidden_vote_share[(discontinuity+1):stability] ~ 
        normal(hidden_vote_share[discontinuity:(stability-1)], sigma_volatile);
    hidden_vote_share[(stability+1):n_days] ~ 
        normal(hidden_vote_share[stability:(n_days-1)], sigma);
    
    // -- house effects model
    pHouseEffects ~ normal(0, 0.08); // PRIOR 

    // -- observed data / measurement model
    y ~ normal(houseEffect[house] + hidden_vote_share[day], 
        pseudoSampleSigma + poll_qual_adj);
}