Python GIS, and election results

Share on:

Election mapping

A few weeks ago there was a by-election in my local electorate (known as an electoral division) of Batman here in Australia. I was interested in comparing the results of this election with the previous election two years ago. In this division it's become a two-horse race: the Greens against the Australian Labor Party. Although Batman had been a solid Labor seat for almost its entire existence - it used to be considered one of the safest Labor seats in the country - over the past decade or so the Greens have been making inroads into this Labor heartland, to the extent that is no longer considered a safe seat. And in fact for this particular election the Greens were the popular choice to win. In the end Labor won, but my interest is not so much tracing the votes, but trying to map them.

Python has a vast suite of mapping tools, so much so that it may be that Python has become the GIS tool of choice. And there are lots of web pages devoted to discussing these tools and their uses, such as this one.

My interest was producing maps such as are produced by pollbludger This is the image from that page:

!pollbludger

As you can see there are basically three elements:

  • the underlying streetmap
  • the border of the division
  • the numbers showing the percentage wins of each party at the various polling booths.

I wanted to do something similar, but replace the numbers with circles whose sizes showed the strength of the percentage win at each place.

Getting the information

Because this election was in a federal division, the management of the polls and of the results (including counting the votes) was managed by the Australian Electoral Commission, whose pages about this by-election contain pretty much all publicly available information. You can copy and paste the results from their pages, or download them as CSV files.

Then I needed to find the coordinates (Longitude and Latitude) of all the polling places, of which there were 42 at fixed locations. There didn't seem to be a downloadable file for this, so for each booth address (given on the AEC site), I entered it into Google Maps and copied down the coordinates as given.

The boundaries of all the divisions can again be downloaded from the AEC GIS page. These are given in various standard GIS files.

Putting it all together

The tools I felt brave enough to use were:

  • Pandas: Python's data analysis library. I really only needed to read information from CSV files that I could then use later.
  • Geopandas: This is a GIS library with Pandas-like syntax, and is designed in part to be a GIS extension to Pandas. I would use it to extract and manage the boundary data of the electoral division.
  • Cartopy: which is a library of "cartographic tools".

And of course the standard matplotlib for plotting, numpy for array handling.

My guides were the London tube stations example from Cartopy and a local (Australian) data analysis blog which discussed the use of Cartopy including adding graphics to an map image.

There are lots of other GIS tools for Python, some of which seem to be very good indeed, and all of which I downloaded:

  • Fiona: which is a "nimble" API for handling maps
  • Descartes: which provides a means by which matplotlib can be used to manage geographic objects
  • geoplotlib: for "visualizing geographical data and making maps"
  • Folium: for visualizing maps using the leaflet.js library. It may be that the mapping I wanted to do with Python could have been done just as well in Javascript alone. And probably other languages. I stuck with Python simply because I knew it best.
  • QGIS: which is designed to be a complete free and open source GIS, and with APIs both for Python and C++
  • GDAL: the "Geospatial Data Abstraction Library" which has a Python package also called GDAL, for manipulating geospatial raster and vector data.

I suspect that if I was professionally working in the GIS area some or all of these packages would be at least as - and maybe even more - suitable than the ones I ended up using. But then, I was starting from a position of absolute zero with regards to GIS, and also I wanted to be able to make use of the tools I already knew, such as Pandas, matplotlib, and numpy.

Here's the start, importing the libraries, or the bits of them I needed:

1import matplotlib.pyplot as plt
2import numpy as np
3import cartopy.crs as ccrs
4from cartopy.io.img_tiles import GoogleTiles
5import geopandas as gpd
6import pandas as pd

I then had to read in the election data, which was a CSV files from the AEC containing the Booth, and the final distributed percentage weighting to the ALP and Greens candidates, and heir percentage scores. As well, I read in the boundary data:

1bb = pd.read_csv('Elections/batman_booths_coords.csv')  # contains all election info plus lat, long of booths
2longs = np.array(bb['Long'])
3lats = np.array(bb['Lat'])
4v = gpd.read_file('VicMaps/VIC_ELB.MIF')  # all electoral divisions in MapInfo form
5bg = v.loc[2].geometry                    # This is the Polygon representing Batman
6b_longs = bg.exterior.xy[0]               # These next two lines are the longitudes and latitudes
7b_lats = bg.exterior.xy[1]                #

Notice that bb uses Pandas to read in the CSV files which contains all the AEC information, as well as the latitude and longitude of each Booth, which I'd added myself. Here longs and lats are the coordinates of the polling booths, and b_longs and b-lats are all the vertices which form the boundary of the division.

Now it's all pretty straigtforward, especially with the examples mentioned above:

 1fig = plt.figure(figsize=(16,16))
 2
 3tiler = GoogleTiles()
 4ax = plt.axes(projection=tiler.crs)
 5
 6margin=0.01
 7ax.set_extent((bg.bounds[0]-margin, bg.bounds[2]+margin,bg.bounds[1]-margin, bg.bounds[3]+margin))
 8
 9ax.add_image(tiler,12)
10for i in range(44):
11    plt.plot(longs[i],lats[i],ga2[i],markersize=abs(ga[i]),alpha=0.7,transform=ccrs.Geodetic())
12
13plt.plot(b_longs,b_lats,'k-',linewidth=5,transform=ccrs.Geodetic())
14plt.title('Booth results in the 2018 Batman by-election')
15plt.show()

Here GoogleTiles provide the street map to be used as the "base" of our map. Open Streep Map (as OSM) is available too, but I thin in this instance, Google Maps is better. Because the map is rendered as an image (with some unavoidable blurring), I find that Google gave a better result than OSM.

Also, ga2 is a little array which simply produces plotting of the style ro (red circle) or go (green circle). Again, I make the program do most of the work.

And here is the result, saved as an image:

!Batman 2018

I'm quite pleased with this output. https://www.gnu.org/software/glpk/