Dynamiques du chômage dans le Loiret

Analyse géospatiale et statistique — DREETS Centre-Val de Loire

Auteur·rice

Lansana CISSE — Master 2 SISE

Date de publication

mars 2026

Afficher le code
suppressPackageStartupMessages({
  library(tidyverse)
  library(sf)
  library(leaflet)
  library(scales)
  library(readxl)
  library(knitr)
  library(kableExtra)
})

# Charte graphique DREETS
couleurs <- c("Loiret" = "#003189", "CVL" = "#e63946", "France" = "#adb5bd")

# Codes zones d'emploi Loiret (source : INSEE ZE2020_au_01-01-2026)
codes_loiret  <- c("2407", "2409", "2410", "2411")
couleurs_loiret <- c("Gien"      = "#003189", "Montargis" = "#e63946",
                     "Orléans"   = "#f4a261", "Pithiviers" = "#2a9d8f")
f_cvl <- "../data/dares_cvl.xls"
f_ze  <- "../data/dares_zones_emploi.xls"
f_geo <- "../data/georef-france-zone-emploi-2020.geojson"

1 Chargement des données

Afficher le code
library(readxl)
library(sf)

dares_cvl <- read.csv(f_cvl, sep = ";", encoding = "UTF-8")
dares_ze  <- read.csv(f_ze,  sep = ";", encoding = "UTF-8")
zones_geo <- st_read(f_geo, quiet = TRUE)

cat("Dares CVL :", nrow(dares_cvl), "lignes\n")
#> Dares CVL : 5760 lignes
Afficher le code
cat("Dares ZE  :", nrow(dares_ze),  "lignes\n")
#> Dares ZE  : 378550 lignes
Afficher le code
cat("GeoJSON   :", nrow(zones_geo), "zones\n")
#> GeoJSON   : 306 zones

2 Nettoyage des données

Afficher le code
dares_cvl_clean <- dares_cvl %>%
  filter(Variable == "Catégorie A", Année >= 2019) %>%
  mutate(
    Valeur     = as.numeric(Valeur),
    Date       = as.Date(paste0(Date, "-01")),
    territoire = case_when(
      Territoire == "Loiret"                ~ "Loiret",
      Territoire == "CENTRE-VAL DE LOIRE"   ~ "CVL",
      Territoire == "FRANCE MÉTROPOLITAINE" ~ "France",
      TRUE ~ NA_character_
    )
  ) %>%
  filter(!is.na(territoire))

cat("dares_cvl_clean :", nrow(dares_cvl_clean), "lignes\n")
#> dares_cvl_clean : 84 lignes
Afficher le code
# Fonction utilitaire : parser dares_ze brut
# Date au format "YYYY-MM" → paste0("-01") puis as.Date()
parse_ze <- function(df, cat_val, sexe = "Total",
                     age = "Total", anciennete = "Total") {
  df %>%
    filter(
      Catégorie       == cat_val,
      Sexe            == sexe,
      Tranche.d.âge  == age,
      Ancienneté     == anciennete,
      Type.de.données == "Brutes"
    ) %>%
    mutate(
      Date    = as.Date(paste0(Date, "-01"), format = "%Y-%m-%d"),
      code_ze = as.character(Code.zone.d.emploi),
      nb      = as.numeric(gsub("[[:space:]]", "",
                                Nombre.de.demandeurs.d.emploi))
    )
}

dares_ze_clean <- parse_ze(dares_ze, "A") %>%
  select(Date, code_ze, Zone.d.emploi, nb)

dernier_mois <- max(dares_ze_clean$Date, na.rm = TRUE)
cat("Dernier mois :", format(dernier_mois, "%B %Y"), "\n")
#> Dernier mois : février 2026
Afficher le code
cat("Lignes       :", nrow(dares_ze_clean), "\n")
#> Lignes       : 121270
Afficher le code
ze_stat <- dares_ze_clean %>%
  filter(Date == dernier_mois) %>%
  group_by(code_ze, Zone.d.emploi) %>%
  summarise(inscrits_catA = sum(nb, na.rm = TRUE), .groups = "drop") %>%
  mutate(is_loiret = code_ze %in% codes_loiret)

ze_stat %>%
  filter(is_loiret) %>%
  select(`Zone d'emploi` = Zone.d.emploi,
         `Inscrits cat. A` = inscrits_catA) %>%
  kable(caption = paste0("Zones d'emploi Loiret — ",
                         format(dernier_mois, "%B %Y"))) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"),
                full_width = FALSE)
