Kuntavaalien tulokset ja geofi-paketin geofacet-datat

Luokat: data

Kuntavaalit on käyty ja ääntenlaskenta on edennyt niin että Oikeusministeriö on julkaissut ladattavat tulostiedostot osoitteessa: tulospalvelu.vaalit.fi/KV-2021/fi/ladattavat_tiedostot.html.

Tässä blogissa esittelen R-kielen geofi-paketin käteviä geofacet- ja ggplot2-pakettien kanssa yhteensopivia maantieteellista aluejakoa jäljitteleviä matriiseja. Blogin esimerkissä aluejakomatriisien avulla esitetään kuntavaalien puoluekohtaisia ääniosuuksia sekä maakuntien tasolla että kuntatasolla maakunnittain.

Ensisijainen tapa esittää jonkun muuttujan jakaumaa eri alueilla on kartta. Vaalikarttojen merkittävin ongelma on useamman muuttujan kuten monen puolueen kannatuksen samanaikainen esittämisen vaikeus. Vuorovaikutteiset internet-teknologiat voivat olla toki osittaiseksi avuksi. Toinen ongelma liittyy alueiden suhteellisiin kokoihin: isot vähäväkiset kunnat täyttävät pinta-alansa verran kuvasta, vaikka vaaleissa äänestävät ihmiset, eivät hehtaarit.

geofacet:it ovat hyödyksi kun halutaan esittää alueisiin liittyvää tietoa niin että esitystapa noudattelee suurpiirteisesti alueiden maantieteellista suhdetta toisiinsa. Tässä esimerkissä siis lähellä olevien alueiden kannatusluvut ovat kuvioissa lähempänä toisiaan kuin kauempana olevien. Esimerkkinä alla Tampereen seudun kunnat.

Alla on lähdekoodi koko analyysin toteuttamiseen. Vaalitusdatan käsittelyn ja maantieteellisen asettelun lisäksi siellä on muutama muu ggplot2-kikka, jotka laitan tänne itselle muistiin.

Klikkaa kuva isommaksi!

library(dplyr)
library(glue)
library(ggplot2)
library(tidyr)
library(readr)
library(geofi)
library(sf)
library(geofacet)
library(janitor)
library(rvest)
library(stringr)
library(ggnewscale)
library(extrafont)


df_puolueet <- tribble(
~puolue_nimi, ~puolue_lyhenne, ~vari,
"Vasemmistoliitto", "Vas.", "#8c1212",
"Sininen tulevaisuus", "Sin.", "#031d6c",
"Vihreä liitto", "Vihr.", "#c2da7f",
"Suomen Sosialidemokraattinen Puolue", "SDP", "#f4151f",
"Suomen ruotsalainen kansanpuolue", "RKP", "#ffae18",
"Suomen Kristillisdemokraatit (KD)", "KD", "#1968c0",
"Kristallipuolue", "Kristall.", "#882b9f",
"Feministinen puolue", "FP", "#e94786",
"Eläinoikeuspuolue", "EOP", "#fab92b",
"Piraattipuolue", "PP", "#660099",
"Perussuomalaiset", "PS", "#88b6f0",
"Liike Nyt", "Liik.", "#be0078",
"Kansallinen Kokoomus", "Kok.", "#02577d",
"Suomen Keskusta", "Kesk.", "#286008",
"muut", "muut", "#ffb3ec"
)

library(htmlTable)

where <- cbind(c(1:nrow(df_puolueet)),
      rep(3, nrow(df_puolueet)))
style <- as.character(glue('background-color: {df_puolueet$vari}; color: black;'))

