Site icon R-bloggers

Election

[This article was first published on r.iresmi.net, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

French and European flags – CC-BY-NC-ND by Ibrahim Ajaja / World Bank

Election maps are hard: Land doesn’t vote, people do !. So most maps made on these occasions (generally choropleths) are possibly misleading.

Figure 1: Carte des communes de France selon la couleur de la liste arrivée en tête aux élections – Le Monde

A solution is a cartogram, however we generally only see the winner of each constituency.

Figure 2: Each municipality was transformed into a dot, with the area of the dot proportional to the number of voters 👉 a more accurate representation of voting patterns – Karim Douieb

So it’s biased again, especially when several parties have very similar scores.

Here I show a cartogram of the main parties (those who got at least one deputy), aggregated by département; so each département has generally 5 dots whose area is proportional to the number of votes.

< section id="setup" class="level2">

Setup

Some packages are needed…

library(tidyverse)
library(sf)
library(xml2)
library(glue)
library(janitor)
library(cartogram)
library(arrow)
library(colorspace)
< section id="data" class="level2">

Data

Provisional results by communes or département are only available in (many) XML files1, so some data wrangling is necessary.

I get the list of département from INSEE to iterate on.

Département polygons come from Adminexpress COG Carto, simplified beforehand with Mapshaper.

dep <- read_csv("https://www.insee.fr/fr/statistiques/fichier/7766585/v_departement_2024.csv") |>
  clean_names()

dep_aex <- read_sf("DEPARTEMENT.shp") |>
  st_transform("EPSG:2154") |> 
  st_make_valid() |>
  clean_names()

And the 101 XML files are scraped from the Ministère de l’intérieur. We get the results for 34,929 communes.

if (!file.exists("resultats.parquet")) {
  extraire_dep <- function(dep, reg,...) {
    message(glue("{dep}"))
    read_xml(glue("https://www.resultats-elections.interieur.gouv.fr/telechargements/EU2024/resultatsT1/{reg}/{dep}/R1{dep}COM.xml")) |>
      as_list() |> 
      pluck("Election", "Departement", "Communes") |> 
      map(as_tibble) |> 
      list_rbind() |> 
      unnest_wider(Tours) |> 
      unnest_wider(Resultats) |> 
      unnest_wider(Mentions) |> 
      unnest_wider(Abstentions) |> 
      rename(n_abstentions = Nombre) |> 
      select(-RapportInscrits) |> 
      unnest_wider(Votants) |> 
      rename(n_votants = Nombre) |> 
      select(-RapportInscrits) |>
      unnest_wider(Blancs) |> 
      rename(n_blancs = Nombre) |> 
      select(-RapportInscrits, -RapportVotants) |>
      unnest_wider(Nuls) |> 
      rename(n_nuls = Nombre) |> 
      select(-RapportInscrits, -RapportVotants) |>
      unnest_wider(Exprimes) |> 
      rename(n_exprimes = Nombre) |> 
      select(-RapportInscrits, -RapportVotants) |>
      unnest_longer(Listes) |> 
      unnest_wider(Listes) |> 
      mutate(across(everything(), unlist))
  }
  
  resultats <- dep |>
    pmap(safely(extraire_dep, quiet = FALSE),
         .progress = "   Import") |> 
    map(~ pluck(.x, "result")) |> 
    list_rbind() |> 
    clean_names() |> 
    mutate(across(c(inscrits:n_exprimes, nb_voix), parse_integer),
           across(c(rapport_exprimes, rapport_inscrits),
                  ~ parse_number(.x, locale = locale(decimal_mark = ",")))) |>
    select(-listes_id)
  
  write_parquet(resultats, "resultats.parquet")
  date_fichier <- Sys.Date()
} else {
  resultats <- read_parquet("resultats.parquet")
  date_fichier <- as.Date(file.info("resultats.parquet")$mtime)
}

Download it here, in parquet format.

< section id="processing" class="level2">

Processing

We aggregate some political parties together after keeping only the parties having at least one deputy, and compute the votes by département (only for metropolitan France, sorry).

nuances_deputes_elus <- tribble(
  ~cod_nua_liste, ~affiliation,     ~couleur,
  "LRN",          "Extrême droite", "tan4",
  "LENS",         "Centre",         "orange3",
  "LUG",          "Gauche",         "violetred3",
  "LFI",          "Gauche",         "violetred3", 
  "LLR",          "Droite",         "lightblue3",
  "LVEC",         "Verts",          "darkgreen",
  "LREC",         "Extrême droite", "tan4")

res_carto <- resultats |> 
  filter(cod_nua_liste %in% nuances_deputes_elus$cod_nua_liste,
         cod_com < "971",
         nb_voix > 0) |> 
  inner_join(nuances_deputes_elus,
             join_by(cod_nua_liste)) |> 
  group_by(affiliation,
           dep = str_sub(cod_com, 1, 2))  |> 
  summarise(nb_voix = sum(nb_voix),
            .groups = "drop")

Several methods are available to make a cartogram; I’ll chose a Dorling cartogram. The {cartogram} package does its hard work:

resultats_dorling <- dep_aex |>
  filter(insee_dep < "971") |> 
  left_join(res_carto,
            join_by(insee_dep == dep)) |> 
  mutate(affiliation = factor(affiliation, 
                              levels = c(
                                "Gauche",        
                                "Verts",    
                                "Centre",        
                                "Droite",        
                                "Extrême droite"))) |> 
  arrange(affiliation, insee_dep) |> 
  cartogram_dorling("nb_voix", k = .6)
< section id="map" class="level2">

Map

And we map the result:

palette_affiliation <- nuances_deputes_elus |> 
  distinct(affiliation, couleur) |> 
  deframe()

dep_aex |>
  filter(insee_dep < "971") |> 
  ggplot() +
  geom_sf(fill = NA, color = "#aaaaaa") +
  geom_sf(data = resultats_dorling,
          aes(fill = affiliation, 
              color = affiliation),
          alpha = 0.7) +
  scale_fill_manual(values = palette_affiliation) +
  scale_color_manual(values = set_names(darken(palette_affiliation, 0.2),
                                        names(palette_affiliation))) +
  labs(title = "Élections europénnes 2024",
       subtitle = "France métropolitaine",
       fill = "",
       color = "",
       caption = glue("données MIOM {date_fichier}
                  filtre sur les nuances ayant élu des députés
                  https://r.iresmi.net/ {Sys.Date()}")) +
  guides(fill = guide_legend(label.position = "bottom")) +
  theme_void() +
  theme(legend.position = "bottom",
        legend.key.width = unit(1.5, "cm"),
        legend.key.height = unit(.5, "cm"),
        legend.text = element_text(size = 8),
        plot.caption = element_text(size = 7))
Figure 3: 2024 European election in France

Not perfect but maybe more faithful…

< !-- -->
< section id="footnotes" class="footnotes footnotes-end-of-document">

Footnotes

  1. It seems that a nicer dataset is now available.↩︎

To leave a comment for the author, please follow the link and comment on their blog: r.iresmi.net.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Exit mobile version