Dynamiques emploi salarié privé par zone d’emploi

EDA Économie — Notebook exploratoire

Synthèse exécutive

Note4 résultats clés — Volet économie

1. Concentration extrême — CR10 = 37,2 %, Gini = 0,625, Palma = 6,0. L’emploi privé est bien plus concentré que la population.

2. Trajectoires divergentes — L’indice 100 base 2014 révèle un écart croissant entre ZE diversifiées (qui accélèrent) et ZE industrielles/agricoles (qui stagnent ou déclinent).

3. COVID : choc universel, reprise sélective — Le glissement 2019→2020 a frappé toutes les tailles de ZE (-0,5% France). Mais 59% des ZE accélèrent post-COVID — les tertiaires et couronnes métropolitaines en tête.

4. Résidentiel ≠ économique — La corrélation idxresid × idxeco n’atteint que 0,33 : des territoires attractifs résidentiellement stagnent économiquement, et inversement.

Données : URSSAF Séquoia (effectifs salariés privés, 2014-2024) · FLORES 2023 (Krugman, Gini) · INSEE RP 2022 (structures sectorielles, taux d’emploi, CSP)

Périmètre : 287 zones d’emploi métropolitaines (hors DOM) · 19,6 M salariés privés (2024)

Hypothèses : H1 Concentration croissante · H2 Effet métropole au-delà de la taille · H3 COVID redistribue vers moyennes/littorales


QE2.1 — Photographie concentration emploi

Les 10 premières ZE concentrent 37,2 % de l’emploi salarié privé — une distribution plus inégale que celle de la population, typique des rendements d’agglomération.

La concentration de l’emploi est structurellement élevée et reflète les économies d’échelle urbaines : le ratio de Palma atteint 6,0 (les 10% les plus grosses ZE emploient 6,0 fois plus que les 40% les plus petites). Le Gini de 0,625 confirme une distribution très asymétrique.

Code
emp_sorted <- sort(ze$emppriv_vol_24[!is.na(ze$emppriv_vol_24)])
n <- length(emp_sorted)
cum_share <- cumsum(emp_sorted) / sum(emp_sorted)
pop_share <- seq_len(n) / n

lorenz_df <- data.frame(pop = c(0, pop_share), emp = c(0, cum_share))
p80_idx <- which.min(abs(pop_share - 0.80))
emp_at_80 <- cum_share[p80_idx] * 100

ggplot(lorenz_df, aes(x = pop, y = emp)) +
  geom_line(color = col_cyan, linewidth = 1.2) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = col_gray) +
  geom_segment(aes(x = 0.80, xend = 0.80, y = 0, yend = cum_share[p80_idx]),
               linetype = "dotted", color = col_orange) +
  geom_segment(aes(x = 0, xend = 0.80, y = cum_share[p80_idx], yend = cum_share[p80_idx]),
               linetype = "dotted", color = col_orange) +
  annotate("text", x = 0.55, y = cum_share[p80_idx] + 0.04,
           label = glue("80% des ZE = {fmt_pct(emp_at_80, 0)} emploi"),
           size = 3.5, color = col_orange, fontface = "bold") +
  annotate("text", x = 0.35, y = 0.85,
           label = glue("Gini = {fmt_fr(gini_emp, 3)}"),
           size = 4, color = col_cyan, fontface = "bold") +
  scale_x_continuous(labels = percent_format(), expand = c(0, 0.01)) +
  scale_y_continuous(labels = percent_format(), expand = c(0, 0.01)) +
  labs(title = "La concentration de l'emploi privé est très marquée",
       subtitle = "Courbe de Lorenz — ZE triées par emploi croissant",
       x = "Part cumulée des ZE", y = "Part cumulée de l'emploi privé",
       caption = make_source("URSSAF Séquoia 2024"))
