Analyses des résultats électoraux de la circonscription 92-13

Author
Affiliation

Cédric Midoux

BLR

Published

2026-05-09

Import data

Code
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Code
palette_nuances <- c(
    "LEXD" = "black",
    "LDVD" = "royalblue2",
    "LUD" = "royalblue4",
    "LUDI" = "deepskyblue3",
    "LDVC" = "steelblue",
    "LREM" = "orange",
    "LDIV" = "goldenrod2",
    "LDVG" = "orchid",
    "LUG" = "deeppink3",
    "LVEC" = "darkgreen",
    "LFI" = "firebrick2"
)

communes9213 <- c(
    "92002" = "Antony",
    "92014" = "Bourg-la-Reine",
    "92019" = "Châtenay-Malabry",
    "92071" = "Sceaux"
)

url_sig <- "https://www.data.gouv.fr/api/1/datasets/r/f98165a7-7c37-4705-a181-bcfc943edc73"

sig9213 <- sf::st_read(url_sig) |>
    filter(codeDepartement == "92", codeCommune %in% names(communes9213))
Reading layer `f98165a7-7c37-4705-a181-bcfc943edc73' from data source 
  `https://www.data.gouv.fr/api/1/datasets/r/f98165a7-7c37-4705-a181-bcfc943edc73' 
  using driver `GeoJSON'
Simple feature collection with 68806 features and 9 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -61.80984 ymin: -21.38963 xmax: 55.83665 ymax: 51.08899
Geodetic CRS:  WGS 84
Code
df <- arrow::read_parquet(
    "https://opendata.hauts-de-seine.fr/api/explore/v2.1/catalog/datasets/resultats-des-elections-par-bureau-de-vote/exports/parquet?lang=fr&timezone=Europe%2FBerlin",
    col_select = c(
        "election",
        "type",
        "annee",
        "tour",
        "code_circonscription",
        "circonscription",
        "commune",
        "code_insee",
        "code_bureau_vote",
        "inscrits",
        "abstentions",
        "votants",
        "blancs_et_nuls",
        "blancs",
        "nuls",
        "exprimes",
        "num_panneau",
        "candidat_ou_liste",
        "sexe",
        "nom",
        "prenom",
        "voix",
        "code_canton",
        "canton",
        "code_nuance",
        "candidat_ou_liste_nuance",
        "nom_source",
        "prenom_source",
        "code_sigle"
    )
) |>
    mutate(
        across(
            c(
                election,
                type,
                commune,
                code_insee,
                code_bureau_vote,
                candidat_ou_liste,
                code_nuance
            ),
            as_factor
        ),
        annee = as_factor(year(annee))
    )

df_2026 <- df |>
    filter(
        election == "Municipales 2026 - 1er tour",
        code_insee %in% names(communes9213)
    ) |>
    select(
        commune,
        code_insee,
        code_bureau_vote,
        inscrits,
        exprimes,
        voix,
        nom,
        code_nuance
    ) |>
    mutate(
        code_nuance = factor(
            code_nuance,
            levels = names(palette_nuances)
        ),
    )

df_2026 |>
    select(commune, nom, code_nuance) |>
    distinct() |>
    knitr::kable()
commune nom code_nuance
ANTONY EVENNOU LDVG
ANTONY SENANT LDVD
ANTONY PRECETTI LDVD
ANTONY LA SELVE LEXD
ANTONY MAUGER LUG
BOURG-LA-REINE BUREL LEXD
BOURG-LA-REINE WENTZLER LFI
BOURG-LA-REINE ANDRIEUX LDVD
BOURG-LA-REINE BONAZZI LUG
BOURG-LA-REINE DONATH LUDI
BOURG-LA-REINE KHALED LDIV
CHATENAY-MALABRY SEGAUD LUD
CHATENAY-MALABRY GAILLARD LUG
SCEAUX WIETZERBIN LDVG
SCEAUX DESSANGES LDVC
SCEAUX BONTE LDVD
SCEAUX POUPINEL LDIV
SCEAUX BOURDIER LDVD
SCEAUX LAURENT LDVC

Histogramme

Code
p <- df_2026 |>
    summarise(
        voix = sum(voix, na.rm = TRUE),
        inscrits = sum(inscrits, na.rm = TRUE),
        exprimes = sum(exprimes, na.rm = TRUE),
        .by = c(commune, nom, code_nuance)
    ) |>
    ggplot(aes(x = reorder(commune, inscrits), y = voix, fill = code_nuance)) +
    coord_flip() +
    theme_minimal() +
    scale_fill_manual(values = palette_nuances) +
    labs(x = NULL, y = NULL, fill = "Nuance")

p +
    geom_col(color = "grey30") +
    geom_text(
        aes(label = nom),
        position = position_stack(vjust = 0.5),
        angle = 90,
        check_overlap = TRUE,
        color = "white",
        size = 3
    ) +
    labs(
        title = "Voix par nuance",
        subtitle = "Municipales 2026 • 1er tour • Communes 92-13"
    )

Code
p +
    geom_col(position = "fill", color = "grey30") +
    geom_text(
        aes(label = nom),
        position = position_fill(vjust = 0.5),
        angle = 90,
        check_overlap = TRUE,
        color = "white",
        size = 3
    ) +
    scale_y_continuous(labels = scales::label_percent()) +
    labs(
        title = "Répartition des nuances",
        subtitle = "Municipales 2026 • 1er tour • Communes 92-13"
    )

Carte

Code
library(leaflet)

sf_2026G <- df_2026 |>
    filter(nom %in% c("MAUGER", "GAILLARD", "WIETZERBIN", "BONAZZI")) |>
    mutate(
        score = voix / exprimes,
        codeBureauVote = glue::glue(
            "{code_insee}_{str_pad(code_bureau_vote, 4, pad = '0')}"
        )
    ) |>
    left_join(
        sig9213 |> select(codeBureauVote, geometry),
        by = "codeBureauVote"
    ) |>
    sf::st_as_sf()

pal <- colorNumeric(
    palette = "Reds",
    domain = sf_2026G$score,
    na.color = "transparent"
)

leaflet(sf_2026G) |>
    addProviderTiles(providers$CartoDB.Positron) |>
    addPolygons(
        fillColor = ~ pal(score),
        fillOpacity = 0.7,
        weight = 0.3,
        color = "white",
        opacity = 0.8,
        popup = ~ glue::glue(
            "<b>{commune}</b><br>{nom} : {scales::percent(score, accuracy = 0.01, decimal.mark = ',')}"
        )
    ) |>
    addControl(
        html = "Score des candidat·e·s soutenus par le PS<br>Municipales 2026 • 1er tour • Communes 92-13",
        position = "bottomleft"
    )

Evolution 2020-2026

Code
df_evol <- df |>
    filter(
        type == "Municipales",
        tour == "1",
        annee %in% c("2020", "2026"),
        code_insee %in% names(communes9213)
    ) |>
    mutate(
        code_nuance = factor(
            code_nuance,
            levels = names(palette_nuances)
        ),
        codeBureauVote = glue::glue(
            "{code_insee}_{str_pad(code_bureau_vote, 4, pad = '0')}"
        ),
        score = voix / exprimes,
    ) |>
    select(
        type,
        tour,
        annee,
        commune,
        code_insee,
        codeBureauVote,
        inscrits,
        exprimes,
        voix,
        score,
        nom,
        code_nuance
    )

df_evol |>
    summarise(
        voix = sum(voix, na.rm = TRUE),
        inscrits = sum(inscrits, na.rm = TRUE),
        exprimes = sum(exprimes, na.rm = TRUE),
        .by = c(code_insee, nom, code_nuance, annee)
    ) |>
    ggplot(aes(
        x = reorder(code_insee, inscrits),
        y = voix,
        fill = code_nuance
    )) +
    geom_col(position = "fill", color = "grey30") +
    geom_text(
        aes(label = nom),
        position = position_fill(vjust = 0.5),
        angle = 90,
        check_overlap = TRUE,
        color = "white",
        size = 3
    ) +
    coord_flip() +
    facet_wrap(~annee) +
    theme_minimal() +
    scale_fill_manual(values = palette_nuances) +
    labs(x = NULL, y = NULL, fill = "Nuance") +
    scale_x_discrete(labels = communes9213) +
    scale_y_continuous(labels = scales::label_percent()) +
    labs(
        title = "Evolution des nuances entre 2020 et 2026",
        subtitle = "Municipales 2020 & 2026 • 1er tour • Communes 92-13"
    )

Code
nuances_NFP <- c("LDVG", "LUG", "LVEC", "LFI")

sf_evol_nfp <- df_evol |>
    select(-type, -tour, -code_insee) |>
    mutate(
        popup = glue::glue(
            "{annee} - {nom} : {scales::percent(score, accuracy = 0.01, decimal.mark = ',')}"
        )
    ) |>
    filter(code_nuance %in% nuances_NFP, annee %in% c("2020", "2026")) |>
    summarise(
        score_nfp = sum(score, na.rm = TRUE),
        popup = str_c(unique(popup), collapse = "<br>"),
        .by = c(codeBureauVote, annee)
    ) |>
    pivot_wider(
        names_from = annee,
        values_from = c(score_nfp, popup),
        names_sep = "_",
        values_fill = NA
    ) |>
    mutate(
        evolution_nfp = score_nfp_2026 - score_nfp_2020,
        popup = str_c(
            popup_2020,
            "<br>",
            popup_2026
        ),
        .keep = "unused"
    ) |>
    left_join(
        sig9213 |> select(codeBureauVote, geometry),
        by = "codeBureauVote"
    ) |>
    sf::st_as_sf()

palRG <- colorNumeric(
    palette = c("red", "white", "green"),
    domain = c(
        -max(abs(sf_evol_nfp$evolution_nfp), na.rm = TRUE),
        +max(abs(sf_evol_nfp$evolution_nfp), na.rm = TRUE)
    ),
    na.color = "transparent"
)

leaflet(sf_evol_nfp) |>
    addProviderTiles(providers$CartoDB.Positron) |>
    addPolygons(
        fillColor = ~ palRG(evolution_nfp),
        fillOpacity = 0.7,
        weight = 0.3,
        color = "white",
        opacity = 0.8,
        popup = ~ glue::glue(
            "<b>{codeBureauVote} ({ifelse(evolution_nfp>0, '+', '')}{scales::percent(evolution_nfp, accuracy = 0.01, decimal.mark = ',')})</b><br>{popup}"
        )
    ) |>
    addControl(
        html = "Evolution de la somme des candidat·e·s de gauche (LDVG, LUG, LVEC, LFI) <br>Municipales 2020-2026 • 1er tour • Communes 92-13",
        position = "bottomleft"
    ) |>
    addLegend(
        position = "bottomright",
        pal = palRG,
        values = c(
            -max(abs(sf_evol_nfp$evolution_nfp), na.rm = TRUE),
            +max(abs(sf_evol_nfp$evolution_nfp), na.rm = TRUE)
        ),
        title = "Évolution",
        labFormat = labelFormat(suffix = " pts")
    )