torstai 14. huhtikuuta 2016

Predicting elections with socio-economic indicators - case Finnish Centre Party

All the way back from my time in university as a political sciences student with growing interest towards statistical methods, I have been intrigued by the prospects of explaining and predicting political elections using socio-economic indicators. Without resorting to such traditional explanatory variables from within the political sphere as the polls, news events or candidates' media acumen, how accurate predictions or astute explanatory models could I draw from simple socio-economic variables such as regional unemployment or education levels? Could I predict the candidate's chance of being elected knowing his age, income, marital status and degree? (turns out I can to a surprisingly large degree, link in Finnish)

This time I showcase a neat predictive model called random forest with my test case being the 2015 Finnish parliamentary elections and specifically the Centre Party. A natural party of government ever since the second World War ideologically rooted in the Finnish agrarian movement and situated between centre-left social democrats and centre right National Coalition, the party slumped to it's worst ever post-independence election result in 2011, gathering 15,8 % of votes cast. Four year later the party regained top spot with 21,1 % support, aided largely by inept 6-party rainbow coalition government in charge in between the contests. Besides the overwhelming victory, what makes Centre Party so interesting as a test case is regionally stable and rather well-defined support concentrated in large part to the Finnish rural municipalities. Training a random forest model on 2011 election and predicting (or postdicting?) the 2015 results with it, can I do better than the baseline model of predicting exactly same election result for each municipality?

Lets begin! (for convenience I will skip large parts of code involving the nitty-gritties of data wrangling required to assemble the datasets used)

Lets start with the simple baseline model: the average of absolute difference in support of Centre Party in elections of 2015 and 2011, geographical unit being a municipality
mean(abs(centredf$KESK - centredf$KESK2011))
[1] 6.755305
 So this is the score to beat and below are the tools we attempt to accomplish the task with:

library(rgdal)
library(randomForest)
library(tmap)
library(ggplot2)

Tmap is for GIS purposes, that is visualizing the results on a map while rgdal allows me to read an kml-file on which the visualization is done on. Ggplot2 is for your more regular run-of-the-mill visualization purposes. The actual predictive tool is stored in aptly named library randomForest.

Very shortly explained, random forests are an extension of decision trees, one of the most used data mining algorithms. Random forest-model tries to overcome the weaknessess of decision trees by believing in the wisdom of the masses. As the name suggests, it creates a number of decision tree models, randomizing the variables and rows included in the modeling process. While losing the ease of interpretability of decision trees, by averaging the biases and errors of individual decision trees it should come packed with increased predictive power.

For my independent variables I have picked a number of indicators off Statistics Finland's open data menu, including such classics as unemployment level, average age, maintenance ratio and demographic loss from movement. While my variable set is still within maintainable levels, it is still easier to create the formula for the algorithm outside randomForest-function. That is achieved by saving the names of variables to a vector, picking a fitting subset of them and then pasting them on to an as.formula-function.
n <- names(df)
n <- n[-c(4:11)]
fkesk <- as.formula(paste("KESK ~", paste(n,collapse = " + ")))
Equipped with ready made formula, we are ready to start predicting. At this point, data is split between two dataframes, df and df2011, consisting of exactly the same make up of variables but one for 2015 elections and the latter, naturally, for 2011. Training the predictive model is done simply by feeding randomForest-function the formula saved in variable fkesk and the 2011 data we wish to train the predictive model with. By saving the resulting model to fitkesk and using predict-function to 2015 data we get our predictions. By merging the resulting data with the original 2015 dataframe it is easy to plot the predicted vs observed values on a scatter plot.

fitkesk <- randomForest(fkesk, data=df2011)
predkesk <- predict(fitkesk,df)
dffkesk <- merge(df,predkesk, by="row.names")

ggplot(data=dffkesk, aes(y, KESK)) + geom_point(color="darkgreen") + geom_smooth(method="lm") + coord_cartesian(xlim=c(0,80), ylim = c(0,80))


We see that there's definitely a clear trend between predicted and actual values. The variation is pretty evenly spread. The obvious problem are the few municipilaties where Centre Party gained no votes at all where the worst individual mistake predicted over 30 percent of votes.

Looking at the plot alone we can infer that the baseline model could have beaten us this time. And sure enough:

mean(abs(dffkesk$KESK - dffkesk$y))
11.4
We almost doubled the average absolute difference between predicted and actual values! How is this possible?

This is a proof that data science and fancy algorithms are often only as good as the domain knowledge and understanding of the subject matter behind it. Elections that really shake the political field are hard enough to gauge with a human eye with access to all the relevant information; almost impossible for models trained with secondary, "dumb" data. Centre Party's massive electoral victory meant that whereas the model trained for 2011 elections would have made mistakes even inside the specific 2011 electoral contest, the model's biases and mistakes were multiplied when the context was badly shaken.

So what kind of biases did our model have? Let's take a look at how the support for Centre Party was spread vis a vis the spread of error in predictions geographically. For this, we use some of R's great GIS capabilities, though in a very simple manner.

dffkesk$diff <- abs(dffkesk$KESK - dffkesk$y)
names(dffkesk)[19] <- "Name"

kml<-readOGR("C:\\Users\\user\\Desktop\\Kuntarajat 2015.kml", "Kuntarajat 2015", stringsAsFactors = F, use_iconv = T,
             encoding = "UTF-8")

kml@data <- left_join(kml@data, dffkesk)

qtm(kml, "diff")
qtm(kml, "KESK")
First we create a new variable diff to store absolute difference between. Then we change the column containing municipal names to "Name", which is exactly the column name in our kml-file. Somebody else can explain the details of kml-files but what one needs to know that they are files containing slots for both traditional data (such as, employment levels etc) and the geographical details (coordinates and such, creating an empty map we can paint with our data)

I have downloaded the kml file containing Finnish map with 2015 municipal borders from Finnish datajournalismi-blog. readOgr is used to bring it to R system. use_iconv and encoding-options are used to deal with the umlauts met everywhere in beautiful Finnish language (hello, yököttää!). Finally, by left_joining the traditional data set dffkesk to kml's data-slot, we have the data to visualize. Qtm-function from library tmap makes it about as it as it gets, requiring at it simplest only two parameters: kml file and the variable to be plotted. And the results aren't ugly either!
 Map of absolute difference between predicted and actual values
Actual electoral support of Finnish Centre Party in Finnish parliamentary elections 2015

Two likely patterns stand out: the model performed worst in cases of exceptionally high or exceptionally low actual support for Centre Party. Nobody who has heard the phrase "regression toward the mean" should be surprised. The model, not knowing better, tends gravitate more towards the average support. This is especially so when socioeconomic differences between municipalities are not as great as the difference in political support and traditions.

To sum up the lesson: good algorithms don't necessarily produce good results but the results of "dumb" models can be sufficiently close to the real deal.

Naturally I went through this same exercise for all the political parties involved in the elections. I just wanted to focus on the most interesting case and left the best performing model to be revealed at the end. So which model was the winner?

Dun dun dun.

The model predicting the support for Green Party was the standout performer. Below is the scatter plot and "baseline" and "model" absolute differences.

Absolute difference of 2011 and 2015 election scores for Greens: 0.91 
Absolute difference of predicted and actual values: 1.37 


This proves we can get close when the party in question has stable level of support with socioeconomically distinct strongholds (well-off, educated and most of all urban municipalies). Just imagine how good a model could be armed with a little common sense, such as polling numbers.