ggplot(ze %>% filter(!is.na(typo_insee_lib)),
       aes(x = emppriv_vol_24,
           y = reorder(typo_insee_lib, emppriv_vol_24, FUN = median))) +
  geom_boxplot(fill = col_cyan, alpha = 0.5, outlier.size = 1.5) +
  scale_x_log10(labels = label_comma()) +
  labs(title = "Les ZE métropolitaines dominent en volume",
       subtitle = "Emploi salarié privé 2024 — échelle logarithmique",
       x = "Emploi salarié privé (échelle log)", y = NULL,
       caption = make_source("URSSAF Séquoia 2024"))
Figure 1
Figure 2

Gauche : courbe de Lorenz — plus la courbe s’éloigne de la diagonale, plus la concentration est forte. Droite : boxplots en échelle log — les ZE à fonctions métropolitaines (17 ZE) concentrent les plus gros volumes.

Spécialisation sectorielle et taille

Code
scatter_quadrants(
  ze %>% filter(!is.na(eco_krugman_a21_23)),
  x = "emppriv_vol_24", y = "eco_krugman_a21_23",
  title = "Les petites ZE sont les plus spécialisées",
  subtitle = "Krugman A21 vs emploi privé — taille = population",
  xlab = "Emploi salarié privé 2024", ylab = "Krugman A21",
  hline = median(ze$eco_krugman_a21_23, na.rm = TRUE),
  vline = median(ze$emppriv_vol_24, na.rm = TRUE),
  quad_labels = c("Grand diversifié", "Petit diversifié",
                  "Petit spécialisé", "Grand spécialisé")
) +
  scale_x_log10(labels = label_comma())
plot_bar_ranked(
  ze, var = "emppriv_vol_24", label_col = "libelle",
  n_show = 15,
  title = glue("Top/Bottom 15 — Emploi privé 2024"),
  subtitle = glue("Total : {fmt_k(total_emp)} salariés privés"),
  xlab = "Emploi salarié privé",
  caption = make_source("URSSAF Séquoia 2024")
)
Figure 3
Figure 4

Gauche : le Krugman mesure la dissimilarité sectorielle (0 = identique à la France, 2 = totalement différent). Les grandes ZE diversifiées sont en bas-droite, les petites ZE industrielles/agricoles en haut-gauche. Droite : top/bottom 15 en volume.

Concentration sectorielle par typologie