Zones d'emploi Loiret — février 2026
Zone d'emploi Inscrits cat. A
Gien 3500
Montargis 6700
Orléans 20630
Pithiviers 2790
Afficher le code
ze_map <- zones_geo %>%
  filter(reg_code == "24") %>%
  left_join(ze_stat, by = c("ze2020_code" = "code_ze")) %>%
  mutate(is_loiret = ze2020_code %in% codes_loiret)

ze_map_wgs84 <- st_transform(ze_map, 4326)

cat("Zones CVL          :", nrow(ze_map), "\n")
#> Zones CVL          : 15
Afficher le code
cat("Dont Loiret        :", sum(ze_map$is_loiret), "\n")
#> Dont Loiret        : 4
Afficher le code
cat("Sans données stat  :", sum(is.na(ze_map$inscrits_catA)), "\n")
#> Sans données stat  : 0

3 Analyse temporelle — CVL vs Loiret vs France

Afficher le code
dares_cvl_clean %>%
  group_by(territoire) %>%
  arrange(Date) %>%
  mutate(
    base_2019   = Valeur[Date == as.Date("2019-01-01")][1],
    base_2025   = Valeur[Date == as.Date("2025-01-01")][1],
    indice_2019 = (Valeur / base_2019) * 100,
    indice_2025 = (Valeur / base_2025) * 100
  ) %>%
  select(Date, territoire, indice_2019, indice_2025) %>%
  pivot_longer(starts_with("indice"), names_to = "base", values_to = "indice") %>%
  ggplot(aes(x = Date, y = indice, color = territoire)) +
  annotate("rect",
           xmin = as.Date("2020-03-01"), xmax = as.Date("2020-08-01"),
           ymin = -Inf, ymax = Inf, fill = "grey70", alpha = 0.3) +
  annotate("text", x = as.Date("2020-05-15"), y = 125,
           label = "COVID", color = "grey40", size = 3, fontface = "italic") +
  geom_hline(yintercept = 100, linetype = "dashed", color = "grey50") +
  geom_line(linewidth = 1.2) +
  scale_color_manual(values = couleurs) +
  facet_wrap(~ base, labeller = as_labeller(c(
    indice_2019 = "Base 100 = jan. 2019 (pré-COVID)",
    indice_2025 = "Base 100 = jan. 2025 (situation actuelle)"
  ))) +
  labs(x = NULL, y = "Indice", color = NULL,
       caption = "Source : DARES / France Travail — CVS-CJO · L. CISSE") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom", panel.grid.minor = element_blank())

Évolution relative des demandeurs d’emploi cat. A (CVS-CJO). Source : DARES.

4 Évolution par zone d’emploi — Loiret

Afficher le code
dares_ze_clean %>%
  filter(code_ze %in% codes_loiret, Date >= as.Date("2019-01-01")) %>%
  group_by(code_ze, Zone.d.emploi) %>%
  arrange(Date) %>%
  mutate(
    base_jan2019 = nb[Date == as.Date("2019-01-01")][1],
    indice       = (nb / base_jan2019) * 100
  ) %>%
  ungroup() %>%
  ggplot(aes(x = Date, y = indice, color = Zone.d.emploi)) +
  annotate("rect",
           xmin = as.Date("2020-03-01"), xmax = as.Date("2020-09-01"),
           ymin = -Inf, ymax = Inf, fill = "grey70", alpha = 0.25) +
  annotate("text", x = as.Date("2020-06-01"), y = 132,
           label = "COVID", color = "grey40", size = 3, fontface = "italic") +
  geom_vline(xintercept = as.Date("2025-01-01"),
             linetype = "dotted", color = "#e63946", linewidth = 0.8) +
  annotate("text", x = as.Date("2025-02-01"), y = 132,
           label = "Rupture\n2025", color = "#e63946",
           size = 2.8, hjust = 0, fontface = "italic") +
  geom_hline(yintercept = 100, linetype = "dashed", color = "grey50") +
  geom_line(linewidth = 1.1) +
  scale_color_manual(values = couleurs_loiret, name = NULL) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  labs(x = NULL, y = "Indice (base 100 = jan. 2019)",
       caption = "Source : DARES/France Travail · ⚠ Rupture jan. 2025") +
  theme_minimal(base_size = 13) +
  theme(legend.position = "top", panel.grid.minor = element_blank(),
        axis.text.x = element_text(angle = 45, hjust = 1))

Évolution mensuelle des inscrits cat. A, base 100 = janvier 2019.

5 Cartographie — CVL