css.cell <- matrix('', nrow(df_puolueet), ncol(df_puolueet))
css.cell[where] <- style
htmlTable(df_puolueet, css.cell = css.cell, rnames = FALSE)
puolue_nimi puolue_lyhenne vari
Vasemmistoliitto Vas. #8c1212
Sininen tulevaisuus Sin. #031d6c
Vihreä liitto Vihr. #c2da7f
Suomen Sosialidemokraattinen Puolue SDP #f4151f
Suomen ruotsalainen kansanpuolue RKP #ffae18
Suomen Kristillisdemokraatit (KD) KD #1968c0
Kristallipuolue Kristall. #882b9f
Feministinen puolue FP #e94786
Eläinoikeuspuolue EOP #fab92b
Piraattipuolue PP #660099
Perussuomalaiset PS #88b6f0
Liike Nyt Liik. #be0078
Kansallinen Kokoomus Kok. #02577d
Suomen Keskusta Kesk. #286008
muut muut #ffb3ec
muni <- municipality_key_2021 %>% 
  select(municipality_name_fi,municipality_code,maakunta_name_fi)
flie1 <- tempfile()
flie2 <- tempfile()
download.file("https://tulospalvelu.vaalit.fi/KV-2021/kv-2021_puo_maa.csv.zip", flie1) # 2021
download.file("https://tulospalvelu.vaalit.fi/KV-2017/kv-2017_puo_maa.csv.zip", flie2) # 2017
tmpdir <- tempdir()
unzip(zipfile = flie1, exdir = tmpdir)
unzip(zipfile = flie2, exdir = tmpdir)
raw21 <- read_csv2(glue("{tmpdir}/kv-2021_apa_maa.csv"), col_names = FALSE)
raw17 <- read_csv2(glue("{tmpdir}/kv-2017_tpat_maa.csv"), col_names = FALSE)

dat_raw <- bind_rows(raw17 %>% 
                           mutate(vuosi = 2017) %>% 
                           select(-X52),
                         raw21 %>% 
                           mutate(vuosi = 2021) %>% 
                           select(-X52)) %>% 
  filter(grepl("\\*", X5),
         !grepl("\\*", X3)) %>%
    # äänimäärä ja ehdokasnumero kokonaisluvuiksi
  mutate(X3 = as.integer(X3),
         X35 = as.integer(X35),
         X39 = as.integer(X39),
         X40 = as.integer(X40),
         X41 = as.integer(X41)) %>% #count(X14) 
  # vaalipiirit veks
  filter(!is.na(X3)) %>%
  # Merkistöenkoodaukset
  mutate(
    X17 = iconv(x = X17, from = "Windows-1252", to = "UTF-8"),
    X11 = iconv(x = X11, from = "Windows-1252", to = "UTF-8"),
    X19 = iconv(x = X19, from = "Windows-1252", to = "UTF-8"),
    X18 = iconv(x = X18, from = "Windows-1252", to = "UTF-8"),
    X16 = iconv(x = X16, from = "Windows-1252", to = "UTF-8"),
    X15 = iconv(x = X15, from = "Windows-1252", to = "UTF-8"),
    X14 = iconv(x = X14, from = "Windows-1252", to = "UTF-8"),
    X12 = iconv(x = X12, from = "Windows-1252", to = "UTF-8")
    ) %>% 
  select(vuosi,X3,X6,X11,X14,X16,X39,X40,X41,X15,X12) %>% 
  ungroup()

df_kannatus <- dat_raw  %>% 
  select(vuosi,X3,X16,X41) %>% 
  rename(aanet = X41,
         municipality_code = X3
         ) %>% 
  left_join(muni) %>% 
  rename(ehdokasasettaja = X16) %>% 
  mutate(ehdokasasettaja = ifelse(ehdokasasettaja %in% df_puolueet$puolue_nimi, ehdokasasettaja, "muut")) %>% 
  group_by(vuosi,municipality_name_fi,ehdokasasettaja,municipality_code,maakunta_name_fi) %>% 
  summarise(aanet = sum(aanet, na.rm = TRUE)) %>% 
  ungroup()

Koko Suomi maakunnittain

reorder_within <- function(x, by, within, fun = mean, sep = "___", ...) {
  new_x <- paste(x, within, sep = sep)
  stats::reorder(new_x, by, FUN = fun)
}
scale_x_reordered <- function(..., sep = "___") {
  reg <- paste0(sep, ".+$")
  ggplot2::scale_x_discrete(labels = function(x) gsub(reg, "", x), ...)
}