Code
gini_typo <- ze %>%
  filter(!is.na(typo_insee_lib)) %>%
  group_by(typo_insee_lib) %>%
  summarise(
    n = n(),
    krugman_a21 = median(eco_krugman_a21_23, na.rm = TRUE),
    gini_a21 = median(eco_gini_a21_23, na.rm = TRUE),
    emp_med = median(emppriv_vol_24, na.rm = TRUE),
    tcam_2224 = median(eco_emppriv_vtcam_2224, na.rm = TRUE),
    pct_cadres = median(dsp_csp_cadres_pct_22, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(emp_med))

rt_styled(
  gini_typo,
  columns = list(
    typo_insee_lib = colDef(name = "Typologie INSEE", minWidth = 220),
    n = col_num("Nb ZE"),
    krugman_a21 = col_num("Krugman A21", digits = 3),
    gini_a21 = col_num("Gini A21", digits = 3),
    emp_med = col_pop(gini_typo, "emp_med", label = "Emploi méd."),
    tcam_2224 = col_variation(gini_typo, "tcam_2224", label = "TCAM 22-24", unit = "%/an"),
    pct_cadres = col_num("% cadres", unit = "%", digits = 1)
  )
)
Table 1

QE2.2 — Trajectoires emploi 2011-2024

Indice 100 base 2014 par typologie

Code
# Indice base 2014 propre
typo_lookup <- ze %>% select(code, typo_insee_lib, taille_ze)
base2014 <- serie_ze %>% filter(year == 2014) %>% select(code, base_eff = eff_total)

serie_idx <- serie_ze %>%
  left_join(base2014, by = "code") %>%
  mutate(indice_100 = eff_total / base_eff * 100) %>%
  left_join(typo_lookup, by = "code") %>%
  filter(!is.na(typo_insee_lib))

serie_summary <- serie_idx %>%
  group_by(typo_insee_lib, year) %>%
  summarise(
    med = median(indice_100, na.rm = TRUE),
    q25 = quantile(indice_100, 0.25, na.rm = TRUE),
    q75 = quantile(indice_100, 0.75, na.rm = TRUE),
    .groups = "drop")

serie_fr_idx <- serie_fr %>%
  mutate(indice_100 = eff_total / eff_total[year == 2014] * 100)

ggplot(serie_summary, aes(x = year, y = med, color = typo_insee_lib)) +
  geom_ribbon(aes(ymin = q25, ymax = q75, fill = typo_insee_lib),
              alpha = 0.08, color = NA) +
  geom_line(linewidth = 0.9) +
  geom_line(data = serie_fr_idx, aes(x = year, y = indice_100),
            inherit.aes = FALSE, linetype = "dashed", color = "#333", linewidth = 0.7) +
  geom_vline(xintercept = 2020, linetype = "dotted", color = col_red, alpha = 0.6) +
  annotate("text", x = 2020.3, y = 116, label = "COVID", color = col_red,
           size = 3, fontface = "italic") +
  annotate("text", x = 2023.5,
           y = serie_fr_idx$indice_100[serie_fr_idx$year == 2023] + 0.8,
           label = "France", size = 3, color = "#333", fontface = "italic") +
  scale_x_continuous(breaks = 2014:2024) +
  labs(title = "Les trajectoires divergent : les diversifiées accélèrent, les industrielles stagnent",
       subtitle = "Indice 100 base 2014 — médiane par typologie INSEE, ruban = IQR",
       x = NULL, y = "Indice (base 100 = 2014)",
       color = "Typologie", fill = "Typologie",
       caption = make_source("URSSAF Séquoia", glue("{N_ZE} ZE"))) +
  theme(legend.position = "bottom", legend.text = element_text(size = 8))
Figure 5

Chaque ligne = médiane des ZE d’une typologie. Le ruban = intervalle interquartile (IQR). Pointillé noir = France. La divergence s’accentue après 2020 : les ZE diversifiées et métropolitaines rattrapent plus vite que les industrielles.

Choc COVID et trajectoire pré/post

Code
ggplot(ze %>% filter(!is.na(choc_covid_pct)),
       aes(x = taille_ze, y = choc_covid_pct, fill = taille_ze)) +
  geom_boxplot(alpha = 0.6, outlier.size = 1.5) +
  geom_hline(yintercept = fr_choc, linetype = "dashed", color = col_red) +
  annotate("text", x = 0.6, y = fr_choc + 0.3,
           label = glue("France : {fmt_sign(fr_choc, 1)}%"),
           size = 3, color = col_red, hjust = 0) +
  scale_fill_manual(values = c(col_cyan, col_green, col_orange, col_yellow)) +
  labs(title = "Le choc COVID a frappé toutes les tailles de ZE",
       subtitle = "Glissement effectifs privés 2019 → 2020",
       x = NULL, y = "Variation emploi (%)",
       caption = make_source("URSSAF Séquoia")) +
  guides(fill = "none")
plot_distribution(ze, "eco_emppriv_vtcam_2224", bins = 35,
  title = "TCAM emploi privé 2022-2024",
  xlab = "TCAM emploi privé (%/an)") +
  geom_vline(xintercept = fr_tcam_emp, linetype = "dashed",
             color = col_orange, linewidth = 0.7) +
  annotate("text", x = fr_tcam_emp + 0.12, y = Inf,
           label = glue("France : {fmt_sign(fr_tcam_emp, 2)}%"),
           vjust = 2, hjust = 0, size = 3, color = col_orange)
Figure 6
Figure 7

Scatter pré × post COVID

Code
labs_pre <- label_top_bottom(ze, "eco_emppriv_vtcam_2224", n = 12)

ggplot(ze, aes(x = eco_emppriv_vtcam_1117, y = eco_emppriv_vtcam_2224)) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = col_gray) +
  geom_hline(yintercept = 0, color = "#ddd") +
  geom_vline(xintercept = 0, color = "#ddd") +
  geom_point(aes(size = emppriv_vol_24, color = choc_covid_pct), alpha = 0.6) +
  geom_text_repel(
    data = ze %>% filter(libelle %in% labs_pre),
    aes(label = libelle), size = 2.8, max.overlaps = 20,
    color = col_spacegray, segment.color = col_gray) +
  scale_size_continuous(range = c(1, 10), guide = "none") +
  scale_color_gradient2(low = col_red, mid = "white", high = col_green,
                        midpoint = 0, name = "Choc COVID\n(% 19→20)") +
  labs(title = glue("Sous la bissectrice = décélération ({100 - pct_accel}% des ZE)"),
       subtitle = "Taille = emploi 2024, couleur = ampleur du choc COVID",
       x = "TCAM emploi privé 2011-2017 (%/an)",
       y = "TCAM emploi privé 2022-2024 (%/an)",
       caption = make_source("URSSAF Séquoia", glue("{N_ZE} ZE")))
