This data visualisation aims to discover the patterns behind Singapore’s General Election results. We intend to look at the historical trends of the performance of the different political parties across 3 General Elections (2011, 2015 and 2020), where every seat has been contested (sans 2011, where only one GRC became a walkover).
In the 2020 Parliamentary General Election (GE 2020), the People’s Action Party (PAP) was returned to power with 61.24% of the popular vote share, which was a decline from the previous General Election where it garnered 69.86% of the popular vote. Nevertheless, the PAP managed to retain its two-third supermajority in Parliament, but lost another GRC - Sengkang - to the Worker’s Party (WP).
Support for the opposition appears to have improved significantly. It became much more noticeable beginning from the 2011 General Election (GE 2011), where huge crowds began gathering at rallies of opposition parties compared to that of the ruling PAP. Of course, whether these translated to more votes for the opposition is what the visualisation that we will be performing aims to uncover. However, lacking attendance figures at rallies, and with the present Covid-19 situation having put plans for physical rallies on the backburner, it is impossible to conclusively relate attendance rates of rallies to votes. We will therefore solely use election results for our analysis to determine whether opposition support has been gaining ground.
We propose to utilise the stacked bar chart to compare the performance across political parties and across the 3 General Elections as defined earlier. We will utilise the stacked bar charts from ggplot to plot our graphs.
tidyverse for data manipulationggplot2 for plotting graphspackages = c(
'ggplot2',
'tidyverse'
)
for(p in packages){
if(!require(p, character.only = T)){
install.packages(p)
}
library(p, character.only = T)
}
There are 3 sets of data that we will need to load: - Results for 2020 General Election - Scraped using a separate script from the Elections Department website - Results for General Election of Previous Years from data.gov.sg - List of Political Parties from data.gov.sg
data_ge_2020_raw = read_csv('data/parliamentary-general-election-results-by-candidate-2020.csv')
Missing column names filled in: 'X1' [1]Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
data_ge_raw = read_csv('data/parliamentary-general-election-results-by-candidate.csv')
data_parties = read_csv('data/list-of-political-parties.csv')
The results of the General Election of previous years did not include the latest General Election results. Therefore, we have included the 2020 General Election results separately. We will have to perform cleaning on the datasets such that we have a single dataset from which we will visualise the results.
data_parties = data_parties %>%
add_row(abbreviation = 'PSP', political_party = 'Progress Singapore Party') %>%
add_row(abbreviation = 'PV', political_party = 'Peoples Voice') %>%
add_row(abbreviation = 'RDU', political_party = 'Red Dot United') %>%
add_row(abbreviation = 'Independent', political_party = 'Independent')
data_ge_2020_sum = data_ge_2020_raw %>%
group_by(constituency) %>%
summarise(n_total_votes = sum(n_votes)) %>%
ungroup()
data_ge_2020_clean = data_ge_2020_raw %>%
left_join(data_parties, by = c('party_long' = 'political_party')) %>%
left_join(data_ge_2020_sum, by = 'constituency') %>%
unique() %>%
rename(
vote_count = n_votes,
party = abbreviation,
constituency_type = type
) %>%
mutate(vote_percentage = vote_count / n_total_votes) %>%
add_column(
year = 2020,
is_walkover = F
) %>%
select(-X1, -division, -n_voters, -n_total_votes, -party_long)
data_ge_clean = data_ge_raw %>%
mutate(
n_seats = str_count(candidates, '\\|') + 1,
is_walkover = vote_count == 'na',
vote_count = if_else(vote_count == 'na', 0.0, as.double(vote_count)),
vote_percentage = if_else(vote_percentage == 'na', 0.0, as.double(vote_percentage))
) %>%
select(-candidates)
NAs introduced by coercionNAs introduced by coercion
After cleaning, we will obtain a single dataset.
data_ge = rbind(data_ge_clean, data_ge_2020_clean) %>%
mutate(vote_count = as.numeric(vote_count))
data_ge
First, we are interested to know what the performance of the PAP was like against its opposition competitors. We will therefore need to compute the percentage results for both the PAP and Opposition camps.
colours = c('PAP' = 'red', 'Opposition' = 'blue')
ggplot(data_ge_pap_vs_opp, aes(x = year, y = total_votes_percentage, fill = party_group)) +
geom_bar(stat = 'identity', position = 'fill') +
# geom_text(
# aes(label = scales::percent(total_votes_percentage)),
# position = position_stack(vjust = .5),
# colour = 'white'
# ) +
scale_y_continuous(labels = scales::percent) +
geom_hline(yintercept = 0.65) +
geom_hline(yintercept = 0.6) +
ggtitle('Performance of PAP over the years has continued to fluctuate') +
labs(x = 'General Election (Year)', y = 'Popular Vote (%)', fill = 'Party') +
scale_fill_manual(values = colours) +
theme(
axis.ticks.x = element_blank()
)
The PAP has indicated that the ideal popular vote share for their party is at 65% of all valid votes cast. We can observe from the visualisation that following Singapore’s independence in 1965, the popular vote share for the PAP has only fallen below its target 5 times. However, the PAP still enjoys a clear mandate even well into the recent election, with at least 60% of the popular vote. Nevertheless, except for the General Election in 2015, the popular vote share has been decreasing at each General Election. Conversely, support for the Opposition has likewise risen.
The Worker’s Party is the leading opposition party in Singapore, having had a presence in Parliament for at least 29 years. We know that the trend of the electorate voting for the opposition party has increased with each General Election. However, there has been discussions in political circles that more voters voted for the Workers’ Party than that of the PAP. We would like to know whether this statement holds true.
To conduct our analysis, we will need to filter down to the electoral divisions where both the Workers’ Party and the PAP had contested in before coming up with the visualisation.
colours = c('PAP' = 'red', 'WP' = 'blue')
header = 'More people voted for the Workers\' Party over the years'
ggplot(data_ge_pap_vs_wp_percentage, aes(x = year, y = total_votes_percentage, fill = party)) +
geom_bar(stat = 'identity', position = 'fill') +
# geom_text(
# aes(label = scales::percent(total_votes_percentage)),
# position = position_stack(vjust = .5),
# colour = 'white'
# ) +
scale_y_continuous(labels = scales::percent) +
geom_hline(yintercept = 0.5) +
ggtitle(header) +
labs(x = 'General Election (Year)', y = 'Popular Vote (%)', fill = 'Party') +
scale_fill_manual(values = colours) +
theme(
axis.ticks.x = element_blank()
)
We can see that support for the Workers’ Party has increased to the point where slightly more voters voted for the opposition than PAP in wards where the Workers’ Party were contesting in, most notably in the recent General Election.
While the ruling People’s Action Party remains the leading party trusted by many Singaporeans, the rise of opposition political parties is something that will keep the ruling party on its toes. The trends of the General Election is part of the process to help political parties identify the reasons for its popularity, or lack thereof. This could have an influence on the policy and decision making process in Singapore.