1. Overview

The aim of the visualization is to discover trends of from the 2015 General Elections to the recently held General Elections in July of 2020. I’ll be comparing the percentage of votes of the ruling party, the People’s Action Party(PAP) and the opposition parties mainly, the strongest opposition in recent years, the Worker’s Party (WP).

For GE2015, the KML file (for illustrating the electoral boundaries) and the results of the election can be found on data.gov.
For GE2020, the KML file can be retrieved from data.gov and the results of the election can be found on the Elections Department Singapore’s website.

2. Major Data and Design Problems

  1. Data Challenge - Neither an Excel file nor a CSV file about the results of the 2020 General Election(G.E.) was available. The file I downloaded from data.gov only has results from GE1955 to GE2015. To solve the lack of compiled results (no GE2020 information), I manually copied and pasted the results into the file I downloaded.

  2. Data Challenge - The electoral boundary of 2020 file was only available in a KMZ file but I needed it to be in a SHP file. To solve this problem, I used an online converter - MyGeodata Cloud to get the SHP file.

  3. Design Challenge When it came to the labeling of the constituency of the map, there are some labels that overlap each other. To rectify this problem, I used and set the parameter - remove.overlap = TRUE in the tm_text function.

  4. Design Challenge I had difficulty in displaying 2 separate scaled colours for PAP and WP meaning a gradient of blue for PAP and a gradient of Red for WP on the map. To solve this, I separated the original dataframe into map_2015_pap containing the constituencies that PAP won and map_2015_wp containing the constituencies that WP won. This is done likewise for 2020 results. I then used 2 tm_shape functions and add them together.

  5. Design Challenge When setting the tmap_mode to view (meaning the graph will be interactive), the labels of the constituency are not correctly displayed. They display “kml_3”, “kml_6”, etc, which is the first column of the combined SHP and CSV files. To display the constituency names (they are in the 3rd column), I move the 3rd column to the 1st column using: mpeb_2015[,c(3,1,2,4,5,6,7)].

3. Proposed Sketch Designs



4. Load the Necessary Libraries and Data, Conduct Data Wrangling and Generate the Plots.

library(tidyverse); library(ggplot2); library(tmap); library(sf); library(plotly);

Load the csv file containing results for 2015 and 2020.

results <- read_csv("parliamentary-general-election-results-by-candidate-edited.csv")
glimpse(results)
## Rows: 1,539
## Columns: 8
## $ year              <dbl> 1955, 1955, 1955, 1955, 1955, 1955, 1955, 1955, 1...
## $ constituency      <chr> "Bukit Panjang", "Bukit Panjang", "Bukit Timah", ...
## $ constituency_type <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...
## $ candidates        <chr> "Goh Tong Liang", "Lim Wee Toh", "S. F. Ho", "Lim...
## $ party             <chr> "PP", "SLF", "PP", "PAP", "SLF", "DP", "SLF", "PP...
## $ vote_count        <dbl> 3097, 1192, 722, 3259, 924, 1308, 3305, 2530, 111...
## $ vote_percentage   <dbl> 0.7221, 0.2779, 0.1162, 0.5245, 0.1488, 0.2105, 0...
## $ winner            <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...

To convert the vote_percentage column into percentage instead of proportion, multiply by 100.

results$vote_percentage <- results$vote_percentage*100
glimpse(results)
## Rows: 1,539
## Columns: 8
## $ year              <dbl> 1955, 1955, 1955, 1955, 1955, 1955, 1955, 1955, 1...
## $ constituency      <chr> "Bukit Panjang", "Bukit Panjang", "Bukit Timah", ...
## $ constituency_type <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...
## $ candidates        <chr> "Goh Tong Liang", "Lim Wee Toh", "S. F. Ho", "Lim...
## $ party             <chr> "PP", "SLF", "PP", "PAP", "SLF", "DP", "SLF", "PP...
## $ vote_count        <dbl> 3097, 1192, 722, 3259, 924, 1308, 3305, 2530, 111...
## $ vote_percentage   <dbl> 72.21, 27.79, 11.62, 52.45, 14.88, 21.05, 47.58, ...
## $ winner            <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...

Load the 2015 electoral boundary SHP file.

mpeb_2015 <- st_read(dsn = "electoral_boundaries_2015", 
                layer = "electoral-boundary-2015-kml-polygon")