df_kannatus_maakunta <-
  df_kannatus %>% 
  group_by(maakunta_name_fi,vuosi) %>% 
  # maakuntatason kannatus
  mutate(aanet_maakunta = sum(aanet, na.rm = TRUE)) %>% 
  ungroup() %>% 
  group_by(maakunta_name_fi,vuosi,ehdokasasettaja) %>% 
  # maakuntatason kannatus
  mutate(aanet_maakunta_puolue = sum(aanet, na.rm = TRUE),
         kannatus = round(aanet_maakunta_puolue/aanet_maakunta * 100,1)) %>% 
  ungroup() %>% 
  left_join(df_puolueet, by = c("ehdokasasettaja" = "puolue_nimi")) %>% 
  mutate(puolue_lyhenne = factor(puolue_lyhenne, df_puolueet$puolue_lyhenne)) %>% 
    # pudotetaan kunnat
  distinct(vuosi,
           ehdokasasettaja,
           maakunta_name_fi,
           aanet_maakunta,
           aanet_maakunta_puolue,
           kannatus,puolue_lyhenne) %>% 
  filter(!is.na(ehdokasasettaja)) %>% 
  ungroup() %>% 
  # kannatuksen vuosimuutos
  arrange(maakunta_name_fi,ehdokasasettaja,vuosi) %>% 
  group_by(maakunta_name_fi,ehdokasasettaja) %>% 
  mutate(kannatus_ero = round(kannatus - lag(kannatus),1),
         kannatus_ero_label = format(kannatus_ero, nsmall = 1),
         kannatus_ero_label = ifelse(kannatus_ero > 0, paste0("+",kannatus_ero_label), kannatus_ero_label)
                                     ) %>%
  filter(vuosi == 2021) %>% 
    ungroup()

ggplot(df_kannatus_maakunta, 
       aes(reorder_within(puolue_lyhenne, -kannatus, maakunta_name_fi), 
           y = kannatus, label = round(kannatus,1))) +
  geom_col(aes(fill = puolue_lyhenne)) +
  geom_text(nudge_y = 2, family = "Space Mono", size = 1.8) +
  scale_fill_manual("kannatus" , values = df_puolueet$vari) +
  facet_geo(~maakunta_name_fi, grid = grid_maakunta, scales = "free_x") + 
  scale_x_reordered() +
  labs(title = "Puolueiden kannatusosuus kuntavaaleissa 2021 maakunnittain",
       subtitle = "Palkin alapuolella prosenttiyksikköinä ero vuoden 2017 vaaleihin") +
  new_scale("fill") +
  geom_label(aes(y = -5, 
                 label = kannatus_ero_label, 
                 fill = kannatus_ero), 
             color = "black",
             family = "Space Mono",
             label.size = 0,
             size =  1.6, 
             # fontface = "bold",
             label.padding = unit(.10, "lines")) +
  scale_fill_distiller("kannatus_ero", palette = "RdYlGn", direction = +1) +
  theme_minimal(base_family = "Space Mono") +
  theme(axis.text.x = element_text(angle = 270, size = 8, hjust  = 0),
        panel.grid = element_blank(),
        legend.position = "Space Mono", 
        axis.text.y = element_blank(), 
        axis.title = element_blank()) -> tmp
ggsave(tmp, filename = "maakunta.png", 
         width = max(grid_maakunta$col)*3, 
         height = max(grid_maakunta$row)*3)

Kannatusosuudet kunnittain