Afficher le code
ggplot(ze_map) +
  geom_sf(aes(fill = inscrits_catA), color = "white", linewidth = 0.4) +
  geom_sf(data = ze_map %>% filter(is_loiret),
          fill = NA, color = "#003189", linewidth = 1.2) +
  geom_sf_label(aes(label = ze2020_name),
                size = 2.2, fill = "white", alpha = 0.75,
                label.size = 0, check_overlap = TRUE) +
  scale_fill_distiller(palette = "YlOrRd", direction = 1,
                       name = "Inscrits\ncat. A",
                       labels = label_number(big.mark = " "),
                       na.value = "#dddddd") +
  labs(subtitle = paste0(format(dernier_mois, "%B %Y"),
                         " | Contour bleu = Loiret"),
       caption = "Source : DARES / France Travail · L. CISSE") +
  theme_void(base_size = 12) +
  theme(plot.subtitle = element_text(hjust = 0.5, color = "grey50"),
        legend.position = "right")

Inscrits France Travail cat. A par zone d’emploi CVL.

6 Analyse par tranche d’âge

Afficher le code
profils_age <- dares_ze %>%
  filter(
    Catégorie       == "ABC",
    Sexe            == "Total",
    Ancienneté     == "Total",
    Tranche.d.âge  != "Total",
    Type.de.données == "Brutes"
  ) %>%
  mutate(
    Date    = as.Date(paste0(Date, "-01"), format = "%Y-%m-%d"),
    code_ze = as.character(Code.zone.d.emploi),
    nb      = as.numeric(gsub("[[:space:]]", "",
                              Nombre.de.demandeurs.d.emploi))
  ) %>%
  filter(Date == dernier_mois, code_ze %in% ze_stat$code_ze) %>%
  group_by(code_ze, Zone.d.emploi, Tranche.d.âge) %>%
  summarise(nb = sum(nb, na.rm = TRUE), .groups = "drop") %>%
  mutate(is_loiret = code_ze %in% codes_loiret)

profils_age %>%
  mutate(Tranche.d.âge = factor(Tranche.d.âge,
    levels = c("Moins de 25 ans", "25 à 49 ans", "50 ans et plus"))) %>%
  ggplot(aes(x = reorder(Zone.d.emploi, nb), y = nb, fill = Tranche.d.âge)) +
  geom_col(position = "fill") +
  geom_hline(yintercept = 0.5, linetype = "dashed",
             color = "white", linewidth = 0.8) +
  coord_flip() +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  scale_fill_brewer(palette = "Set2", name = "Tranche d'âge") +
  labs(subtitle = format(dernier_mois, "%B %Y"),
       x = NULL, y = "Part (%)",
       caption = "Source : DARES / France Travail · L. CISSE") +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(face = "bold"),
        legend.position = "bottom",
        panel.grid.minor = element_blank())

Structure par âge des inscrits cat. ABC par zone d’emploi CVL.

7 Demande d’emploi longue durée (DELD)

Afficher le code
deld_ze <- dares_ze %>%
  filter(
    Catégorie       == "ABC",
    Sexe            == "Total",
    Tranche.d.âge  == "Total",
    Ancienneté     != "Total",
    Type.de.données == "Brutes"
  ) %>%
  mutate(
    Date    = as.Date(paste0(Date, "-01"), format = "%Y-%m-%d"),
    code_ze = as.character(Code.zone.d.emploi),
    nb      = as.numeric(gsub("[[:space:]]", "",
                              Nombre.de.demandeurs.d.emploi)),
    is_deld = Ancienneté %in% c("1 à 2 ans", "2 ans et plus")
  ) %>%
  filter(Date == dernier_mois, code_ze %in% ze_stat$code_ze) %>%
  group_by(code_ze, Zone.d.emploi) %>%
  summarise(
    total    = sum(nb, na.rm = TRUE),
    deld     = sum(nb[is_deld], na.rm = TRUE),
    pct_deld = deld / total,
    .groups  = "drop"
  ) %>%
  mutate(is_loiret = code_ze %in% codes_loiret)

deld_ze %>%
  ggplot(aes(x = reorder(Zone.d.emploi, pct_deld),
             y = pct_deld, fill = is_loiret)) +
  geom_col() +
  geom_text(aes(label = percent(pct_deld, accuracy = 0.1)),
            hjust = -0.1, size = 3.5, fontface = "bold") +
  coord_flip(clip = "off") +
  scale_y_continuous(labels = percent_format(accuracy = 1),
                     expand = expansion(mult = c(0, 0.15))) +
  scale_fill_manual(values = c("FALSE" = "#e63946", "TRUE" = "#003189"),
                    labels = c("Autre CVL", "Loiret"), name = NULL) +
  labs(subtitle = paste0("Catégorie ABC — ", format(dernier_mois, "%B %Y")),
       x = NULL, y = "Taux DELD",
       caption = "Source : DARES / France Travail · L. CISSE") +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(face = "bold"),
        legend.position = "top",
        panel.grid.minor = element_blank())