## Reading layer `electoral-boundary-2015-kml-polygon' from data source `C:\smu\Year 3 Sem 1\Visual Analytics\Assignments (Makeovers)\Assignment 5\A5\electoral_boundaries_2015' using driver `ESRI Shapefile'
## Simple feature collection with 29 features and 6 fields
## geometry type:  POLYGON
## dimension:      XY
## bbox:           xmin: 103.599 ymin: 1.150372 xmax: 104.0975 ymax: 1.477406
## geographic CRS: WGS 84
glimpse(mpeb_2015)
## Rows: 29
## Columns: 7
## $ Name       <chr> "kml_3", "kml_6", "kml_7", "kml_9", "kml_12", "kml_14", ...
## $ descriptio <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
## $ ED_DESC    <chr> "HOLLAND-BUKIT TIMAH", "PUNGGOL EAST", "PIONEER", "POTON...
## $ ED_CODE    <chr> "HT", "PE", "PI", "PS", "SW", "TP", "WE", "YH", "MP", "B...
## $ INC_CRC    <chr> "59D1ADB532F37979", "8298D32B826A5881", "20CC8109497AD9F...
## $ FMEL_UPD_D <chr> "20150916151419", "20150916151419", "20150916151419", "2...
## $ geometry   <POLYGON [°]> POLYGON ((103.8059 1.411095..., POLYGON ((103.91...

Move “ED_DESC” to the 1st column and capitalize the first letter of the constituencies

mpeb_2015 <- mpeb_2015[,c(3,1,2,4,5,6,7)] 

mpeb_2015$ED_DESC <- str_to_title(mpeb_2015$ED_DESC)

glimpse(mpeb_2015)
## Rows: 29
## Columns: 7
## $ ED_DESC    <chr> "Holland-Bukit Timah", "Punggol East", "Pioneer", "Poton...
## $ Name       <chr> "kml_3", "kml_6", "kml_7", "kml_9", "kml_12", "kml_14", ...
## $ descriptio <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
## $ ED_CODE    <chr> "HT", "PE", "PI", "PS", "SW", "TP", "WE", "YH", "MP", "B...
## $ INC_CRC    <chr> "59D1ADB532F37979", "8298D32B826A5881", "20CC8109497AD9F...
## $ FMEL_UPD_D <chr> "20150916151419", "20150916151419", "20150916151419", "2...
## $ geometry   <POLYGON [°]> POLYGON ((103.8059 1.411095..., POLYGON ((103.91...

Load the 2020 electoral boundary SHP file.

# load the 2020 shp file
mpeb_2020 <- st_read(dsn = "electoral_boundaries_2020", 
                layer = "electoral-boundary-dataset-polygon")
## Reading layer `electoral-boundary-dataset-polygon' from data source `C:\smu\Year 3 Sem 1\Visual Analytics\Assignments (Makeovers)\Assignment 5\A5\electoral_boundaries_2020' using driver `ESRI Shapefile'
## Simple feature collection with 31 features and 7 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: 103.6057 ymin: 1.158762 xmax: 104.0885 ymax: 1.470783
## geographic CRS: WGS 84
glimpse(mpeb_2020)
## Rows: 31
## Columns: 8
## $ Name       <chr> "TANJONG PAGAR", "JALAN BESAR", "PIONEER", "YUHUA", "MAR...
## $ descriptio <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
## $ altitudeMo <chr> "clampToGround", "clampToGround", "clampToGround", "clam...
## $ ED_CODE    <chr> "TP", "JB", "PI", "YH", "MR", "HG", "MA", "WE", "BP", "Y...
## $ FID        <chr> "2", "3", "5", "7", "12", "14", "10", "11", "18", "19", ...
## $ ED_DESC    <chr> "TANJONG PAGAR", "JALAN BESAR", "PIONEER", "YUHUA", "MAR...
## $ Field_1    <chr> "TANJONG PAGAR", "JALAN BESAR", "PIONEER", "YUHUA", "MAR...
## $ geometry   <MULTIPOLYGON [°]> MULTIPOLYGON (((103.8458 1...., MULTIPOLYGO...

Capitalize the first letter of the constituencies.

