Alue- ja kuntavaalien 2025 tulokset ja geofi-paketin geofacet-datat

data
R
graphics
dataviz
suomeksi
ggplot2
aluevaalit2025
geofi
Tekijä
Julkaistu

13. huhtikuuta 2025

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!

[/kode]
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",
"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.", "#031d6c",
"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
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. #031d6c
Suomen Keskusta Kesk. #286008
muut muut #ffb3ec

Puoluiden kannatus aluevaaleissa hyvinvointialueittain

[/kode]
muni <- municipality_key_2025 %>% 
  select(municipality_name_fi,municipality_code,hyvinvointialue_name_fi,hyvinvointialue_code)
flie1 <- tempfile()
flie2 <- tempfile()
download.file("https://tulospalvelu.vaalit.fi/AV-2022/av-2022_puo_maa.csv.zip", flie1) # 2022
download.file("https://tulospalvelu.vaalit.fi/AV-2025/av-2025_puo_maa.csv.zip", flie2) # 2025
tmpdir <- tempdir()
unzip(zipfile = flie1, exdir = tmpdir)
unzip(zipfile = flie2, exdir = tmpdir)
raw21 <- read_csv2(glue("{tmpdir}/av-2022_tpat_maa.csv"), col_names = FALSE)
raw25 <- read_csv2(glue("{tmpdir}/av-2025_apa_maa.csv"), col_names = FALSE)

dat_raw <- bind_rows(raw25 %>% 
                           mutate(vuosi = 2025) %>% 
                           select(-X52),
                         raw21 %>% 
                           mutate(vuosi = 2022) %>% 
                           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,
           hyvinvointialue_name_fi,
           hyvinvointialue_code) %>% 
  summarise(aanet = sum(aanet, na.rm = TRUE)) %>% 
  ungroup()
[/kode]
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(hyvinvointialue_name_fi,hyvinvointialue_code,vuosi) %>% 
  # maakuntatason kannatus
  mutate(aanet_maakunta = sum(aanet, na.rm = TRUE)) %>% 
  ungroup() %>% 
  group_by(hyvinvointialue_name_fi,hyvinvointialue_code,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,
           hyvinvointialue_name_fi,
           aanet_maakunta,
           aanet_maakunta_puolue,
           kannatus,puolue_lyhenne) %>% 
  filter(!is.na(ehdokasasettaja)) %>% 
  ungroup() %>% 
  # kannatuksen vuosimuutos
  arrange(hyvinvointialue_name_fi,ehdokasasettaja,vuosi) %>% 
  group_by(hyvinvointialue_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 == 2025) %>% 
    ungroup()

ggplot(df_kannatus_maakunta, 
       aes(reorder_within(puolue_lyhenne, -kannatus, hyvinvointialue_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(~hyvinvointialue_name_fi, grid = grid_hyvinvointialue, scales = "free_x") + 
  scale_x_reordered() +
  labs(title = "Puolueiden kannatusosuus kuntavaaleissa 2025 hyvinvointialueittain",
       subtitle = "Palkin alapuolella prosenttiyksikköinä ero vuoden 2021 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 = "hyvinvointialue.png", 
         width = max(grid_hyvinvointialue$col)*3, 
         height = max(grid_hyvinvointialue$row)*3)

Puolueiden kannatus kuntavaaleissa kunnittain

[/kode]
muni <- municipality_key_2025 %>% 
  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-2025/kv-2025_puo_maa.csv.zip", flie2) # 2025
tmpdir <- tempdir()
unzip(zipfile = flie1, exdir = tmpdir)
unzip(zipfile = flie2, exdir = tmpdir)
raw21 <- read_csv2(glue("{tmpdir}/kv-2021_tpat_maa.csv"), col_names = FALSE)
raw25 <- read_csv2(glue("{tmpdir}/kv-2025_apa_maa.csv"), col_names = FALSE)

dat_raw <- bind_rows(raw25 %>% 
                           mutate(vuosi = 2025) %>% 
                           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()
[/kode]
df_kannatus_kunta <- df_kannatus %>% #filter(X3 == 91, vuosi == 2021)
  group_by(maakunta_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 == 2025) %>% 
    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 2025 kunnittain maakunnassa: {maakunnat[i]}"), 
       subtitle = "Palkin alapuolella prosenttiyksikköinä ero vuoden 2022 kuntavaaleihin") -> tmp
  ggsave(tmp, filename = paste0(i,".png"), 
         width = max(griddi$col)*3, 
         height = max(griddi$row)*3)
}
[/kode]
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


Uudelleenkäyttö

Viittaus

BibTeX-viittaus:
@online{kainu2025,
  author = {Kainu, Markus},
  title = {Alue- ja kuntavaalien 2025 tulokset ja geofi-paketin
    geofacet-datat},
  date = {2025-04-13},
  url = {https://markuskainu.fi/posts/2025-04-14-aluevaalit-geofi/},
  langid = {fi}
}
Viitatkaa tähän teokseen seuraavasti:
Kainu, Markus. 2025. “Alue- ja kuntavaalien 2025 tulokset ja geofi-paketin geofacet-datat.” April 13, 2025. https://markuskainu.fi/posts/2025-04-14-aluevaalit-geofi/.