Figure 8: TCAM pré-COVID vs post-reprise — Qui accélère, qui décroche ?

Points au-dessus de la bissectrice = accélération post-COVID. Points rouges = ZE très touchées en 2020. Le scatter montre que le choc COVID n’a pas systématiquement favorisé les petites ZE : les accélérations sont sélectives.

59% des ZE accélèrent (TCAM post > TCAM pré), mais l’accélération n’est pas aléatoire — elle favorise les ZE tertiaires, les couronnes métropolitaines et certaines littorales. Les ZE industrielles restent majoritairement sous la bissectrice.

Top/Bottom 15 — TCAM post-reprise

Code
tb_emp <- table_top_bottom(ze, "eco_emppriv_vtcam_2224", n = 15,
  extra_cols = c("eco_emppriv_vtcam_1117", "tcam_diff", "typo_insee_lib",
                 "emppriv_vol_24", "eco_krugman_a21_23"))

tb_emp_data <- bind_rows(
  tb_emp$top %>% mutate(groupe = "Top 15 — Croissance rapide"),
  tb_emp$bottom %>% mutate(groupe = "Bottom 15 — Déclin ou stagnation")
)

rt_styled(
  tb_emp_data,
  groupBy = "groupe", defaultExpanded = TRUE, height = 550,
  columns = list(
    rang = colDef(name = "#", width = 45),
    libelle = colDef(name = "Zone d'emploi", minWidth = 160),
    eco_emppriv_vtcam_2224 = col_variation(tb_emp_data, "eco_emppriv_vtcam_2224",
                                            label = "TCAM 22-24", unit = "%/an"),
    eco_emppriv_vtcam_1117 = col_variation(tb_emp_data, "eco_emppriv_vtcam_1117",
                                            label = "TCAM 11-17", unit = "%/an"),
    tcam_diff = col_variation(tb_emp_data, "tcam_diff",
                              label = "Accélération", unit = "pts"),
    typo_insee_lib = colDef(name = "Typologie", minWidth = 140),
    emppriv_vol_24 = col_pop(tb_emp_data, "emppriv_vol_24", label = "Emploi 24"),
    eco_krugman_a21_23 = col_num("Krugman", digits = 3),
    groupe = colDef(show = FALSE)
  )
)
Table 2

QE2.3 — Tests statistiques

Kruskal-Wallis : TCAM par typologie

Code
ze_kw <- ze %>% filter(!is.na(typo_insee_lib), !is.na(eco_emppriv_vtcam_2224))