mpeb_2020$Name <- str_to_title(mpeb_2020$Name)
glimpse(mpeb_2020)
## Rows: 31
## Columns: 8
## $ Name       <chr> "Tanjong Pagar", "Jalan Besar", "Pioneer", "Yuhua", "Mar...
## $ descriptio <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
## $ altitudeMo <chr> "clampToGround", "clampToGround", "clampToGround", "clam...
## $ ED_CODE    <chr> "TP", "JB", "PI", "YH", "MR", "HG", "MA", "WE", "BP", "Y...
## $ FID        <chr> "2", "3", "5", "7", "12", "14", "10", "11", "18", "19", ...
## $ ED_DESC    <chr> "TANJONG PAGAR", "JALAN BESAR", "PIONEER", "YUHUA", "MAR...
## $ Field_1    <chr> "TANJONG PAGAR", "JALAN BESAR", "PIONEER", "YUHUA", "MAR...
## $ geometry   <MULTIPOLYGON [°]> MULTIPOLYGON (((103.8458 1...., MULTIPOLYGO...

4.1 Generate the map by percentage of votes for the winning parties - PAP & WP

2015 Map
To generate the map representing the electoral boundaries in 2015, the steps are as follows.

Data Wrangling:
1. Filter the original dataframe, results to include only results for 2015.
2. Combine the map dataframe to the filtered results dataframe and name it “map_2015”.
3. Split “map_2015” by the winning parties. “map_2015” will be split into two since there are only 2 winning parties (PAP & WP). Name them “map_2015_pap” & “map_2015_wp”.

Map:
1. Construct the map by calling tm_shape and setting the data to use to “map_2015_pap”. The tm_fill for this is set to “Blues”. Functions tm_shape and tm_fill are from the tmap package.
2. Add another tm_shape and tm_fill for map_2015_wp. Set tm_shape’s data to “map_2015_wp” and 3. The map is now filled according to the vote percentage.
4. Make cosmetic changes e.g. adjust legend size, plot title, remove the frame of the map, etc.
5. Set tmap_mode("view") to make the map interactive.

# filter the original dataframe for 2015 results
results2015 <- results %>%
  filter(year == 2015)

glimpse(results2015)
## Rows: 61
## Columns: 8
## $ year              <dbl> 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2...
## $ constituency      <chr> "Aljunied", "Aljunied", "Ang Mo Kio", "Ang Mo Kio...
## $ constituency_type <chr> "GRC", "GRC", "GRC", "GRC", "GRC", "GRC", "SMC", ...
## $ candidates        <chr> "Chen Show Mao | Sylvia Lim | Low Thia Khiang | M...
## $ party             <chr> "WP", "PAP", "PAP", "RP", "PAP", "SPP", "PAP", "S...
## $ vote_count        <dbl> 70050, 67424, 135316, 36758, 86701, 31108, 18234,...
## $ vote_percentage   <dbl> 50.96, 49.04, 78.64, 21.36, 73.59, 26.41, 73.02, ...
## $ winner            <chr> "WP", NA, "PAP", NA, "PAP", NA, "PAP", NA, NA, NA...
# Combine map (SHP file) with the 2015 results 
 map_2015 <- mpeb_2015 %>% 
  left_join(results2015, by = c("ED_DESC" = "constituency"))

glimpse(map_2015)
## Rows: 59
## Columns: 14
## $ ED_DESC           <chr> "Holland-Bukit Timah", "Holland-Bukit Timah", "Pu...
## $ Name              <chr> "kml_3", "kml_3", "kml_6", "kml_6", "kml_7", "kml...
## $ descriptio        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
## $ ED_CODE           <chr> "HT", "HT", "PE", "PE", "PI", "PI", "PS", "PS", "...
## $ INC_CRC           <chr> "59D1ADB532F37979", "59D1ADB532F37979", "8298D32B...
## $ FMEL_UPD_D        <chr> "20150916151419", "20150916151419", "201509161514...
## $ year              <dbl> 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2...
## $ constituency_type <chr> "GRC", "GRC", "SMC", "SMC", "SMC", "SMC", "SMC", ...
## $ candidates        <chr> "Chee Soon Juan| Chong Wai Fung | Md Sidek Bin Ma...
## $ party             <chr> "SDP", "PAP", "PAP", "WP", "PAP", "NSP", "SPP", "...
## $ vote_count        <dbl> 31494, 62786, 16977, 15818, 18017, 5581, 5368, 10...
## $ vote_percentage   <dbl> 33.40, 66.60, 51.77, 48.23, 76.35, 23.65, 33.61, ...
## $ winner            <chr> NA, "PAP", "PAP", NA, "PAP", NA, NA, "PAP", NA, "...
## $ geometry          <POLYGON [°]> POLYGON ((103.8059 1.411095..., POLYGON (...
# Split the map by the winners, PAP and WP
map_2015_pap <- map_2015 %>% 
  filter(winner == "PAP")

