---
title: "Dynamiques de l'emploi salarié privé"
subtitle: "Volet 2 — Économie et spécialisation sectorielle par zone d'emploi"
author: "Vincent R."
date: last-modified
date-format: "D MMMM YYYY"
categories: ["Rapport d'analyse"]
number-sections: false
---

<!-- Rapport synthèse volet économie (rpt-peco-rapport-ecoze-260215)
     Type : RAPPORT (code hidden, insight-first, rédigé)
     Companion : eco/rpt-peco-edanbk-ecoze.qmd (notebook EDA, code visible)
     Données : URSSAF Séquoia 2014-2024, FLORES 2023, INSEE RP 2022
     Helpers : _setup-common.R → jcn-graph.R, jcn-table.R, jcn-claude-log.R
     Config  : hérite de _quarto.yml (theme-html.scss, toc-left, echo:false)
-->

```{r}
#| label: setup
#| include: false

# &s &SETUP_aaMAIN - Setup rapport volet économie
# Charge helpers JCN + données ZE via _setup-common.R
# Enrichit ze avec effectifs URSSAF, choc COVID, variables dérivées
# Pré-calcule tous les KPIs utilisés dans le texte inline

# &s &PACKAGES - Helpers + libs supplémentaires
source("_setup-common.R")

suppressPackageStartupMessages({
  library(ineq)       # Gini, Lorenz
  library(dunn.test)  # Dunn post-hoc KW
  library(corrplot)   # Heatmap corrélations
})
# &e

# &s &CLAUDE_LOG - Logging résultats inter-sessions
claude_log_init("rpt-20-eco",
                output_dir = file.path(PROJECT_ROOT, "reports/outputs"))
log_step("SETUP", n_ze = N_ZE, n_cols = ncol(ze))
# &e

# &s &SERIE_URSSAF - Série temporelle URSSAF emploi privé
serie_raw <- read.csv(file.path(PROJECT_ROOT,
  "data/processed/urssaf/urssaf_serie_indice100.csv"),
  stringsAsFactors = FALSE, fileEncoding = "UTF-8-BOM")

serie_ze <- serie_raw %>% filter(echelon == "ze")
serie_fr <- serie_raw %>% filter(echelon == "france")
# &e

# &s &ENRICHISSEMENT - Variables dérivées

# Effectif emploi privé 2024
eff_24 <- serie_ze %>%
  filter(year == 2024) %>%
  select(code, emppriv_vol_24 = eff_total)
ze <- ze %>% left_join(eff_24, by = "code")

# Choc COVID 2019→2020
eff_covid <- serie_ze %>%
  filter(year %in% c(2019, 2020)) %>%
  select(code, year, eff_total) %>%
  pivot_wider(names_from = year, values_from = eff_total,
              names_prefix = "eff_") %>%
  mutate(choc_covid_pct = (eff_2020 - eff_2019) / eff_2019 * 100)
ze <- ze %>% left_join(eff_covid, by = "code")

# France référence
fr_eff_24 <- serie_fr %>% filter(year == 2024) %>% pull(eff_total)
fr_eff_14 <- serie_fr %>% filter(year == 2014) %>% pull(eff_total)
fr_eff_19 <- serie_fr %>% filter(year == 2019) %>% pull(eff_total)
fr_eff_20 <- serie_fr %>% filter(year == 2020) %>% pull(eff_total)
fr_choc <- (fr_eff_20 - fr_eff_19) / fr_eff_19 * 100
fr_tcam_emp <- as.numeric(FR$eco_emppriv_vtcam_2224)
fr_evol_1424 <- (fr_eff_24 / fr_eff_14 - 1) * 100

# Taille ZE (4 classes)
ze <- ze %>%
  mutate(
    taille_ze = case_when(
      grepl("Paris", libelle) ~ "IDF (Paris)",
      P22_POP >= 200000 ~ "Grande (>200k)",
      P22_POP >= 50000  ~ "Moyenne (50-200k)",
      TRUE              ~ "Petite (<50k)"
    ),
    taille_ze = factor(taille_ze,
      levels = c("IDF (Paris)", "Grande (>200k)", "Moyenne (50-200k)", "Petite (<50k)"))
  )

# Accélération pré/post COVID
ze <- ze %>%
  mutate(tcam_diff = eco_emppriv_vtcam_2224 - eco_emppriv_vtcam_1117)
# &e

# &s &KPIS - Indicateurs clés (réutilisés dans texte inline + synthèse)
ze_sorted <- ze %>% arrange(desc(emppriv_vol_24))
total_emp <- sum(ze$emppriv_vol_24, na.rm = TRUE)
top10_emp <- sum(ze_sorted$emppriv_vol_24[1:10], na.rm = TRUE)
cr10 <- top10_emp / total_emp * 100

n10 <- ceiling(N_ZE * 0.10)
n40 <- ceiling(N_ZE * 0.40)
palma_employment <- sum(ze_sorted$emppriv_vol_24[1:n10], na.rm = TRUE) /
                    sum(tail(ze_sorted$emppriv_vol_24, n40), na.rm = TRUE)

gini_emp <- ineq(ze$emppriv_vol_24[!is.na(ze$emppriv_vol_24)], type = "Gini")

n_accel <- sum(ze$tcam_diff > 0, na.rm = TRUE)
pct_accel <- round(100 * n_accel / sum(!is.na(ze$tcam_diff)), 0)

cor_idx_tcam <- cor(ze$idxeco_soc_ind_1622, ze$eco_emppriv_vtcam_2224, use = "complete.obs")
cor_cross <- cor(ze$idxresid_dyn_ind_1623, ze$idxeco_soc_ind_1622, use = "complete.obs")
cor_indus <- cor(ze$eco_sectindus_pct_22, ze$eco_emppriv_vtcam_2224,
                 use = "complete.obs", method = "spearman")
cor_krug <- cor(ze$eco_krugman_a21_23, ze$eco_emppriv_vtcam_2224,
                use = "complete.obs", method = "spearman")
cor_cadres <- cor(ze$dsp_csp_cadres_pct_22, ze$eco_emppriv_vtcam_2224,
                  use = "complete.obs", method = "spearman")

# KW pré-calcul (résultat utilisé dans texte inline)
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)))

# Série France indice 100 (pour vue nationale)
serie_fr_idx <- serie_fr %>%
  mutate(indice_100 = eff_total / eff_total[year == 2014] * 100)

log_step("DATA_ENRICHMENT",
         n_ze_with_emp24 = sum(!is.na(ze$emppriv_vol_24)),
         fr_choc_pct = round(fr_choc, 2))
log_result("cr10", cr10, interpret = glue("CR10 = {fmt_pct(cr10, 1)}"))
log_result("gini_emp", gini_emp, interpret = glue("Gini emploi = {fmt_fr(gini_emp, 3)}"))
log_result("palma", palma_employment, interpret = glue("Palma = {fmt_fr(palma_employment, 1)}"))
log_result("pct_accel", pct_accel, interpret = glue("{pct_accel}% ZE accélèrent post-COVID"))
log_result("cor_cross_resid_eco", cor_cross, interpret = glue("r resid x eco = {fmt_fr(cor_cross, 2)}"))
# &e

# &e &SETUP_aaMAIN
```


