## Thursday, April 25, 2013

### Towards a more integrated primary vote share model

A few weeks ago I said that I had developed a Bayesian model for primary vote shares. At the time, I was seeking (but had not attained) an integrated solution that ensured the primary votes shares across the four party groups (Coalition, Labor, Green and Other) summed to one (or one hundred per cent). My original model typically summed in the range 99.5 to 100.5 per cent.

I have now have a more integrated model working. This new model typically sums in the range 99.95 to 100.05 per cent (an order of magnitude improvement on the unintegrated model). The model is as follows.

model {
#### -- observational model
for(poll in 1:NUMPOLLS) { # for each poll result - rows
for(party in 1:PARTIES) { # for each party - columns
yhat[poll, party] <- houseEffect[house[poll], party] +
walk[pollDay[poll], party]
primaryVotes[poll, party] ~ dnorm(yhat[poll, party], precision[poll, party])
}
}

#### -- temporal model (a daily walk where today is much like yesterday)
for(day in 2:PERIOD) { # rows
for (party in 1:PARTIES) { # columns
tmp[day, party] ~ dnorm(walk[day-1, party], walkPrecision[party])
}
}

## -- impose a sum-to-one constraint ... total of all parties sums to one every day
for(day in 1:PERIOD) { # rows
walk[day, 1:PARTIES] <- tmp[day, 1:PARTIES] / sum(tmp[day, 1:PARTIES ])
}

## -- constrained priors for the day-to-day variance of the temporal model
for(party in 1:PARTIES) { # for each party
sigmaWalk[party] ~ dunif(0, 0.005)  ## uniform prior on std. dev.
walkPrecision[party] <- pow(sigmaWalk[party], -2)
}

## -- uninformative priors for first day in the temporal model
for (party in 1:PARTIES) { # for each party
tmp[1, party] ~ dunif(0.0001, 0.9999) # fairly uninformative
}

#### -- sum-to-zero constraint on house effects (ignoring Morgan F2F)
for (party in 1:PARTIES) { # for each party, house effects across houses sum to zero
houseEffect[1, party] <- 0 - sum( houseEffect[2:HOUSECOUNT, party] ) +
houseEffect[MORGANF2F, party]
}
for(house in 2:HOUSECOUNT) { # for each house, house effects across the parties sum to zero
houseEffect[house, 1] <- 0 - sum( houseEffect[house, 2:PARTIES] )
}
# but note, we do not apply a double constraint to houseEffect[1, 1] [NEWSPOLL, LABOR]

## -- vague normal priors for house effects - centred on zero
for (party in 2:PARTIES) { # for each party (cols)
for(house in 2:HOUSECOUNT) { #  (rows)
houseEffect[house, party] ~ dnorm(0, pow(0.1, -2))
}
}
}

The results from the model (with a 100,000 iteration run) are as follows ... noting this model took an hour of computing time.

While I have a working model, I remain concerned that it is inelegant. If you are feeling particularly wonkish, you can help me improve the model.