Taux d’inscrits cat. ABC depuis ≥ 1 an par zone d’emploi CVL.

8 Analyse par genre

Afficher le code
genre_ze <- dares_ze %>%
  filter(
    Catégorie       == "A",
    Tranche.d.âge  == "Total",
    Ancienneté     == "Total",
    Sexe           %in% c("Hommes", "Femmes"),
    Type.de.données == "Brutes"
  ) %>%
  mutate(
    Date    = as.Date(paste0(Date, "-01"), format = "%Y-%m-%d"),
    code_ze = as.character(Code.zone.d.emploi),
    nb      = as.numeric(gsub("[[:space:]]", "",
                              Nombre.de.demandeurs.d.emploi))
  ) %>%
  filter(Date == dernier_mois, code_ze %in% ze_stat$code_ze) %>%
  group_by(code_ze, Zone.d.emploi, Sexe) %>%
  summarise(nb = sum(nb, na.rm = TRUE), .groups = "drop") %>%
  pivot_wider(names_from = Sexe, values_from = nb) %>%
  mutate(
    total      = Hommes + Femmes,
    pct_femmes = Femmes / total,
    is_loiret  = code_ze %in% codes_loiret
  )

genre_ze %>%
  ggplot(aes(x = reorder(Zone.d.emploi, pct_femmes),
             y = pct_femmes, color = is_loiret)) +
  geom_hline(yintercept = 0.5, linetype = "dashed",
             color = "grey60", linewidth = 0.8) +
  geom_segment(aes(xend = Zone.d.emploi, y = 0.5, yend = pct_femmes),
               linewidth = 0.8) +
  geom_point(size = 4) +
  geom_text(aes(label = percent(pct_femmes, accuracy = 0.1)),
            hjust = -0.35, size = 3.2, fontface = "bold") +
  coord_flip(clip = "off") +
  scale_y_continuous(labels = percent_format(accuracy = 1),
                     limits = c(0.3, 0.68)) +
  scale_color_manual(values = c("FALSE" = "#e63946", "TRUE" = "#003189"),
                     labels = c("Autre CVL", "Loiret"), name = NULL) +
  labs(subtitle = paste0(format(dernier_mois, "%B %Y"),
                         " | référence = 50%"),
       x = NULL, y = "Part femmes",
       caption = "Source : DARES / France Travail · L. CISSE") +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(face = "bold"),
        legend.position = "top",
        panel.grid.minor = element_blank())

Part des femmes parmi les inscrits cat. A par zone d’emploi CVL.

9 Carte interactive enrichie

Afficher le code
# Part jeunes < 25 ans par zone CVL
pct_jeunes_ze <- profils_age %>%
  group_by(code_ze, Zone.d.emploi) %>%
  mutate(total_abc = sum(nb)) %>%
  filter(Tranche.d.âge == "Moins de 25 ans") %>%
  summarise(pct_jeunes = nb / first(total_abc), .groups = "drop")

# Enrichissement du sf object
ze_map_enrichi <- ze_map_wgs84 %>%
  left_join(deld_ze    %>% select(code_ze, pct_deld),
            by = c("ze2020_code" = "code_ze")) %>%
  left_join(genre_ze   %>% select(code_ze, pct_femmes),
            by = c("ze2020_code" = "code_ze")) %>%
  left_join(pct_jeunes_ze %>% select(code_ze, pct_jeunes),
            by = c("ze2020_code" = "code_ze"))

pal_catA <- colorNumeric("YlOrRd", domain = ze_map_enrichi$inscrits_catA,
                         na.color = "#dddddd")
pal_deld  <- colorNumeric("PuRd",  domain = ze_map_enrichi$pct_deld,
                          na.color = "#dddddd")

popup_html <- ~lapply(paste0(
  "<b style='font-size:14px'>", ze2020_name, "</b>",
  ifelse(is_loiret, " <span style='color:#003189'>★</span>", ""),
  "<hr style='margin:4px 0'>",
  "<b>Inscrits cat. A : </b>",
  format(inscrits_catA, big.mark = " "), "<br>",
  "<b>Taux DELD : </b>",
  ifelse(is.na(pct_deld), "n.d.", percent(pct_deld, accuracy = 0.1)), "<br>",
  "<b>Part femmes : </b>",
  ifelse(is.na(pct_femmes), "n.d.", percent(pct_femmes, accuracy = 0.1)), "<br>",
  "<b>Part < 25 ans : </b>",
  ifelse(is.na(pct_jeunes), "n.d.", percent(pct_jeunes, accuracy = 0.1)), "<br>",
  "<small style='color:grey'>", format(dernier_mois, "%B %Y"),
  " — DARES / France Travail</small>"
), htmltools::HTML)

