On Wednesday, May 8, 2019, South Africans had the opportunity to elect their leaders for the coming five years by taking part in the 2019 general election.

Of the 26727921 registered voters, 17671616 made their way to the ballots. Of these, 17436144 citizens managed to submit a valid vote.

These 17M+ votes were then tallied up to determine how the 400 parliamentary seats would be allocated to each of the qualifying parties.

The Sankey diagram below is a visualization of how exactly the 17M+ incoming votes contributed to the resultant parliamentary seat allocation.

Sankey diagram showing how all valid votes in the 2019 general election in South Africa contributed to the parliamentary seats allocated to the winning parties. Click for the high-res zoomable version where you can also hover over any chart element to get the relevant vote counts.

All 13 parties on the far right of the chart received extra seats (from the “pool”) during the second and third allocations according to the election rules.

All of the smaller parties who did not manage to gain a seat, as well as the largest party, contributed to those extra seats in the pool.

Read on for a more detailed explanation of the seat allocation algorithm.

Seat allocation algorithm

This PDF on the IEC South Africa’s website is a great albeit compact explanation of the seat allocation algorithm.

Based only on the valid votes, the Droop quota, or

$$\frac{\operatorname{ValidVotes}}{\operatorname{Seats} + 1} + 1$$

is used to determine the minimum number of votes required for each of the 400 parliamentary seats. In this election, it was 43482 votes per seat.

First allocation

During the first allocation, all parties with a minimum of the Droop quota (43482) of votes, were allocated the result of dividing their number of votes by the quota, without remainders.

In this way, 387 seats were allocated, leaving 13 extra seats. These 13 extra seats are due to parties who did not make the minimum quota at this stage, and due to the remainders of parties that did.

Second allocation

During the second allocation, all parties, including those that had so far received no seats, are sorted according to their remainder after the first quota division, from highest remainder to lowest remainder.

Any extra seats are then allocated to the five parties with the highest remainders.

Because there were 13 extra seats, each of the following five parties were granted an additional seat in the second allocation, because they had the highest remainders after the first allocation:

  1. United Democratic Movement (UDM): remainder of 34548 votes gained an extra seat for a total of 2.
  2. African Transformation Movement (ATM): remainder of 33348 votes gained an extra seat for a total 2.
  3. Pan Africanist Congress (PAC): remainder of 32677 votes gained an extra seat for a total of 1.
  4. Al Jama-ah: remainder of 31468 votes gained an extra seat for a total of 1.
  5. Good: remainder of 26926 votes gained an extra seat for a total of 2.

It’s interesting to note that here the PAC and Al Jama-ah went from 0 seats to a single seat in parliament.

Third allocation

During the third allocation, any remaining extra seats are allocated to the parties with the highest number of votes per seat assigned so far. In other words, this is assigning seats to parties who have “worked the hardest” for their seats, in terms of votes, up to this point.

Each of the following eight parties were granted an additional seat in the third allocation, because they had the highest number of voters per seat already earned (sorted from highest to lowest within the winning 8):

  1. National Freedom Party: From 1 to 2.
  2. African Christian Democratic Party: From 3 to 4.
  3. African Independent Congress: From 1 to 2.
  4. Congress of the People: From 1 to 2.
  5. Freedom Front Plus: From 9 to 10.
  6. Inkatha Freedom Party: From 13 to 14.
  7. Economic Freedom Fighters: From 43 to 44.
  8. Democratic Alliance: From 83 to 84.

Implementation

I imported the national assembly votes and seats table from the Wikipedia page into a spreadsheet.

After this, I added the first, second and third allocation stages as per the IEC descriptions, double-checking that my calculations yielded the same results as the official ones.

Then I wrote a Python script to convert the spreadsheet to a JSON dataset suitable for use with d3.sankey, and then used an Observable notebook to render the SVG diagram above.

If I had more time, I would add a dash of javascript and CSS to highlight vote streams on hover.

Conclusions

Reducing millions of votes to a few hundred seats in parliament is a tricky problem which the electoral system solves in an as-fair-as-possible way.

In this specific case, votes for smaller parties (who did not manage to gain a seat in parliament) ended up contributing to seats that were allocated to opposition parties exclusively.

With slightly different numbers, it is entirely possible that the incumbent could have gained one of these extra seats.

That being said, the only mistake in these elections would have been not to participate, either by not showing up, or by deliberately spoiling one’s vote.