kw_result <- kruskal.test(eco_emppriv_vtcam_2224 ~ typo_insee_lib, data = ze_kw)
kw_chi2 <- round(kw_result$statistic, 2)
kw_p <- kw_result$p.value
kw_sig <- ifelse(kw_p < 0.001, "p < 0.001",
           paste0("p = ", formatC(kw_p, format = "f", digits = 3)))
TipKruskal-Wallis — TCAM emploi par typologie

H₀ : Les TCAM emploi privé 2022-2024 sont identiques entre typologies INSEE.

Résultat : χ² = 31.73, p < 0.001 → les TCAM diffèrent significativement selon la typologie.

Code
ggplot(ze_kw,
       aes(x = eco_emppriv_vtcam_2224,
           y = reorder(typo_insee_lib, eco_emppriv_vtcam_2224, FUN = median))) +
  geom_boxplot(fill = col_cyan, alpha = 0.5, outlier.size = 1.5) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#333") +
  geom_vline(xintercept = fr_tcam_emp, linetype = "dotted", color = col_orange) +
  labs(title = glue("KW : {kw_sig}"),
       subtitle = "TCAM emploi privé 2022-2024 par typologie",
       x = "TCAM (%/an)", y = NULL,
       caption = "Pointillé orange = France")
ggplot(ze %>% filter(!is.na(taille_ze), !is.na(eco_emppriv_vtcam_2224)),
       aes(x = eco_emppriv_vtcam_2224, y = taille_ze, fill = taille_ze)) +
  geom_boxplot(alpha = 0.5, outlier.size = 1.5) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#333") +
  geom_vline(xintercept = fr_tcam_emp, linetype = "dotted", color = col_orange) +
  scale_fill_manual(values = c(col_cyan, col_green, col_orange, col_yellow)) +
  labs(title = "Par taille de ZE",
       subtitle = "TCAM emploi privé 2022-2024",
       x = "TCAM (%/an)", y = NULL,
       caption = "Pointillé orange = France") +
  guides(fill = "none")
Figure 9
Figure 10

Dunn post-hoc

Code
if (kw_p < 0.05) {
  dunn_res <- dunn.test(ze_kw$eco_emppriv_vtcam_2224,
                        ze_kw$typo_insee_lib,
                        method = "bonferroni", list = FALSE, table = FALSE)

  dunn_df <- data.frame(
    comparaison = dunn_res$comparisons,
    z = round(dunn_res$Z, 2),
    p_ajuste = round(dunn_res$P.adjusted, 4),
    significatif = ifelse(dunn_res$P.adjusted < 0.05, "Oui", "Non")
  ) %>%
    filter(significatif == "Oui") %>%
    arrange(p_ajuste)

  n_sig <- nrow(dunn_df)
  n_total <- length(dunn_res$P.adjusted)
}
Code
if (exists("dunn_df") && nrow(dunn_df) > 0) {
  rt_styled(
    dunn_df,
    columns = list(
      comparaison = colDef(name = "Comparaison", minWidth = 300),
      z = col_num("Z", digits = 2),
      p_ajuste = col_num("p ajusté", digits = 4),
      significatif = colDef(name = "Sign.", width = 60)
    )
  )
}
Table 3

6 paire(s) significative(s) sur 21 comparaisons (Bonferroni α = 0.05).

KW sur l’accélération pré/post

Code
ze_kw2 <- ze %>% filter(!is.na(typo_insee_lib), !is.na(tcam_diff))
kw2 <- kruskal.test(tcam_diff ~ typo_insee_lib, data = ze_kw2)

kw2_p <- ifelse(kw2$p.value < 0.001, "p < 0.001",
          paste0("p = ", formatC(kw2$p.value, format = "f", digits = 3)))
TipKW — Accélération emploi (TCAM post − TCAM pré)

χ² = 15.79, p = 0.015 — l’accélération/décélération diffère significativement selon la typologie.

Corrélations Spearman