map_2015_wp <- map_2015 %>% 
  filter(winner == "WP")

glimpse(map_2015_pap)
## Rows: 26
## Columns: 14
## $ ED_DESC           <chr> "Holland-Bukit Timah", "Punggol East", "Pioneer",...
## $ Name              <chr> "kml_3", "kml_6", "kml_7", "kml_9", "kml_12", "km...
## $ descriptio        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
## $ ED_CODE           <chr> "HT", "PE", "PI", "PS", "SW", "TP", "WE", "YH", "...
## $ INC_CRC           <chr> "59D1ADB532F37979", "8298D32B826A5881", "20CC8109...
## $ FMEL_UPD_D        <chr> "20150916151419", "20150916151419", "201509161514...
## $ year              <dbl> 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2...
## $ constituency_type <chr> "GRC", "SMC", "SMC", "SMC", "SMC", "GRC", "GRC", ...
## $ candidates        <chr> "Christopher De Souza | Liang Eng Hwa | Sim Ann |...
## $ party             <chr> "PAP", "PAP", "PAP", "PAP", "PAP", "PAP", "PAP", ...
## $ vote_count        <dbl> 62786, 16977, 18017, 10602, 17586, 90635, 71214, ...
## $ vote_percentage   <dbl> 66.60, 51.77, 76.35, 66.39, 62.13, 77.71, 78.57, ...
## $ winner            <chr> "PAP", "PAP", "PAP", "PAP", "PAP", "PAP", "PAP", ...
## $ geometry          <POLYGON [°]> POLYGON ((103.8059 1.411095..., POLYGON (...
# Construct the 2015 map
map_2015 <- tm_shape(map_2015_pap) +
  tm_fill("vote_percentage", 
          title = "Percentage of PAP votes (%)",
            style = "fixed", 
            breaks = c(0,60,70,100),
          palette = "Blues") +
  tm_text("ED_DESC", size = 0.7, col = "black", remove.overlap = T)+
  tm_borders(alpha = 0.5, lwd = 1.5) +
  
    tm_shape(map_2015_wp) +
    tm_fill("vote_percentage",
            title = "Percentage of WP votes (%)",
            style = "fixed", 
            breaks = c(0,60,70,100),
            palette = "Reds") +
    tm_text("ED_DESC", size = 0.7, col = "black", remove.overlap = T) +
    tm_borders(alpha = 0.5, lwd = 2) +

   tm_layout(main.title = "Results of the 2015 General Elections (PAP vs WP)",
            main.title.position = "center",
            main.title.size = 1,
            legend.outside = FALSE,
            legend.height = 1, 
            legend.width = 1,
            legend.outside.position = c("left", "bottom"),
            legend.title.size = 0.85,
            legend.text.size = 0.7,
            frame = FALSE) 

# set the tmap_mode to view to make the map interactive
tmap_mode("view")
map_2015



2020 Map
Follow the steps above but ensure that it uses the 2020 dataframes to generate the 2020 map.

# filter the original dataframe for 2020 results
results2020 <- results %>%
  filter(year == 2020)
glimpse(results2020)
## Rows: 64
## Columns: 8
## $ year              <dbl> 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2...
## $ constituency      <chr> "ALJUNIED", "ALJUNIED", "ANG MO KIO", "ANG MO KIO...
## $ constituency_type <chr> "GRC", "GRC", "GRC", "GRC", "GRC", "GRC", "SMC", ...
## $ candidates        <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...
## $ party             <chr> "PAP", "WP", "PAP", "RP", "PAP", "SPP", "PAP", "S...
## $ vote_count        <dbl> 57330, 85815, 124597, 48677, 62983, 30696, 15500,...
## $ vote_percentage   <dbl> 40.05, 59.95, 71.91, 28.09, 67.23, 32.77, 54.80, ...
## $ winner            <chr> NA, "WP", "PAP", NA, "PAP", NA, "PAP", NA, "PAP",...
# Combine map (SHP file) with the 2020 results 
map_2020 <- mpeb_2020 %>% 
  left_join(results2020, by = c("ED_DESC" = "constituency"))