::: {.callout-note}
## Cadrage

**Question centrale** : Quels facteurs structurels et conjoncturels expliquent les trajectoires divergentes de l'emploi salarié privé par zone d'emploi ?

**Périmètre** : `r N_ZE` ZE métropolitaines (hors DOM) · `r fmt_fr(total_emp / 1e6, 1)` M salariés privés (2024)

**Sources** : URSSAF Séquoia (effectifs 2014-2024) · FLORES 2023 (Krugman, Gini) · INSEE RP 2022 (structures sectorielles, taux d'emploi, CSP)

**Hypothèses** :

- **H1** : La concentration de l'emploi est supérieure à celle de la population
- **H2** : L'effet métropole s'explique plus par la tertiarisation que par la taille
- **H3** : Le choc COVID a redistribué l'emploi vers les ZE moyennes/littorales
:::

::: {.kpi-grid}

::: {.kpi-card}
<div class="kpi-value">`r fmt_pct(cr10, 1)`</div>
<div class="kpi-label">CR10 emploi privé</div>
<div class="kpi-trend">10 premières ZE</div>
:::

::: {.kpi-card style="--kpi-color: #ca5800"}
<div class="kpi-value">`r fmt_fr(gini_emp, 3)`</div>
<div class="kpi-label">Gini emploi</div>
<div class="kpi-trend">Concentration extrême</div>
:::

::: {.kpi-card style="--kpi-color: #55b748"}
<div class="kpi-value">`r pct_accel`%</div>
<div class="kpi-label">ZE en accélération</div>
<div class="kpi-trend">Post-COVID 2022-2024</div>
:::

::: {.kpi-card style="--kpi-color: #ec008b"}
<div class="kpi-value">`r fmt_fr(cor_cross, 2)`</div>
<div class="kpi-label">Corrélation résid. × éco.</div>
<div class="kpi-trend">Dimensions partiellement indépendantes</div>
:::

:::

---

# Vue nationale — L'emploi privé en France

::: {.insight}
**L'emploi salarié privé français progresse de `r fmt_sign(fr_evol_1424, 1)`% entre 2014 et 2024, mais cette moyenne nationale masque des disparités territoriales majeures**
:::

La France compte `r fmt_fr(fr_eff_24 / 1e6, 1)` millions de salariés du secteur privé en 2024. La série temporelle nationale fait apparaître trois phases distinctes : une croissance modérée pré-COVID, un choc brutal en 2020 (`r fmt_sign(fr_choc, 1)`%), puis une reprise qui ramène l'emploi au-dessus de son niveau d'avant-crise dès 2022. Le TCAM post-reprise s'établit à `r fmt_sign(fr_tcam_emp, 2)`%/an (2022-2024).

```{r}
#| label: fig-france-serie
#| fig-height: 5.5

ggplot(serie_fr_idx, aes(x = year, y = indice_100)) +
  geom_line(color = col_cyan, linewidth = 1.3) +
  geom_point(color = col_cyan, size = 2) +
  geom_hline(yintercept = 100, linetype = "dashed", color = col_gray) +
  geom_vline(xintercept = 2020, linetype = "dotted", color = col_red, alpha = 0.6) +
  annotate("text", x = 2020.3, y = min(serie_fr_idx$indice_100) + 0.3,
           label = "COVID", color = col_red, size = 3, fontface = "italic") +
  annotate("text", x = 2022.5,
           y = serie_fr_idx$indice_100[serie_fr_idx$year == 2022] + 0.5,
           label = "Retour niveau\navant-crise", color = col_green,
           size = 2.8, fontface = "italic") +
  scale_x_continuous(breaks = 2014:2024) +
  labs(title = "L'emploi privé retrouve son niveau d'avant-crise fin 2022",
       subtitle = "France — Indice 100 base 2014, emploi salarié privé",
       x = NULL, y = "Indice (base 100 = 2014)",
       caption = make_source("URSSAF Séquoia 2014-2024"))
```

::: {.grey-section}
**Chiffres clés France** : `r fmt_fr(fr_eff_24 / 1e6, 1)` M salariés privés (2024) · Évolution 2014-2024 : `r fmt_sign(fr_evol_1424, 1)`% · Choc COVID : `r fmt_sign(fr_choc, 1)`% · TCAM post-reprise : `r fmt_sign(fr_tcam_emp, 2)`%/an
:::

---

# Concentration de l'emploi salarié privé

::: {.insight}
**Les 10 premières ZE concentrent `r fmt_pct(cr10, 1)` 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 `r fmt_fr(palma_employment, 1)` (les 10% les plus grosses ZE emploient `r fmt_fr(palma_employment, 1)` fois plus que les 40% les plus petites). Le Gini de `r fmt_fr(gini_emp, 3)` confirme une distribution très asymétrique.

:::: {.column-body-outset}

::: {layout-ncol=2}

```{r}
#| label: fig-lorenz
#| fig-height: 6.5

# Courbe de Lorenz emploi privé 2024
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 = "Concentration de l'emploi privé — courbe de Lorenz",
       subtitle = "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"))
```

```{r}
#| label: fig-boxplot-emploi-typo
#| fig-height: 6.5

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"))
```

:::

::::

::: {.note-lecture}
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 concentrent les plus gros volumes.
:::

## Spécialisation sectorielle et taille

Les petites ZE tendent à être les plus spécialisées (Krugman élevé), avec un profil souvent industriel ou agricole. À l'inverse, les grandes ZE diversifiées présentent un tissu tertiaire proche de la moyenne nationale.

:::: {.column-body-outset}

::: {layout-ncol=2}

```{r}
#| label: fig-krugman-taille
#| fig-height: 7

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())
```

```{r}
#| label: fig-bar-emploi
#| fig-height: 7

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")
)
```

:::

::::

::: {.note-lecture}
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.
:::

## Profil par typologie

```{r}
#| label: tbl-gini-secteurs

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)
  )
)
```

```{r}
#| label: log-qe21
#| include: false
log_step("QE2.1_CONCENTRATION", cr10 = round(cr10, 1), gini = round(gini_emp, 3),
         palma = round(palma_employment, 1))
log_distribution("emppriv_vol_24", ze$emppriv_vol_24, label = "Emploi privé 2024")
log_ranking("top5_emploi_ze", ze, "libelle", "emppriv_vol_24", n = 5)
log_ranking("bot5_emploi_ze", ze, "libelle", "emppriv_vol_24", n = 5, ascending = TRUE)
```

---

# Trajectoires et choc COVID

## Indice 100 base 2014 par typologie

Les trajectoires de l'emploi salarié privé divergent nettement depuis 2014, avec une accélération de l'écart après le choc COVID de 2020.

```{r}
#| label: fig-indice100
#| fig-height: 7.5

# Indice base 2014 par typologie
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")

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))
```

::: {.note-lecture}
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 reprise

Le glissement 2019→2020 a frappé l'ensemble du tissu économique (`r fmt_sign(fr_choc, 1)`% France), mais la reprise est sélective. `r pct_accel`% des ZE accélèrent post-COVID (TCAM 2022-2024 > TCAM 2011-2017), favorisant les ZE tertiaires, les couronnes métropolitaines et certaines littorales.

:::: {.column-body-outset}

::: {layout-ncol=2}

```{r}
#| label: fig-choc-covid
#| fig-height: 6

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")
```

```{r}
#| label: fig-distrib-tcam
#| fig-height: 6

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)
```

:::

::::

## Qui accélère, qui décroche ?

```{r}
#| label: fig-scatter-pre-post
#| fig-height: 8

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")))
```

::: {.note-lecture}
Points au-dessus de la bissectrice = accélération post-COVID. Points rouges = ZE très touchées en 2020. L'accélération n'est pas aléatoire — elle favorise les ZE tertiaires, les couronnes métropolitaines et certaines littorales.
:::

## Top/Bottom 15 — TCAM post-reprise

:::: {.column-body-outset}

```{r}
#| label: tbl-topbot-tcam

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)
  )
)
```

::::

```{r}
#| label: log-qe22
#| include: false
log_step("QE2.2_TRAJECTOIRES", pct_accel = pct_accel,
         fr_choc = round(fr_choc, 2), fr_tcam_emp = fr_tcam_emp)
log_distribution("tcam_emppriv_2224", ze$eco_emppriv_vtcam_2224, label = "TCAM emp privé 22-24")
log_ranking("top5_tcam_post", ze, "libelle", "eco_emppriv_vtcam_2224", n = 5)
log_ranking("bot5_tcam_post", ze, "libelle", "eco_emppriv_vtcam_2224", n = 5, ascending = TRUE)
```

---

# Structure sectorielle et tests statistiques

## Différenciation par typologie

Le TCAM emploi privé 2022-2024 diffère significativement selon la typologie INSEE (Kruskal-Wallis χ² = `r kw_chi2`, `r kw_sig`). La structure sectorielle — mesurée par le poids de l'industrie et le Krugman — est le premier facteur explicatif.

:::: {.column-body-outset}

::: {layout-ncol=2}

```{r}
#| label: fig-kw-boxplot
#| fig-height: 6

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")
```

```{r}
#| label: fig-kw-boxplot-taille
#| fig-height: 6

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")
```

:::

::::

```{r}
#| label: dunn-test
#| include: false

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)
}
```

`r if (exists("n_sig")) glue("Le test post-hoc de Dunn identifie **{n_sig} paire(s) significative(s)** sur {n_total} comparaisons (correction Bonferroni, α = 0.05).") else "*KW non significatif — Dunn non exécuté.*"`

```{r}
#| label: tbl-dunn

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)
    )
  )
}
```

## Corrélations structurelles

```{r}
#| label: fig-corr-heatmap
#| fig-width: 11
#| fig-height: 11

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))
```

::: {.grey-section}
**Corrélations clés** : Le TCAM post-COVID corrèle positivement avec le % services et le % cadres (ρ = `r fmt_fr(cor_cadres, 2)`), négativement avec le Krugman (ρ = `r fmt_fr(cor_krug, 2)`) et le % industrie (ρ = `r fmt_fr(cor_indus, 2)`).
:::

## Facteurs de la dynamique emploi

::: {.panel-tabset}

### % industrie × TCAM

```{r}
#| label: fig-scatter-indus
#| fig-height: 6.5

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")
)
```

### Krugman × TCAM

```{r}
#| label: fig-scatter-krugman-tcam
#| fig-height: 6.5

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")
)
```

### % cadres × TCAM

```{r}
#| label: fig-scatter-cadres-tcam
#| fig-height: 6.5

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")
)
```

### Taux emploi × TCAM

```{r}
#| label: fig-scatter-txemp
#| fig-height: 6.5

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")
)
```

:::

```{r}
#| label: log-qe23
#| include: false
log_step("QE2.3_TESTS_STATS", kw_chi2 = kw_chi2, kw_p = kw_p)
log_result("cor_indus_tcam", cor_indus,
           interpret = glue("ρ industrie×TCAM = {fmt_fr(cor_indus, 2)}"))
log_result("cor_cadres_tcam", cor_cadres,
           interpret = glue("ρ cadres×TCAM = {fmt_fr(cor_cadres, 2)}"))
log_result("cor_krugman_tcam", cor_krug,
           interpret = glue("ρ Krugman×TCAM = {fmt_fr(cor_krug, 2)}"))
```

---

# Croisement résidentiel × économique

::: {.insight}
**Le croisement résidentiel × économique (r = `r fmt_fr(cor_cross, 2)`) 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)**
:::