Code
corr_vars <- c(
  "eco_krugman_a21_23", "eco_gini_a21_23",
  "eco_emppriv_vtcam_1117", "eco_emppriv_vtcam_2224", "tcam_diff",
  "eco_sectindus_pct_22", "eco_sectservi_pct_22",
  "dsp_csp_cadres_pct_22", "eco_txemp_1564_22",
  "idxeco_soc_ind_1622"
)

corr_labels <- c(
  "Krugman A21", "Gini A21",
  "TCAM emp 11-17", "TCAM emp 22-24", "Accélération",
  "% industrie", "% services",
  "% cadres", "Tx emploi 15-64",
  "Idx éco composite"
)

corr_data <- ze[, corr_vars] %>% na.omit()
corr_mat <- cor(corr_data, method = "spearman")
colnames(corr_mat) <- corr_labels
rownames(corr_mat) <- corr_labels

corrplot(corr_mat, method = "color", type = "lower",
         tl.col = "#333", tl.srt = 45, tl.cex = 0.85,
         addCoef.col = "#333", number.cex = 0.75,
         col = colorRampPalette(c(col_red, "white", col_cyan))(200),
         diag = FALSE,
         title = "Corrélations Spearman — variables structurelles et dynamiques emploi",
         mar = c(0, 0, 2, 0))
Figure 11: Matrice de corrélations Spearman — 10 variables structurelles et dynamiques

Corrélations de Spearman (robustes aux outliers). Le TCAM post-COVID corrèle positivement avec le % services et le % cadres, négativement avec le Krugman et le % industrie. L’indice éco composite corrèle fortement avec les TCAM — validation interne.

Scatters exploratoires

Code
cor_indus <- cor(ze$eco_sectindus_pct_22, ze$eco_emppriv_vtcam_2224,
                 use = "complete.obs", method = "spearman")

scatter_quadrants(
  ze, x = "eco_sectindus_pct_22", y = "eco_emppriv_vtcam_2224",
  title = glue("L'industrie freine la reprise (ρ = {fmt_fr(cor_indus, 2)})"),
  subtitle = "Part industrie 2022 vs TCAM emploi privé 2022-2024",
  xlab = "Part industrie (%)", ylab = "TCAM emploi privé (%/an)",
  hline = 0, vline = median(ze$eco_sectindus_pct_22, na.rm = TRUE),
  quad_labels = c("Industrie + Croissance", "Tertiaire + Croissance",
                  "Tertiaire + Déclin", "Industrie + Déclin")
)
Figure 12
Code
cor_krug <- cor(ze$eco_krugman_a21_23, ze$eco_emppriv_vtcam_2224,
                use = "complete.obs", method = "spearman")

scatter_quadrants(
  ze, x = "eco_krugman_a21_23", y = "eco_emppriv_vtcam_2224",
  title = glue("La spécialisation freine la reprise (ρ = {fmt_fr(cor_krug, 2)})"),
  subtitle = "Krugman A21 (2023) vs TCAM emploi privé 2022-2024",
  xlab = "Krugman A21", ylab = "TCAM emploi privé (%/an)",
  hline = 0, vline = median(ze$eco_krugman_a21_23, na.rm = TRUE),
  quad_labels = c("Spécialisé + Croissance", "Diversifié + Croissance",
                  "Diversifié + Déclin", "Spécialisé + Déclin")
)
Figure 13
Code
cor_cadres <- cor(ze$dsp_csp_cadres_pct_22, ze$eco_emppriv_vtcam_2224,
                  use = "complete.obs", method = "spearman")

scatter_quadrants(
  ze, x = "dsp_csp_cadres_pct_22", y = "eco_emppriv_vtcam_2224",
  title = glue("Cadres et croissance emploi (ρ = {fmt_fr(cor_cadres, 2)})"),
  subtitle = "Part cadres 2022 vs TCAM emploi privé 2022-2024",
  xlab = "Part cadres (%)", ylab = "TCAM emploi privé (%/an)",
  hline = 0, vline = median(ze$dsp_csp_cadres_pct_22, na.rm = TRUE),
  quad_labels = c("Cadres + Croissance", "Ouvriers + Croissance",
                  "Ouvriers + Déclin", "Cadres + Déclin")
)
Figure 14
Code
cor_txemp <- cor(ze$eco_txemp_1564_22, ze$eco_emppriv_vtcam_2224,
                 use = "complete.obs", method = "spearman")