glimpse(map_2020)
## Rows: 64
## Columns: 15
## $ Name              <chr> "Tanjong Pagar", "Tanjong Pagar", "Jalan Besar", ...
## $ descriptio        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
## $ altitudeMo        <chr> "clampToGround", "clampToGround", "clampToGround"...
## $ ED_CODE           <chr> "TP", "TP", "JB", "JB", "PI", "PI", "PI", "YH", "...
## $ FID               <chr> "2", "2", "3", "3", "5", "5", "5", "7", "7", "12"...
## $ ED_DESC           <chr> "TANJONG PAGAR", "TANJONG PAGAR", "JALAN BESAR", ...
## $ Field_1           <chr> "TANJONG PAGAR", "TANJONG PAGAR", "JALAN BESAR", ...
## $ year              <dbl> 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2...
## $ constituency_type <chr> "GRC", "GRC", "GRC", "GRC", "SMC", "SMC", "SMC", ...
## $ candidates        <chr> "na", "na", "na", "na", "na", "na", "na", "na", "...
## $ party             <chr> "PAP", "PSP", "PAP", "PV", "PAP", "PSP", "Indepen...
## $ vote_count        <dbl> 78330, 45807, 64631, 34261, 14593, 8289, 655, 141...
## $ vote_percentage   <dbl> 63.10, 36.90, 65.36, 34.64, 62.00, 35.22, 2.78, 7...
## $ winner            <chr> "PAP", NA, "PAP", NA, "PAP", NA, NA, "PAP", NA, "...
## $ geometry          <MULTIPOLYGON [°]> MULTIPOLYGON (((103.8458 1...., MULT...
# Split the map by the winners, PAP and WP
map_2020_pap <- map_2020 %>% 
  filter(winner == "PAP")

map_2020_wp <- map_2020 %>% 
  filter(winner == "WP")

glimpse(map_2020_wp)
## Rows: 3
## Columns: 15
## $ Name              <chr> "Hougang", "Sengkang", "Aljunied"
## $ descriptio        <chr> NA, NA, NA
## $ altitudeMo        <chr> "clampToGround", "clampToGround", "clampToGround"
## $ ED_CODE           <chr> "HG", "SK", "AJ"
## $ FID               <chr> "14", "20", "17"
## $ ED_DESC           <chr> "HOUGANG", "SENGKANG", "ALJUNIED"
## $ Field_1           <chr> "HOUGANG", "SENGKANG", "ALJUNIED"
## $ year              <dbl> 2020, 2020, 2020
## $ constituency_type <chr> "SMC", "GRC", "GRC"
## $ candidates        <chr> "na", "na", "na"
## $ party             <chr> "WP", "PAP", "WP"
## $ vote_count        <dbl> 15451, 55319, 85815
## $ vote_percentage   <dbl> 61.21, 47.88, 59.95
## $ winner            <chr> "WP", "WP", "WP"
## $ geometry          <MULTIPOLYGON [°]> MULTIPOLYGON (((103.8855 1...., MULT...
# Construct the 2020 map
map_2020 <- tm_shape(map_2020_pap) +
  tm_fill("vote_percentage", 
          title = "Percentage of PAP votes (%)",
            style = "fixed", 
            breaks = c(0,60,70,100),
          palette = "Blues") +
  tm_text("ED_DESC", size = 0.7, col = "black", remove.overlap = T)+
  tm_borders(alpha = 0.5, lwd = 1.5) +
  
    tm_shape(map_2020_wp) +
    tm_fill("vote_percentage",
            title = "Percentage of WP votes (%)",
            style = "fixed", 
            breaks = c(0,60,70,100),
            palette = "Reds") +
    tm_text("ED_DESC", size = 0.7, col = "black", remove.overlap = T) +
    tm_borders(alpha = 0.5, lwd = 2) +

   tm_layout(main.title = "Results of the 2020 General Elections (PAP vs WP)",
            main.title.position = "center",
            main.title.size = 1,
            legend.outside = FALSE,
            legend.height = 1, 
            legend.width = 1,
            legend.outside.position = c("left", "bottom"),
            legend.title.size = 0.85,
            legend.text.size = 0.7,
            frame = FALSE) 
map_2020

Arrange the 2 maps side-by-side for easier comparison.

tmap_arrange(map_2015, map_2020, ncol=2)



4.2 Bar graph - Compare the percentage of votes across contesting parties

Next, I look at the percentage of votes across all the parties that ran for the General Elections (G.E.) in 2015 and 2020. The following steps explain how this is done.

