Calculating Indonesia House of Representative’s 2019 Election Result with Sainte-Laguë Method using R

Baya Inggas
7 min readMay 12, 2019

--

If you are rather unfamiliar but curious on how Sainte-Laguë method works, the wikipedia page for Webster/Sainte-Laguë would be a good place to start. If you prefer a more practical simulation on the Indonesian election case, here is a nice article by Tirto.

Indonesia just underwent one of the most complex elections in the world, contesting presidential candidates along with representative in each level of legislative hierarchy. On. The. Same. Day.

To add into complexity, the arrangement of the seats for the House of Representative is not decided by the total vote on the national level. well, kind of. There are two sets of barriers each party has to go through in this political hurdles:

  1. Each party must get at least 4% of the total vote on the national level for the vote to even matter at all. This year, 7 out of 16 participating parties are expected to fail to reach this threshold. What happened to the vote that went to those 7 parties? They won’t be considered in the final count.
  2. Now that we have slimmed down the race to 9 parties instead of 16, we have to count which party got how many seat. This part is not done using the total of the vote on national level. Instead, each constituency has an assigned number of seats proportional to their population; summing up to 575 of them in total. The total vote for each party (only 9 of them) in each constituency will be the only deciding factor and will be calculated using the Sainte-Laguë method.

It would be a pain to perform the Sainte-Laguë method one by one for each of the 77 constituencies. Fortunately, we can simulate it in R and get the result right away, quicker than you can make kopi tubruk.

Disclaimer:

This simulation is meant to only show how the House of Representative would look like as it stands. The data used in this simulation was obtained from the real count done by KPU on 11 May 2019, with 38.7% of the total votes counted.

Now let’s get into the codes, shall we?

Tidying up

First thing first, we make dataframes that spark joy. For this particular task, dplyr and tidyr packages come in handy. We will also use electoral package to help us calculating the seat arrangement with the Sainte-Laguë method. Lastly, we carry out the visualization using ggplot2 and ggparliament packages which would help us visualize the parliamentary seating arrangement.

library(dplyr)
library(tidyr)
# for calculating seat arrangement based on votes
# with Webster/Sainte-Laguë method
library(electoral)
#for seat arrangement visuallibrary(ggplot2)
library(ggparliament)

Next, we simply import the files containing latest real count by KPU and seat allocation per constituency. You can get ready-to-go files here.

# Seat allocation by constituency
kursi_dapil <- read.csv(“kursi_legislatif_2019_2024.CSV”,
header = TRUE, sep = ‘;’)
# Latest vote count by constituency
hasil_dapil <- read.csv(“hasil_pileg_20190511.csv”,
header = TRUE, sep = ‘;’)
hasil_dapil

The file mirrors KPU’s format which gives you the constituencies as observations and parties as measurements. It would be easier for us to perform analysis if each parties are in rows instead of columns. To do this, we use the gather function and save it to suara_dapil object as follows:

suara_dapil <- gather(hasil_dapil, key = “PARTAI”,
value = “SUARA”, -WILAYAH)
head(suara_dapil)

Now that the dataframe looks workable, we can get on the calculation. The first barrier is to filter out parties that don’t make it through the 4% Parliamentary Threshold. Thus, we count the total vote for each party and the corresponding percentage such as:

# Get the nation-wide sum of votes for each partyhasil_partai <- suara_dapil %>%
group_by(PARTAI) %>%
summarise(JUMLAH_SUARA = sum(SUARA)) %>%
mutate(PERSENTASE =
round(JUMLAH_SUARA/sum(JUMLAH_SUARA)*100, 1))
hasil_partai

We now know which party gets how many percent of the total national vote. The list of parties that make it through the threshold will be useful later. To produce the list, we simply perform filter and get the value of the PARTAI column.

# Make the list of parties that make it through
# the Parliamentary Threshold
partai_lolos <- hasil_partai %>%
filter(PERSENTASE >= 4)
list_partai_lolos = partai_lolos$PARTAIlist_partai_lolos

The list will now be used to filter our original suara_dapil dataframe.

# Go back to suara_dapil dataframe 
# and filter out parties that don't make it through the
# Parliamentary Threshold
partai_lolos_dapil <- suara_dapil %>%
filter(PARTAI %in% list_partai_lolos) %>%
left_join(kursi_dapil, by = c(“WILAYAH” = “CONSTITUENCY”)) %>%
arrange(WILAYAH)
partai_lolos_dapil