scatter_quadrants(
  ze, x = "eco_txemp_1564_22", y = "eco_emppriv_vtcam_2224",
  title = glue("Taux d'emploi et dynamique (ρ = {fmt_fr(cor_txemp, 2)})"),
  subtitle = "Taux emploi 15-64 ans 2022 vs TCAM emploi privé 2022-2024",
  xlab = "Taux emploi 15-64 (%)", ylab = "TCAM emploi privé (%/an)",
  hline = 0, vline = median(ze$eco_txemp_1564_22, na.rm = TRUE),
  quad_labels = c("Plein emploi + Croissance", "Chômage + Croissance",
                  "Chômage + Déclin", "Plein emploi + Déclin")
)
Figure 15

QE2.4 — Indice économique & croisement résidentiel

Code
cor_idx_krugman <- cor(ze$idxeco_soc_ind_1622, ze$eco_krugman_a21_23, use = "complete.obs")

Le croisement résidentiel × économique (r = 0,33) révèle quatre profils : les « double moteur » (métropoles, littoral attractif), les « attractifs sans emploi » (tourisme, retraites), les « éco sans résidentiel » (industrie en reconversion) et les « double fragilité » (diagonale du vide).

Code
labs_cross <- label_top_bottom(ze, "idxeco_soc_ind_1622", n = 10)

ggplot(ze, aes(x = idxresid_dyn_ind_1623, y = idxeco_soc_ind_1622)) +
  geom_hline(yintercept = 50, linetype = "dashed", color = col_gray) +
  geom_vline(xintercept = 50, linetype = "dashed", color = col_gray) +
  geom_point(aes(size = P22_POP, color = typo_insee_lib), alpha = 0.6) +
  geom_text_repel(
    data = ze %>% filter(libelle %in% labs_cross),
    aes(label = libelle), size = 2.8, max.overlaps = 20,
    color = col_spacegray, segment.color = col_gray) +
  scale_size_continuous(range = c(1, 8), guide = "none") +
  scale_color_manual(values = pal_urbn_cat) +
  annotate("text", x = 85, y = 88, label = "Double moteur",
           color = col_green, fontface = "bold", size = 3.5) +
  annotate("text", x = 15, y = 88, label = "Éco dynamique\nsans attractivité",
           color = col_orange, fontface = "bold", size = 3.5) +
  annotate("text", x = 15, y = 12, label = "Double fragilité",
           color = col_red, fontface = "bold", size = 3.5) +
  annotate("text", x = 85, y = 12, label = "Attractif résidentiel\nsans emploi",
           color = col_cyan, fontface = "bold", size = 3.5) +
  labs(title = glue("Résidentiel × Économique (r = {fmt_fr(cor_cross, 2)}) — deux dimensions partiellement indépendantes"),
       subtitle = "Percentiles 0-100, ligne 50 = médiane France",
       x = "Indice résidentiel (attractivité migratoire)",
       y = "Indice économique (dynamisme emploi)",
       color = "Typologie",
       caption = make_source("URSSAF", "MIGCOM", glue("{N_ZE} ZE"))) +
  theme(legend.position = "bottom", legend.text = element_text(size = 8))
Figure 16: Croisement indices résidentiel × économique — 4 profils territoriaux

Le croisement résidentiel × économique montre que les deux dimensions ne sont pas redondantes (r = 0,33). Les ZE touristiques/littorales sont souvent en haut-droite (attractives résidentiellement) mais pas toujours en haut sur l’axe économique. Les ZE industrielles en reconversion peuvent avoir un idxeco élevé mais un idxresid faible.