Data Wrangling:
1. Using the results2015 dataframe, I sum the total of the vote_count column, it is named total_votes_2015.
2. I then create a new dataframe, total_votes_2015. It contains values from the results2015 dataframe in which the records are grouped by ‘year’ and ‘party’. Using the total_votes_2015 variable, I calculate the percentage of votes across all parties that ran in the 2015 G.E.
3. Steps 1 and 2 are repeated for the year 2020.
4. Combine the 2 new dataframes together using bind_rows, name it ‘votes_2015_2020_df’.

Graph:
1. Using geom_col (which comes from the ggplot2 package), set the data to votes_2015_2020_df, the x-axis to “percentage” and the y-axis to “party”. To sort the parties in ascending order (most votes on top), I use the reorder function.
2. Use facet_wrap to generate 2 graphs, one for 2015 and another for 2020.
3. Apply plotly() to the graph to make it interactive.
3. Make cosmetic changes e.g. the information in the tooltip when the cursor hovers over the bars, size of axis labels, etc.

Filter the original dataframe, “results” to keep only the results in 2015 & 2020.

# For 2015
total_votes_2015 <- sum(results2015$vote_count) 

votes_2015_df <- results2015 %>%
  group_by(year, party) %>%
  summarise(percentage = round(sum(vote_count)/total_votes_2015*100, digits = 2))

# For 2020
total_votes_2020 <- sum(results2020$vote_count) 

votes_2020_df <- results2020 %>%
  group_by(year, party) %>%
  summarise(percentage = round(sum(vote_count)/total_votes_2020*100, digits = 2))
# Combine the 2 dataframes
votes_2015_2020_df <- votes_2015_df %>%
  bind_rows(votes_2020_df)

glimpse(votes_2015_2020_df)
## Rows: 22
## Columns: 3
## Groups: year [2]
## $ year       <dbl> 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2015, 2015, 20...
## $ party      <chr> "Independent", "NSP", "PAP", "PPP", "RP", "SDA", "SDP", ...
## $ percentage <dbl> 0.12, 3.53, 69.86, 1.13, 2.63, 2.06, 3.76, 2.25, 2.17, 1...
# Construct the bar graph
votes_plot <- ggplot(votes_2015_2020_df, 
       aes(percentage, 
           reorder(party,percentage))) +
  geom_col(fill = "#9B5540",
           aes(text=party)) + 
  facet_wrap(~year) +
  theme_bw(base_size=11) +
  theme(strip.text = element_text(size=10),
        plot.title = element_text(hjust = 0.5)) +
  labs(title = "Percentage of Votes for Parties that Campaigned in 2015 and 2020", 
       x="Percentage of Votes(%)",
       y="Parties\n") 

ggplotly(votes_plot, tooltip = c("percentage", "party"))

5. Insights Gleaned from the Visualizations

  1. Using the side-by-side map to compare the percentage of votes for PAP in 2015 to the percentage of votes they received in 2020. It shows that the ruling party(PAP) is getting lesser votes across multiple constituencies. Though boundaries have been redrawn, areas that remained mostly unchanged like Choa Chu Kang, Bukit Batok, East Coast etc, have seen a decline in PAP votes. In 2015, the percentage of PAP votes are more than 70% but in 2020, the votes were less than 70%.

  2. WP, the strongest opposition of the ruling party in recent years, have seen major strides in gaining the citizens’ trust. In the Hougang SMC, there was an increase in votes for the WP from less than 60% in 2015 to more than 60% in 2020. Additionally, the newly drawn boundary - Sengkang (refer to 2020 map) which consists of Punggol East, parts of Sengkang West and Pasir Ris-Punggol (refer to 2015 map), became WP supporters when they were originally PAP supporters.

  3. Mousing over the bars of PAP in the 2015 and 2020 bar graphs, we can see that the percentage of votes for PAP have decreased by approximately 8.5%. This means that more citizens are willing to vote for opposition parties. Parties like SDP and NSP have seen higher percentage of votes while parties like WP, SPP, SDA have lower percentage of votes in 2020 as compared to 2015.
    It might be surprising that WP has seen a decrease in votes even though in Insight 2, there was an improvement in percentage of votes for certain constituency. A possible reason for this could be that the party have contested for fewer constituencies in 2020 than in 2015. Instead for contesting in many areas, they have focused on a few constituencies and put in more effort in these areas to convince the citizens that they are worth voting for.