For the next step, we are going to use the Sainte-Laguë method and work with two dataframes: kursi_dapil (containing constituencies and their respective seat allocation) and partai_lolos_dapil (containing constituencies, parties, and their respective count of vote and seat allocation).

In preparation, we create an empty list to house the result of seat allocation per constituency in each run.

We then use a for loop to calculate seat allocation per constituency using seats_ha function from electoral package and set the method to “webster” which will give you the divisor of 1,3,5,7,9,… in each calculation as the Sainte-Laguë method dictates.

Once we run through all of the constituencies, we bind the result together and we will be served with a complete dataframe containing constituencies, parties, votes, and how many seats they get in each constituency.

bagi_kursi = list() #empty datalistfor (x in 1:nrow(kursi_dapil)) {#Get a constituency for each run
dapil = kursi_dapil$CONSTITUENCY[[x]]
#Get the seat allocation by each constituency
kursi = kursi_dapil$SEAT[[x]]

df <- partai_lolos_dapil %>%
filter(WILAYAH == dapil) %>%
mutate(SEAT = seats_ha(PARTAI, SUARA, n_seats = kursi
, method = “webster”))
# Use method = “webster” to use 1,3,5,7,9,… as divisors

bagi_kursi[[x]] <- df
}
hasil_akhir = do.call(rbind, bagi_kursi) #Collect the resulthasil_akhir

We now know how many seats each party gets in a constituency. Now we can quickly gather the information how many seats each party gets overall along with the percentage over 575 total available seats.

kursi_partai <- hasil_akhir %>% 
group_by(PARTAI) %>%
summarise(KURSI = sum(SEAT)) %>%
mutate(PERSENTASE = round(KURSI/sum(KURSI)*100))
kursi_partai

We can also add one more dimension such as which party supports the incumbent President of Indonesia, Joko Widodo, and which supports his challenger, Prabowo Subianto, in the 2019 election cycle.

kursi_partai <- kursi_partai %>% 
mutate(DUKUNGAN = c(“Oposisi”,”Oposisi”, “Petahana”,
“Petahana”, “Oposisi”, “Petahana”, “Petahana”,
“Oposisi”, “Petahana” )) %>%
arrange(desc(DUKUNGAN),KURSI)
kursi_partai

Stop the numbers! I want colors!

Yes, yes. Numbers can be boring. It’s over, though. We can now get into the visual now using ggplot2 and ggparliament.

The following workflow will give you the visual of seat allocation by parties:

color_pal <- c("PPP" = "#1c5e1b", "NasDem" = "#3c447c",
"PKB" = "#698670", "Golkar" = "#FBF501",
"PDIP" = "#870e0f", "PAN" = "#ACC4DC",
"Demokrat" = "#020197", "PKS" = "#030202",
"Gerindra" = "#F3C916" )
color_break <- c("PPP", "NasDem", "PKB", "Golkar", "PDIP", "PAN",
"Demokrat","PKS", "Gerindra")
kursi_dpr <- parliament_data(election_data = kursi_partai,
type = “semicircle”,
parl_rows = 10,
party_seats = kursi_partai$KURSI)
gg_dpr <- ggplot(kursi_dpr, aes(x, y, colour = PARTAI)) +
geom_parliament_seats(size = 2.5) +
#set theme_ggparliament
theme_ggparliament() +
#other aesthetics
scale_colour_manual(values = color_pal,
breaks=color_break)
gg_dpr

9 Parties can be much of a full house. We may be interested in how many seats the supporting parties of incumbent President gets versus their opposition. The workflow for that one is as follows:

color_pal_2 <- c(“Petahana” = “#7B52AC”, “Oposisi” = “#F47321”)petahana_oposisi <- parliament_data(election_data = kursi_partai,
type = “semicircle”,
parl_rows = 10,
party_seats = kursi_partai$KURSI)
gg_petahana_oposisi <- ggplot(kursi_dpr,
aes(x, y, colour = DUKUNGAN)) +
geom_parliament_seats(size = 2.5) +
#draw majority threshold
draw_majoritythreshold(n = 288, label = TRUE,
type = ‘semicircle’)+
#set theme_ggparliament
theme_ggparliament() +
scale_colour_manual(values = color_pal_2)
gg_petahana_oposisi

Now you’re all set! Have fun scribble around the code and visual!

Should you have any feedback or want to discuss more on ideas, you can reach me through my email: baya.inggas@gmail.com

I’m pretty active on twitter too: @BayaInggas

--

--