Stats descriptives par quadrant

Code
ze <- ze %>%
  mutate(quad_eco_resid = case_when(
    idxresid_dyn_ind_1623 >= 50 & idxeco_soc_ind_1622 >= 50 ~ "Double moteur",
    idxresid_dyn_ind_1623 < 50  & idxeco_soc_ind_1622 >= 50 ~ "Éco sans résidentiel",
    idxresid_dyn_ind_1623 >= 50 & idxeco_soc_ind_1622 < 50  ~ "Résidentiel sans éco",
    idxresid_dyn_ind_1623 < 50  & idxeco_soc_ind_1622 < 50  ~ "Double fragilité",
    TRUE ~ NA_character_
  ))

quad_stats <- ze %>%
  filter(!is.na(quad_eco_resid)) %>%
  group_by(quad_eco_resid) %>%
  summarise(
    n = n(),
    pop_med = median(P22_POP, na.rm = TRUE),
    emp_med = median(emppriv_vol_24, na.rm = TRUE),
    tcam_med = median(eco_emppriv_vtcam_2224, na.rm = TRUE),
    pct_cadres = median(dsp_csp_cadres_pct_22, na.rm = TRUE),
    krugman = median(eco_krugman_a21_23, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(n))

rt_styled(
  quad_stats,
  columns = list(
    quad_eco_resid = colDef(name = "Quadrant", minWidth = 180,
                            style = list(fontWeight = "500")),
    n = col_num("Nb ZE"),
    pop_med = col_pop(quad_stats, "pop_med", label = "Pop. méd."),
    emp_med = col_pop(quad_stats, "emp_med", label = "Emploi méd."),
    tcam_med = col_variation(quad_stats, "tcam_med", label = "TCAM 22-24", unit = "%/an"),
    pct_cadres = col_num("% cadres", unit = "%", digits = 1),
    krugman = col_num("Krugman", digits = 3)
  )
)
Table 4

Synthèse provisoire

Tip5 constats principaux
  1. Concentration extrême : CR10 = 37,2 %, Gini = 0,625, Palma = 6,0. L’emploi privé est bien plus concentré que la population — rendements d’agglomération.

  2. Trajectoires divergentes post-2014 : L’indice 100 révèle un écart croissant entre ZE diversifiées/métropolitaines et ZE industrielles/agricoles. La divergence s’accentue après 2020.

  3. Choc COVID universel, reprise sélective : -0,5% en 2020 pour toutes les tailles. Mais 59% des ZE accélèrent post-COVID — les tertiaires, couronnes métropolitaines et certaines littorales.

  4. Structure sectorielle = facteur explicatif clé : % industrie et Krugman corrèlent négativement avec le TCAM post (ρ = -0,15 et -0,13). Les cadres corrèlent positivement (ρ = 0,30).

  5. Résidentiel ≠ Économique : r = 0,33 — les deux dimensions sont partiellement indépendantes, justifiant leur traitement séparé dans l’observatoire.

Bilan hypothèses

Hypothèse Résultat
H1 Concentration croissante À nuancer — CR10 élevé mais les grandes ZE n’accélèrent pas toutes plus vite
H2 Effet métropole Partiellement confirmée — KW significatif (p < 0.001), médié par cadres et tertiarisation
H3 COVID redistribue Partiellement confirmée — accélérations en ZE moyennes, mais pas de pattern systématique
WarningVariables manquantes — phase 2
  • Salaire moyen : masse salariale absente → ratio salaire/emploi indisponible
  • Part littorale : dummy géographique à construire (codes ZE → proximité côte)
  • Theil décomposé : inter/intra-typologies pour quantifier les inégalités territoriales
  • OLS multivarié : TCAM ~ taille + Krugman + % cadres + dummy_metro + dummy_littoral
  • Croisement peco × pdemo : scatter TCAM emploi × z-trajectoire résidentielle