```{r}
#| label: fig-cross-resid-eco
#| fig-height: 8.5

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)})"),
       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))
```

::: {.note-lecture}
Les deux dimensions ne sont pas redondantes (r = `r fmt_fr(cor_cross, 2)`). Les ZE touristiques/littorales sont souvent attractives résidentiellement mais pas toujours dynamiques économiquement.
:::

## Profils par quadrant

```{r}
#| label: tbl-quadrants-eco-resid

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)
  )
)
```

```{r}
#| label: log-qe24
#| include: false
log_step("QE2.4_CROISEMENT", cor_cross = round(cor_cross, 3),
         cor_idx_tcam = round(cor_idx_tcam, 3))
log_result("quadrants_eco_resid",
           as.list(setNames(quad_stats$n, quad_stats$quad_eco_resid)),
           interpret = paste(quad_stats$quad_eco_resid, "=", quad_stats$n, collapse = " | "))
```

---

# Synthèse et bilan

::: {.callout-tip}
## 5 constats principaux

1. **Concentration extrême** : CR10 = `r fmt_pct(cr10, 1)`, Gini = `r fmt_fr(gini_emp, 3)`, Palma = `r fmt_fr(palma_employment, 1)`. 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** : `r fmt_sign(fr_choc, 1)`% en 2020 pour toutes les tailles. Mais `r pct_accel`% 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 (ρ = `r fmt_fr(cor_indus, 2)` et `r fmt_fr(cor_krug, 2)`). Les cadres corrèlent positivement (ρ = `r fmt_fr(cor_cadres, 2)`).

5. **Résidentiel ≠ Économique** : r = `r fmt_fr(cor_cross, 2)` — les deux dimensions sont partiellement indépendantes, justifiant leur traitement séparé.
:::

### 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 (`r kw_sig`), médié par cadres et tertiarisation |
| **H3** COVID redistribue | **Partiellement confirmée** — accélérations en ZE moyennes, mais pas de pattern systématique |

::: {.callout-warning}
## Variables 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
:::

```{r}
#| label: log-save
#| include: false

log_result("synthese_h1", "a_nuancer",
           interpret = "H1 concentration : CR10 élevé mais pas d'accélération systématique grandes ZE")
log_result("synthese_h2", "partiellement_confirmee",
           interpret = glue("H2 effet métropole : KW {kw_sig}, médié par cadres+tertiarisation"))
log_result("synthese_h3", "partiellement_confirmee",
           interpret = glue("H3 COVID redistribue : {pct_accel}% accélèrent, sélectif"))
claude_log_save()
```