df_kannatus_kunta <- df_kannatus %>% #filter(X3 == 91, vuosi == 2021)
  group_by(municipality_name_fi,municipality_code,vuosi) %>% 
  # maakuntatason kannatus
  mutate(aanet_kunta = sum(aanet, na.rm = TRUE)) %>% 
  ungroup() %>% 
  group_by(vuosi) %>% 
  # maakuntatason kannatus
  mutate(kannatus = round(aanet/aanet_kunta * 100,1)) %>% 
  ungroup() %>% #filter(vuosi == 2021)
  left_join(df_puolueet, by = c("ehdokasasettaja" = "puolue_nimi")) %>% 
  mutate(puolue_lyhenne = factor(puolue_lyhenne, df_puolueet$puolue_lyhenne)) %>% 
  filter(!is.na(ehdokasasettaja)) %>% 
  ungroup() %>% 
  # kannatuksen vuosimuutos
  arrange(municipality_name_fi,ehdokasasettaja,vuosi) %>% 
  group_by(municipality_name_fi,ehdokasasettaja) %>% 
    mutate(kannatus_ero = round(kannatus - lag(kannatus),1),
         kannatus_ero_label = format(kannatus_ero, nsmall = 1),
         kannatus_ero_label = ifelse(kannatus_ero > 0, paste0("+",kannatus_ero_label), kannatus_ero_label)
                                     ) %>%
  filter(vuosi == 2021) %>% 
    ungroup() #%>% filter(X3 == 91)

maakunnat <- geofi::grid_maakunta$name %>% .[. != "Ahvenanmaa"]
for (i in seq_along(maakunnat)){

df_kannatus_kunta_tmp <- df_kannatus_kunta[df_kannatus_kunta$maakunta_name_fi %in% maakunnat[i],]
griddi <- get(paste0("grid_", make_clean_names(tolower(maakunnat[i]))))

# oikeat värit!
varit <- df_puolueet[df_puolueet$puolue_lyhenne %in% unique(df_kannatus_kunta_tmp$puolue_lyhenne), ]$vari

ggplot(df_kannatus_kunta_tmp,
       aes(reorder_within(puolue_lyhenne, -kannatus, municipality_name_fi),
           y = kannatus, fill = puolue_lyhenne, label = round(kannatus,1))) +
  geom_col() +
  geom_text(nudge_y = 4, family = "Space Mono", size = 1.8) +
  scale_fill_manual(values = varit) +
  facet_geo(~municipality_name_fi, 
            grid = griddi, 
            scales = "free_x") +
  scale_x_reordered() +
   new_scale("fill") +
  geom_label(aes(y = -5, 
                 label = kannatus_ero_label, 
                 fill = kannatus_ero), 
             color = "black",
             family = "Space Mono",
             label.size = 0,
             size =  1.8, 
             # fontface = "bold",
             label.padding = unit(.10, "lines")) +
  scale_fill_distiller("kannatus_ero", palette = "RdYlGn", direction = +1) +
  theme_minimal(base_family = "Space Mono") +
  theme(axis.text.x = element_text(angle = 270, size = 8, hjust  = 0),
        panel.grid = element_blank(),
        legend.position = "Space Mono", 
        axis.text.y = element_blank(), 
        axis.title = element_blank()) +
  labs(title = glue("Puolueiden kannatusosuudet kuntavaaleissa 2021 kunnittain maakunnassa: {maakunnat[i]}"), 
       subtitle = "Palkin alapuolella prosenttiyksikköinä ero vuoden 2017 vaaleihin") -> tmp
  ggsave(tmp, filename = paste0(i,".png"), 
         width = max(griddi$col)*3, 
         height = max(griddi$row)*3)
}
maakunnat <- geofi::grid_maakunta$name %>% .[. != "Ahvenanmaa"]
for (i in seq_along(maakunnat)){
  
  cat("\n\n")
  cat(glue("## {maakunnat[i]}"))
  cat("\n\n")
  
  path <- glue("<a href = '{i}.png'><img src = '{i}.png' width = '350px'></a>\n<br/>\n\n")
  cat(path)
  
}

Lappi


Kainuu


Pohjois-Pohjanmaa


Keski-Pohjanmaa


Pohjanmaa


Etelä-Pohjanmaa


Pohjois-Savo


Keski-Suomi


Pohjois-Karjala


Pirkanmaa


Satakunta


Etelä-Savo


Päijät-Häme


Kanta-Häme


Etelä-Karjala


Kymenlaakso


Varsinais-Suomi


Uusimaa


Katso myös