leaflet(ze_map_enrichi) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = 2.0, lat = 47.8, zoom = 8) %>%
  addPolygons(
    group = "Inscrits cat. A",
    fillColor = ~pal_catA(inscrits_catA), fillOpacity = 0.72,
    color = "white", weight = 1,
    highlightOptions = highlightOptions(
      weight = 2.5, color = "#003189",
      fillOpacity = 0.9, bringToFront = TRUE),
    popup = popup_html
  ) %>%
  addPolygons(
    group = "Taux DELD",
    fillColor = ~pal_deld(pct_deld), fillOpacity = 0.72,
    color = "white", weight = 1,
    highlightOptions = highlightOptions(
      weight = 2.5, color = "#e63946",
      fillOpacity = 0.9, bringToFront = TRUE),
    popup = popup_html
  ) %>%
  addPolygons(
    data = ze_map_enrichi %>% filter(is_loiret),
    group = "Contour Loiret",
    fill = FALSE, color = "#003189", weight = 3, opacity = 0.9
  ) %>%
  addLegend("bottomright", pal = pal_catA, values = ~inscrits_catA,
            title = paste0("Inscrits cat. A<br>",
                           format(dernier_mois, "%B %Y")),
            labFormat = labelFormat(big.mark = " "),
            group = "Inscrits cat. A") %>%
  addLegend("bottomleft", pal = pal_deld, values = ~pct_deld,
            title = "Taux DELD",
            labFormat = labelFormat(suffix = "%",
              transform = function(x) round(x * 100, 1)),
            group = "Taux DELD") %>%
  addLayersControl(
    baseGroups    = c("Inscrits cat. A", "Taux DELD"),
    overlayGroups = "Contour Loiret",
    options       = layersControlOptions(collapsed = FALSE)
  )

Survoler une zone pour afficher les indicateurs détaillés.


10 Session R

sessionInfo()
#> R version 4.5.3 (2026-03-11)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=fr_FR.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=fr_FR.UTF-8        LC_COLLATE=fr_FR.UTF-8    
#>  [5] LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=fr_FR.UTF-8   
#>  [7] LC_PAPER=fr_FR.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Europe/Paris
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#>  [1] kableExtra_1.4.0 knitr_1.51       readxl_1.4.5     scales_1.4.0    
#>  [5] leaflet_2.2.3    sf_1.1-0         lubridate_1.9.5  forcats_1.0.1   
#>  [9] stringr_1.6.0    dplyr_1.2.0      purrr_1.2.1      readr_2.2.0     
#> [13] tidyr_1.3.2      tibble_3.3.1     ggplot2_4.0.2    tidyverse_2.0.0 
#> 
#> loaded via a namespace (and not attached):
#>  [1] generics_0.1.4          xml2_1.5.2              class_7.3-23           
#>  [4] KernSmooth_2.23-26      stringi_1.8.7           hms_1.1.4              
#>  [7] digest_0.6.39           magrittr_2.0.4          evaluate_1.0.5         
#> [10] grid_4.5.3              timechange_0.4.0        RColorBrewer_1.1-3     
#> [13] fastmap_1.2.0           cellranger_1.1.0        jsonlite_2.0.0         
#> [16] e1071_1.7-17            DBI_1.3.0               viridisLite_0.4.3      
#> [19] crosstalk_1.2.2         jquerylib_0.1.4         codetools_0.2-20       
#> [22] textshaping_1.0.5       cli_3.6.5               rlang_1.1.7            
#> [25] units_1.0-1             withr_3.0.2             yaml_2.3.12            
#> [28] tools_4.5.3             tzdb_0.5.0              vctrs_0.7.2            
#> [31] R6_2.6.1                proxy_0.4-29            lifecycle_1.0.5        
#> [34] classInt_0.4-11         leaflet.providers_3.0.0 htmlwidgets_1.6.4      
#> [37] pkgconfig_2.0.3         pillar_1.11.1           gtable_0.3.6           
#> [40] glue_1.8.0              Rcpp_1.1.1              systemfonts_1.3.2      
#> [43] xfun_0.57               tidyselect_1.2.1        rstudioapi_0.18.0      
#> [46] farver_2.1.2            htmltools_0.5.9         labeling_0.4.3         
#> [49] svglite_2.2.2           rmarkdown_2.31          compiler_4.5.3         
#> [52] S7_0.2.1