Dynamiques démographiques & Attractivité résidentielle

Volet 1 — Croissance, flux migratoires et profils territoriaux

Rapport d'analyse
Auteur·rice

Vincent R.

Date de publication

12 mars 2026

Chaque année, 7,3 millions de Français changent de commune de résidence — soit 11 % de la population. Ces flux migratoires, combinés aux dynamiques naturelles (naissances, décès), recomposent silencieusement la carte démographique du pays. Le recensement 2022 confirme un ralentissement quasi-généralisé : la croissance annuelle moyenne de la population nationale est passée de +0,5 % à +0,3 % entre les deux dernières périodes intercensitaires.

Les flux migratoires compensent-ils le déficit naturel croissant, et cette compensation varie-t-elle selon le type de territoire ?

Ce rapport analyse les dynamiques démographiques sur la période 2011-2022, à trois échelles emboîtées : 287 zones d’emploi (croissance et compensation), 96 départements (géographie des migrations par catégorie de population) et ~34 000 communes agrégées par type de territoire (gradient urbain-rural). Les données mobilisées sont le recensement de la population (RP 2011-2022) et les fichiers détail MIGCOM (2016, 2022) pour les flux migratoires par âge et catégorie socioprofessionnelle. Trois hypothèses structurent l’analyse :

  • H1 — L’attractivité migratoire ne compense pas le déficit naturel dans les ZE vieillissantes
  • H2 — Les cadres et les retraités migrent vers des territoires distincts
  • H3 — Le gradient urbain-rural structure les dynamiques démographiques

Sources : INSEE RP 2011-2022 (populations légales, soldes naturels), MIGCOM 2016 et 2022 (flux migratoires détaillés par âge, CSP, commune d’origine/destination). Périmètre : France métropolitaine hors DOM (287 ZE, 96 départements, ~34 000 communes). Indicateurs : TCAM population, solde naturel (SN) et solde migratoire apparent (SMA) en taux annuels, taux de rotation (TR = (entrants + sortants) / pop), indice de vieillissement (IV = 60+ / 0-14 × 100), taux migratoire interne (TMI) par catégorie. Tests : Kruskal-Wallis sur 4 indicateurs × 4 types TYPO4pers, taille d’effet par eta² = H/(N−1). Limites : MIGCOM ne couvre que les migrations internes (pas l’international) ; le SMA est un résidu (variation pop − SN), pas un flux observé ; rupture de série MIGCOM 2011 (passage quinquennal → annuel).

Partie 1 — La France ralentit : croissance et compensation

38% ZE en déclin 16-22

+0,35% TCAM France

68% ZE qui ralentissent

  • Ralentissement : le TCAM national passe de +0,44% à +0,35% entre les deux périodes, et 68% des ZE décélèrent ou déclinent davantage
  • Compensation : dans 133 ZE, le solde migratoire compense partiellement un déficit naturel croissant — mais cette substitution reste insuffisante dans la majorité des territoires vieillissants
  • Fragilité : 51 ZE cumulent solde naturel et solde migratoire négatifs, concentrées dans la diagonale continentale

Le déclin démographique s’accélère entre 2016 et 2022

110 zones d’emploi sur 287 perdent de la population entre 2016 et 2022, contre 88 sur la période précédente. Le ralentissement est quasi-généralisé : 68% des ZE croissent moins vite ou déclinent davantage. La corrélation entre les TCAM des deux périodes reste forte (r = 0,85) : les zones d’emploi en croissance restent globalement en croissance, celles en déclin continuent de décliner. Mais la pente est inférieure à 1 (0,83) — signe d’une convergence par le bas. Le TCAM moyen passe de +0,27 %/an à +0,16 %/an sur l’ensemble des 287 ZE (figure ci-dessous).

Sur ces 287 zones d’emploi, 119 cumulent un déclin aux deux périodes (quadrant bas-gauche du scatter), tandis que 27 basculent de la croissance au déclin. Seules 26 ZE accélèrent entre T1 et T2 — presque toutes des métropoles du sud et de l’ouest.

Code
ze_long <- ze %>%
  select(libelle, dm_pop_vtcam_1116, dm_pop_vtcam_1622) %>%
  pivot_longer(cols = starts_with("dm_pop_vtcam"),
               names_to = "periode", values_to = "tcam") %>%
  mutate(periode = ifelse(grepl("1116", periode), "2011-2016", "2016-2022"))

ggplot(ze_long, aes(x = tcam, fill = periode)) +
  geom_histogram(bins = 35, alpha = 0.6, position = "identity", color = "white") +
  geom_vline(xintercept = 0, linetype = "solid", color = "#333", linewidth = 0.5) +
  geom_vline(xintercept = fr_tcam_t1, linetype = "dashed", color = col_cyan, linewidth = 0.7) +
  geom_vline(xintercept = fr_tcam_t2, linetype = "dashed", color = col_spacegray, linewidth = 0.7) +
  scale_fill_manual(values = c("2011-2016" = col_cyan, "2016-2022" = col_spacegray)) +
  labs(x = "TCAM population (%/an)", y = "Nombre de ZE", fill = "Période",
       caption = make_source("INSEE RP")) +
  annotate("text", x = fr_tcam_t1 + 0.05, y = Inf, label = "FR 11-16",
           vjust = 2, hjust = 0, size = 3, color = col_cyan) +
  annotate("text", x = fr_tcam_t2 + 0.05, y = Inf, label = "FR 16-22",
           vjust = 3.5, hjust = 0, size = 3, color = col_spacegray)

Distribution du TCAM population par période
Code
plot_scatter_plotly(ze,
  x = "dm_pop_vtcam_1116", y = "dm_pop_vtcam_1622",
  size_col = "P22_POP", n_labels = 12,
  ref_lines = "diagonal",
  quadrant_labels = c("Accélération", "", "", "Ralentissement"),
  xlab = "TCAM population 2011-2016 (%/an)",
  ylab = "TCAM population 2016-2022 (%/an)",
  caption = make_source("INSEE RP"),
  height = 550)

Trajectoire TCAM T1 vs T2 — Accélération ou décrochage ?

Code
tb <- table_top_bottom(ze, "dm_pop_vtcam_1622", n = 15,
                       extra_cols = c("dm_pop_vtcam_1116", "P22_POP"))

tb_data <- bind_rows(
  tb$top %>% mutate(groupe = "Top 15"),
  tb$bottom %>% mutate(groupe = "Bottom 15")
)

rt_styled(
  tb_data,
  groupBy = "groupe",
  height = 500,
  columns = list(
    rang = colDef(name = "#", width = 45),
    libelle = colDef(name = "Zone d'emploi", minWidth = 160),
    dm_pop_vtcam_1622 = col_variation(tb_data, "dm_pop_vtcam_1622",
                                       label = "TCAM 16-22", unit = "%/an"),
    dm_pop_vtcam_1116 = col_variation(tb_data, "dm_pop_vtcam_1116",
                                       label = "TCAM 11-16", unit = "%/an"),
    P22_POP = col_pop(tb_data, "P22_POP", label = "Pop. 2022"),
    groupe = colDef(show = FALSE)
  )
)
Table 1: Top et bottom 15 — TCAM population 2016-2022

Les extrêmes confirment la polarisation territoriale. En tête, Toulouse (+1,29 %/an), Montpellier (+1,19 %) et Bordeaux (+1,05 %) affichent un TCAM supérieur à +1 %/an sur les deux périodes — tirées par l’emploi métropolitain et l’attractivité résidentielle. En queue de classement, les zones d’emploi rurales du quart nord-est (Bar-le-Duc, Vitry-le-François, Saint-Dié) perdent entre −0,5 et −0,9 %/an. Le décrochage est structurel : les 15 ZE les plus dynamiques gagnent en moyenne 3 fois plus que la croissance nationale, tandis que les 15 dernières perdent 4 fois plus vite.

Ce ralentissement généralisé résulte-t-il d’un déficit naturel croissant, et le solde migratoire parvient-il à compenser ?

Le SMA compense partiellement le déficit naturel

133 ZE compensent un déficit naturel par l’attractivité migratoire (quadrant SN- SMA+), soit le groupe le plus fréquent. Mais 51 ZE cumulent les deux déficits — territoires en voie de dévitalisation. Le scatter SN × SMA dessine quatre profils distincts (figure ci-dessous). La corrélation négative (r = −0,32) traduit une substitution partielle : les territoires en déficit naturel attirent des migrants, mais cette compensation reste rarement suffisante pour inverser le déclin. Le quadrant dominant est la compensation (90 ZE, SN- SMA+), à parité avec le profil de fuite (90 ZE, SN+ SMA-) — un quasi-équilibre qui reflète le balancier entre métropoles productrices de naturel et territoires résidentiels vieillissants.

Parmi les 51 ZE en double déficit, les territoires de la diagonale continentale dominent — zones rurales vieillissantes (Meuse, Nièvre, Creuse, Cantal) où ni la natalité ni l’attractivité ne parviennent à stabiliser la population. À l’opposé, 52 ZE combinent solde naturel et solde migratoire positifs (double moteur), concentrées dans le Grand Ouest (Nantes, Rennes, Bordeaux) et quelques pôles alpins.

Code
scatter_quadrants(
  ze, x = "dm_sn_vtcam_1622", y = "dm_sma_vtcam_1622",
  title = "Solde naturel vs. solde migratoire apparent (2016-2022)",
  subtitle = "Chaque point = 1 ZE, taille = population",
  xlab = "Solde naturel TCAM (%/an)",
  ylab = "SMA TCAM (%/an)",
  quad_labels = c("Double moteur", "Compensation", "Double déficit", "Fuite")
)

Diagramme SN × SMA — Les 4 profils démographiques
Code
rt_styled(
  quad_counts %>% rename(`Profil` = quad_snsma, `Nb ZE` = n),
  columns = list(
    `Profil` = colDef(minWidth = 220),
    `Nb ZE` = col_num("Nb ZE")
  ),
  fullWidth = FALSE
)
Table 2: Répartition des ZE par quadrant SN × SMA

La répartition par quadrants éclaire les mécanismes sous-jacents. Le profil de fuite (SN+ SMA-) regroupe 90 ZE — essentiellement des pôles urbains qui produisent du naturel mais perdent des résidents au profit de leur couronne. Ce flux centrifuge alimente les 90 ZE en compensation (SN- SMA+), où l’arrivée de nouveaux résidents masque temporairement le vieillissement structurel. La table ci-dessus confirme que les deux profils dominants sont symétriques : le même nombre de ZE gagne ce que l’autre perd.

Test H1 : compensation dans les ZE vieillissantes

AvertissementTest H1 — L’attractivité migratoire ne compense pas le déficit naturel dans les ZE vieillissantes

Parmi les 172 ZE vieillissantes (IV > 100), 132 ont un SMA positif — elles attirent plus qu’elles ne perdent. Mais la compensation est insuffisante : seulement 51% ont un SMA qui dépasse leur déficit naturel en valeur absolue.

H1 partiellement confirmée : le vieillissement reste le facteur dominant du déclin — l’attractivité migratoire ralentit le déclin sans l’inverser.

Le bilan macro masque des trajectoires divergentes. Si le ralentissement est quasi-généralisé, certaines ZE renforcent leur attractivité tandis que d’autres décrochent — la dynamique entre les deux périodes révèle ces destins contrastés.

Partie 2 — Trajectoires migratoires : qui résiste entre T1 et T2 ?

42% ZE en renforcement

47 ZE en retournement

25 ZE en déclin accentué

  • Persistance : la corrélation SMA entre T1 et T2 est forte (r = 0,83) — les territoires attractifs le restent, les territoires répulsifs aussi
  • Renforcement : 120 ZE voient leur SMA positif s’améliorer encore, essentiellement dans le Grand Ouest et le littoral méditerranéen
  • Décrochage : 25 ZE s’enfoncent dans le déclin migratoire, concentrées dans la diagonale continentale et le quart nord-est industriel

Plus d’un quart des ZE renforcent leur attractivité migratoire

120 ZE sur 287 combinent un SMA positif aux deux périodes et une trajectoire ascendante, contre 25 en déclin accentué. Les retournements — positifs (29) ou négatifs (18) — concernent 16% des ZE. La classification en six trajectoires migratoires (figure ci-dessous) révèle une géographie des destins contrastés. Les deux profils dominants — attractif renforcé et déclin accentué — représentent ensemble plus de la moitié des ZE. Le premier concentre les zones littorales, périurbaines et certains pôles du Grand Ouest. Le second regroupe les territoires de la diagonale continentale, du quart nord-est et des bassins industriels en reconversion.

Les retournements sont plus rares mais signalent des dynamiques en cours. Les 29 retournements positifs (passage de SMA- à SMA+) indiquent des territoires qui retrouvent une attractivité — souvent des zones de proximité métropolitaine ou des communes rurales bénéficiant du desserrement résidentiel post-COVID. Les 18 retournements négatifs (passage de SMA+ à SMA-) signalent au contraire un essoufflement, parfois dans des zones touristiques dont l’attractivité structurelle s’effrite.

Code
traj_plot <- traj_counts %>%
  mutate(
    pct = round(100 * n / sum(n), 1),
    label = glue("{n} ({pct}%)")
  )

ggplot(traj_plot, aes(x = reorder(trajectoire, n), y = n, fill = trajectoire)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = label), hjust = -0.1, size = 3.2, color = col_spacegray) +
  coord_flip(clip = "off") +
  scale_fill_manual(values = traj_colors, guide = "none") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  labs(x = NULL, y = "Nombre de ZE",
       caption = make_source("INSEE RP"))

Trajectoires migratoires des ZE — Classification en 6 types
Code
traj_profil <- ze %>%
  filter(!is.na(trajectoire)) %>%
  group_by(trajectoire) %>%
  summarise(
    n = n(),
    sma_t1 = weighted.mean(dm_sma_vtcam_1116, P22_POP, na.rm = TRUE),
    sma_t2 = weighted.mean(dm_sma_vtcam_1622, P22_POP, na.rm = TRUE),
    delta = sma_t2 - sma_t1,
    pop_tcam = weighted.mean(dm_pop_vtcam_1622, P22_POP, na.rm = TRUE),
    iv = weighted.mean(dmv_iv_ind_22, P22_POP, na.rm = TRUE),
    .groups = "drop"
  )

rt_styled(
  traj_profil,
  title = "Indicateurs moyens pondérés par type de trajectoire",
  source = make_source("INSEE RP"),
  columns = list(
    trajectoire = colDef(name = "Trajectoire", minWidth = 180, style = list(fontWeight = "500")),
    n = col_num("N"),
    sma_t1 = col_variation(traj_profil, "sma_t1", label = "SMA T1", unit = "%/an"),
    sma_t2 = col_variation(traj_profil, "sma_t2", label = "SMA T2", unit = "%/an"),
    delta = col_variation(traj_profil, "delta", label = "Delta", unit = "pts"),
    pop_tcam = col_variation(traj_profil, "pop_tcam", label = "TCAM pop", unit = "%/an"),
    iv = col_num("IV", digits = 0)
  )
)
Table 3: Profils moyens par trajectoire migratoire
Indicateurs moyens pondérés par type de trajectoire
Source : INSEE RP

Le SMA (solde migratoire apparent) est un résidu : variation de population − solde naturel. Il intègre les migrations internes, internationales et les ajustements de comptage. La classification en 6 types repose sur le signe du SMA et son évolution entre T1 (2011-2016) et T2 (2016-2022).

Le tableau confirme la cohérence des profils. Les ZE en renforcement affichent un TCAM population nettement positif et un indice de vieillissement modéré — elles cumulent dynamisme naturel et attractivité migratoire. À l’opposé, les ZE en déclin accentué présentent l’IV le plus élevé et le TCAM population le plus négatif. Les retournements positifs se distinguent par un delta SMA important (passage de négatif à positif), souvent associé à un vieillissement intermédiaire.

L’attractivité migratoire persiste entre les deux périodes

La corrélation entre SMA T1 et SMA T2 est de 0,83 — les territoires attractifs le restent, les territoires répulsifs aussi. Mais la pente inférieure à 1 indique une convergence : les SMA extrêmes se rapprochent de la moyenne.

Code
plot_scatter_plotly(ze,
  x = "dm_sma_vtcam_1116", y = "dm_sma_vtcam_1622",
  size_col = "P22_POP", color_col = "trajectoire",
  palette = unname(traj_colors), n_labels = 10,
  ref_lines = "diagonal",
  quadrant_labels = c("Renforcement", "", "", "Tassement"),
  xlab = "SMA TCAM 2011-2016 (%/an)",
  ylab = "SMA TCAM 2016-2022 (%/an)",
  caption = make_source("INSEE RP"),
  height = 600)

SMA T1 vs SMA T2 — Persistance et convergence de l’attractivité migratoire

Le scatter SMA T1 × T2 révèle une structure en quatre cadrans. Le quadrant haut-droit (attractif renforcé + tassement) concentre les ZE du Grand Ouest et du littoral, tandis que le quadrant bas-gauche (déclin atténué + accentué) regroupe la diagonale continentale. Les retournements se lisent dans les quadrants haut-gauche (positifs) et bas-droit (négatifs). La pente inférieure à 1 signifie que les ZE aux SMA extrêmes convergent vers la moyenne — les très attractifs le deviennent un peu moins, les très répulsifs un peu moins aussi. Ce mouvement de convergence est cohérent avec celui observé sur le TCAM population (cf. partie 1).

Ces trajectoires dessinent une géographie des destins migratoires. Mais à l’échelle départementale, quels territoires captent ces flux, et les cadres migrent-ils vers les mêmes destinations que les retraités ?

Partie 3 — Les profils migratoires départementaux

+0,21% Médiane SMA (DEP)

68 DEP attractifs

5,3 pts Amplitude TMI cadres

  • Géographie : 68 départements sur 96 affichent un SMA positif, avec un arc atlantique-méditerranéen qui capte l’essentiel des flux entrants tandis que la diagonale continentale se vide
  • Brassage : la corrélation entre taux de rotation et SMA est quasi-nulle (r = -0,12) — un fort brassage résidentiel ne garantit pas l’attractivité nette
  • Profils : les départements attractifs captent à la fois cadres et retraités (r = 0,61) — le clivage n’est pas entre catégories mais entre territoires

L’arc atlantique-méditerranéen capte les flux, la diagonale se vide

68 départements sur 96 affichent un SMA positif, mais la dispersion est considérable : de −1,18 %/an (Paris) à +1,36 %/an (Corse-du-Sud). Le solde naturel dessine une géographie inversée — les départements franciliens cumulent le SN le plus élevé et le SMA le plus négatif.

Code
tm_shape(map_dep) +
  tm_polygons(fill = "dm_sma_vtcam_1622",
    fill.scale = tm_scale_intervals(breaks = breaks_sma,
      values = make_pal(rev(pal_urbn_div), 8)),
    fill.legend = tm_legend_clean("%/an"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("SMA TCAM 2016-2022") +
  tm_source(make_source("INSEE RP"))
tm_shape(map_dep) +
  tm_polygons(fill = "dm_sn_vtcam_1622",
    fill.scale = tm_scale_intervals(breaks = breaks_sn,
      values = make_pal(rev(pal_urbn_div), 8)),
    fill.legend = tm_legend_clean("%/an"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("Solde naturel TCAM 2016-2022") +
  tm_source(make_source("INSEE RP"))
tm_shape(map_dep) +
  tm_polygons(fill = "dmv_iv_ind_22",
    fill.scale = tm_scale_intervals(breaks = breaks_iv,
      values = make_pal(pal_urbn_seq_magenta, 7)),
    fill.legend = tm_legend_clean("IV"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("Indice de vieillissement 2022") +
  tm_source(make_source("INSEE RP"))

Les 3 cartes se lisent ensemble : le SMA (à gauche) dessine l’arc attractif littoral ; le SN (centre) révèle le déficit naturel structurel de la diagonale ; l’IV (droite) confirme le vieillissement des territoires en déclin. Les départements combinant SMA- et SN- sont les plus fragiles.

Le triptyque confirme la dualité territoriale française. Le SMA départemental est positif dans 68 des 96 départements, avec une médiane de +0,21 %/an. La Corse-du-Sud (+1,36 %/an) et les Landes (+1,22 %/an) dominent le classement, tandis que Paris (−1,18 %/an) se distingue par une hémorragie migratoire massive. Le solde naturel dessine une géographie inversée : les départements franciliens affichent les SN les plus élevés (Seine-Saint-Denis : +1,14 %/an) quand la Creuse (−1,0 %/an) et le Cantal (−0,73 %/an) cumulent déficit naturel et vieillissement. L’indice de vieillissement varie d’un facteur 4, de 43,6 en Seine-Saint-Denis à 176,6 dans la Creuse — confirmant le lien structurel entre SN négatif et vieillissement avancé (r = −0,97 au niveau départemental).

Ces cartes dessinent des profils régionaux nets. Mais un département très mobile — où beaucoup de gens arrivent et partent — est-il pour autant attractif ?

Fort brassage résidentiel ne signifie pas forte attractivité nette

La corrélation entre taux de rotation et SMA est quasi-nulle (r = −0,12, p = 0,26) — statistiquement non significative. Paris illustre le découplage : TR le plus élevé de France (9,96 %) mais SMA le plus négatif (−1,18 %/an). ::: {.panel-tabset}

Scatter TR × SMA

Code
med_tr_dep <- median(dep$dmf_tr_pct_22, na.rm = TRUE)

plot_scatter_plotly(dep,
  x = "dmf_tr_pct_22", y = "dm_sma_vtcam_1622",
  size_col = "P22_POP", n_labels = 10,
  ref_lines = "none",
  xlab = "Taux de rotation (%)",
  ylab = "SMA TCAM 2016-2022 (%/an)",
  caption = make_source("MIGCOM", "INSEE RP"),
  height = 550) |>
  plotly::layout(shapes = list(
    list(type = "line", x0 = 0, x1 = 1, y0 = 0, y1 = 0,
         xref = "paper", line = list(color = "#ccc", width = 1, dash = "dash"), layer = "below"),
    list(type = "line", x0 = med_tr_dep, x1 = med_tr_dep, y0 = 0, y1 = 1,
         yref = "paper", line = list(color = "#ccc", width = 1, dash = "dash"), layer = "below")
  ))

Taux de rotation vs SMA — Brassage ≠ attractivité

Bar SMA top/bottom

Code
plot_bar_ranked(dep, var = "dm_sma_vtcam_1622", label_col = "libelle",
                n = 15, fill_bot = col_spacegray,
                title = "Attractivité migratoire nette par département",
                subtitle = "SMA TCAM 2016-2022 (%/an)",
                caption = make_source("INSEE RP"))

Top et bottom 15 — SMA départemental

La corrélation entre taux de rotation et SMA est quasi-nulle (r = −0,12, p = 0,26) — statistiquement non significative. Ce résultat invalide l’intuition selon laquelle les départements les plus brassés seraient les plus attractifs. Paris illustre ce découplage : avec un TR de 9,96 % (le plus élevé de France), le département affiche le SMA le plus négatif (−1,18 %/an). À l’inverse, la Corse-du-Sud combine un TR modéré et le SMA le plus élevé (+1,36 %/an). Le brassage reflète la mobilité résidentielle globale — entrées et sorties confondues — tandis que le SMA ne capture que le solde net.

Si le brassage ne prédit pas l’attractivité nette, la composition des flux — par catégorie socioprofessionnelle et par âge — révèle-t-elle des logiques migratoires différentes ?

Les cadres et les retraités ne migrent pas vers les mêmes territoires

Contrairement à l’hypothèse initiale, la corrélation TMI cadres × TMI 65+ est positive (r = 0,61) : les départements qui attirent des cadres attirent aussi des retraités. Le clivage n’est pas cadres vs. retraités, mais territoires globalement attractifs vs. globalement répulsifs.

Code
breaks_tmi <- c(-Inf, -4, -2, -1, 0, 1, 2, 4, Inf)

tm_shape(map_dep) +
  tm_polygons(fill = "dmf_tmi_cscadre_22",
    fill.scale = tm_scale_intervals(breaks = breaks_tmi,
      values = make_pal(rev(pal_urbn_div), 8)),
    fill.legend = tm_legend_clean("pts"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("TMI Cadres 2022") +
  tm_source(make_source("MIGCOM"))
tm_shape(map_dep) +
  tm_polygons(fill = "dmf_tmi_a65p_22",
    fill.scale = tm_scale_intervals(breaks = breaks_tmi,
      values = make_pal(rev(pal_urbn_div), 8)),
    fill.legend = tm_legend_clean("pts"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("TMI 65 ans et plus 2022") +
  tm_source(make_source("MIGCOM"))

Le TMI (taux migratoire interne) mesure le gain ou la perte nette d’une catégorie de population par rapport au stock de cette catégorie. Un TMI cadres de +2 signifie que le département gagne 2 cadres nets pour 1 000 cadres résidents.

Code
plot_scatter_plotly(dep,
  x = "dmf_tmi_cscadre_22", y = "dmf_tmi_a65p_22",
  size_col = "P22_POP", n_labels = 10,
  ref_lines = "auto",
  quadrant_labels = c("Perd cadres, gagne 65+", "Gagne les deux",
                      "Perd les deux", "Gagne cadres, perd 65+"),
  xlab = "TMI Cadres (points)",
  ylab = "TMI 65+ (points)",
  caption = make_source("MIGCOM"),
  height = 600)

TMI cadres vs TMI 65+ — Deux géographies migratoires

Code
tmi_synth <- dep %>%
  summarise(
    across(
      c(dmf_tmi_cscadre_22, dmf_tmi_csouvrier_22, dmf_tmi_a65p_22, dmf_tmi_a2529_22),
      list(med = ~median(.x, na.rm = TRUE),
           q10 = ~quantile(.x, 0.10, na.rm = TRUE),
           q90 = ~quantile(.x, 0.90, na.rm = TRUE)),
      .names = "{.col}__{.fn}")
  ) %>%
  pivot_longer(everything(), names_to = c("var", "stat"), names_sep = "__") %>%
  pivot_wider(names_from = stat, values_from = value) %>%
  mutate(
    label = case_when(
      grepl("cadre", var) ~ "TMI Cadres",
      grepl("ouvrier", var) ~ "TMI Ouvriers",
      grepl("a65p", var) ~ "TMI 65+",
      grepl("a2529", var) ~ "TMI 25-29 ans"
    ),
    amplitude = q90 - q10
  ) %>%
  select(label, med, q10, q90, amplitude)

rt_styled(
  tmi_synth,
  title = "Distributions TMI départementales (points)",
  source = make_source("MIGCOM"),
  columns = list(
    label = colDef(name = "Catégorie", minWidth = 140, style = list(fontWeight = "500")),
    med = col_variation(tmi_synth, "med", label = "Médiane", unit = "pts", digits = 1),
    q10 = col_num("P10", digits = 1),
    q90 = col_num("P90", digits = 1),
    amplitude = col_num("Amplitude", digits = 1)
  )
)
Table 4: TMI par catégorie — synthèse départementale
Distributions TMI départementales (points)
Source : MIGCOM

Le scatter TMI cadres × TMI 65+ (r = 0,61) révèle une corrélation positive, pas négative comme on pourrait l’attendre. Les départements qui attirent des cadres attirent aussi des retraités — la Charente-Maritime (+2,73 pts cadres, +1,01 pts 65+), le Lot (+2,61, +0,88) et les Landes (+1,22, +1,03) cumulent les deux. À l’opposé, Paris (−2,59 cadres, −1,57 retraités) perd massivement sur les deux catégories. Les 11 départements en quadrant haut-gauche (TMI cadres négatif, TMI 65+ positif) correspondent au profil “silver economy” attendu, mais ils restent minoritaires. L’amplitude TMI cadres (5,3 pts entre min et max) est 2,4 fois plus large que celle des 65+ (2,6 pts) — les cadres sont plus géographiquement sélectifs dans leurs choix migratoires.

ImportantBilan H2

H2 nuancée : La corrélation TMI cadres × TMI 65+ au niveau départemental est de 0,61 — positive, pas nulle. Les territoires les plus attractifs captent à la fois cadres et retraités. Les deux géographies ne sont pas opposées mais hiérarchisées : quelques départements captent tout, d’autres perdent tout. Le clivage majeur est entre départements attractifs globalement et départements répulsifs globalement.

L’échelle départementale révèle la géographie des profils migratoires, mais pas encore les origines et destinations des flux. D’où viennent ces migrants, vers quels territoires se dirigent-ils, et à quelle distance ?

Partie 4 — Origines-Destinations : d’où viennent-ils, où vont-ils ?

140 k Solde net pôle-couronne

48 k Perte nette Paris

6 k Paires DEP significatives

  • Flux centrifuges : les pôles envoient 729 k résidents vers les couronnes mais n’en reçoivent que 590 k en retour, soit un solde net de 140 k en faveur des couronnes
  • Polarisation : Paris concentre à lui seul plus de la moitié des pertes migratoires inter-départementales, avec un solde net de -48 k résidents
  • Flux intra-type dominants : la majorité des migrations se font au sein du même type de territoire (pôle-pôle, couronne-couronne), pas entre types

Les couronnes captent les flux centrifuges des pôles

Les pôles perdent 140 k résidents nets au profit des couronnes chaque année. Mais le flux dominant reste intra-pôle (1,1 M migrants) : la déconcentration est un processus marginal en volume, massif en solde net.

Code
suppressPackageStartupMessages(library(plotly))

fig <- plot_ly(
  type = "sankey",
  orientation = "h",
  node = list(
    label = sk_nodes$name,
    color = c(col_cyan, col_darkblue, col_green,
              col_cyan, col_darkblue, col_green),
    pad = 15,
    thickness = 20
  ),
  link = list(
    source = sk_links$source,
    target = sk_links$target,
    value = sk_links$value,
    color = c(
      paste0(col_cyan, "66"),    # Pole->Pole
      paste0(col_cyan, "66"),    # Pole->Couronne
      paste0(col_cyan, "44"),    # Pole->Hors
      paste0(col_darkblue, "66"),  # Cour->Pole
      paste0(col_darkblue, "66"),  # Cour->Cour
      paste0(col_darkblue, "44"),  # Cour->Hors
      paste0(col_green, "44"),   # Hors->Pole
      paste0(col_green, "44"),   # Hors->Cour
      paste0(col_green, "44")    # Hors->Hors
    )
  )
)

fig <- fig %>%
  plotly::layout(font = list(family = .font_family, size = 11))

fig

Flux migratoires entre types de territoire (2022)

Le diagramme Sankey représente les flux migratoires inter-communaux (IRAN 3-5) entre trois types de territoire AAV : pôle (commune-centre + pôle secondaire), couronne et hors AAV. L’épaisseur des liens est proportionnelle au volume pondéré (IPONDI). Les flux intra-type (pôle-pôle, couronne-couronne) sont les plus épais car la majorité des migrations se font au sein du même type de territoire. Source : MIGCOM 2022, fichier détail.

Le Sankey confirme le gradient centrifuge décrit par Davezies-Talandier. Les flux pôle vers couronne (729k) dépassent nettement le retour couronne vers pôle (590k), soit un solde net de 139k en faveur des couronnes. Mais ce flux centrifuge est un processus marginal rapporté au stock : les 729k sortants des pôles représentent moins de 4 % de leur population, contre 1 132k migrants intra-pôle. La périurbanisation est un flux net modeste en proportion, mais cumulé année après année, il remodèle la géographie résidentielle.

Le flux vers le rural hors AAV est plus modeste mais asymétrique : 82k sortants des pôles et 121k sortants des couronnes, contre 76k et 99k en retour. Le rural attire un filet net positif depuis les deux autres types.

Paris concentre la moitié des pertes migratoires inter-départementales

Paris perd 48 k résidents nets par an par échange inter-départemental — plus que les 4 départements suivants réunis. Les départements du littoral ouest et sud captent l’essentiel de ces flux.

Code
map_solde <- shp_dep %>%
  left_join(solde22, by = c("code_geo" = "dep"))

breaks_flux <- c(-Inf, -10000, -5000, -1000, 0, 1000, 5000, 10000, Inf)

tm_shape(map_solde) +
  tm_polygons(fill = "nb_entrants",
    fill.scale = tm_scale_intervals(
      breaks = quantile(solde22$nb_entrants, probs = seq(0, 1, length.out = 8), na.rm = TRUE),
      values = make_pal(pal_urbn_seq_blue, 7)),
    fill.legend = tm_legend_clean("individus"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("Entrants inter-DEP (2022)") +
  tm_source(make_source("MIGCOM"))
tm_shape(map_solde) +
  tm_polygons(fill = "nb_sortants",
    fill.scale = tm_scale_intervals(
      breaks = quantile(solde22$nb_sortants, probs = seq(0, 1, length.out = 8), na.rm = TRUE),
      values = make_pal(pal_urbn_seq_magenta, 7)),
    fill.legend = tm_legend_clean("individus"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("Sortants inter-DEP (2022)") +
  tm_source(make_source("MIGCOM"))

Cartes en volumes absolus (entrants/sortants inter-départementaux pondérés IPONDI). Les départements les plus peuplés concentrent mécaniquement les plus gros volumes. Comparer avec la carte choroplèthe de solde net ci-dessous pour isoler l’attractivité nette du simple effet de taille.

Code
breaks_solde <- c(-Inf, -10000, -5000, -2000, 0, 2000, 5000, 10000, Inf)

tm_shape(map_solde) +
  tm_polygons(fill = "solde_net",
    fill.scale = tm_scale_intervals(breaks = breaks_solde,
      values = make_pal(rev(pal_urbn_div), 8)),
    fill.legend = tm_legend_clean("individus"),
    col = "grey70", lwd = 0.3) +
  tm_urbn("Solde migratoire net inter-DEP (2022)", "Bleu = gain net, rouge = perte nette") +
  tm_source(make_source("MIGCOM"))

Solde migratoire net inter-départemental (2022)

La carte du solde net confirme la géographie observée sur le SMA départemental (cf. P3) : l’arc atlantique-méditerranéen capte les flux, l’Île-de-France et la diagonale continentale en perdent. Paris domine les pertes avec 48 k résidents nets, suivi de la Seine-Saint-Denis et des Hauts-de-Seine. Les gains se répartissent sur un arc plus large, de la Charente-Maritime au Var en passant par l’Ille-et-Vilaine.

Les 10 premières paires de flux concentrent un quart des échanges

Le flux Paris vers Hauts-de-Seine est le plus important de France (25 k individus/an), suivi de Paris vers Val-de-Marne et Paris vers Seine-Saint-Denis. Les 10 premières paires concentrent un quart des flux inter-départementaux.

Code
top15 <- top22 %>%
  mutate(paire = paste(lib_orig, "\u2192", lib_dest)) %>%
  head(15)

ggplot(top15, aes(x = reorder(paire, flux_pond), y = flux_pond)) +
  geom_col(fill = col_cyan, width = 0.7) +
  geom_text(aes(label = scales::comma(round(flux_pond))), hjust = -0.1, size = 3, color = "#444") +
  coord_flip(clip = "off") +
  scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, 0.2))) +
  labs(x = NULL, y = "Individus/an",
       caption = make_source("MIGCOM"))

Top 15 paires de flux inter-départementaux (2022)
Code
tb_solde <- bind_rows(
  solde22 %>% slice_max(solde_net, n = 10) %>% mutate(groupe = "Top 10 (gains)"),
  solde22 %>% slice_min(solde_net, n = 10) %>% mutate(groupe = "Bottom 10 (pertes)")
)

rt_styled(
  tb_solde %>% select(groupe, libelle, nb_entrants, nb_sortants, solde_net, ratio_es),
  groupBy = "groupe",
  height = 500,
  columns = list(
    groupe = colDef(show = FALSE),
    libelle = colDef(name = "Departement", minWidth = 160, style = list(fontWeight = "500")),
    nb_entrants = col_num("Entrants"),
    nb_sortants = col_num("Sortants"),
    solde_net = col_variation(tb_solde, "solde_net", label = "Solde net"),
    ratio_es = col_num("Ratio E/S", digits = 2)
  )
)
Table 5: Top et bottom 10 — Solde migratoire inter-DEP (2022)

Les flux inter-départementaux sont dominés par le système francilien. Les 5 premières paires impliquent toutes Paris (75) comme origine ou destination — le déversement vers la petite et grande couronne structure l’essentiel des mouvements. Au-delà de l’Île-de-France, les paires Nord-Pas-de-Calais (59-62) et Rhône-Ain/Isère (69-38/01) reflètent les échanges entre métropoles et leur périphérie.

Le tableau des soldes nets confirme l’asymétrie : les 10 départements les plus perdants sont tous franciliens ou limitrophes, tandis que les gagnants sont dispersés sur le littoral et l’Ouest. Le ratio entrants/sortants amplifie le signal — un ratio < 1 signifie que le département perd plus qu’il ne reçoit.

Ces flux entre départements dessinent une géographie des échanges. Mais le type de territoire — pôle, couronne, rural — structure-t-il les profils démographiques au-delà des seuls flux OD ?

Partie 5 — Le gradient urbain-rural

31 k communes classées

65,3 M population totale

0,67 pts gradient SMA max-min

  • Pôles : seul type de territoire en perte migratoire nette (SMA négatif), malgré un solde naturel positif et la moitié de la population nationale
  • Couronnes : principal bénéficiaire des flux centrifuges, avec le SMA le plus élevé des quatre types de territoire
  • Rural : le rural autonome cumule attractivité migratoire et vieillissement avancé (IV = 159) — il attire sans se régénérer

Le gradient urbain-rural structure les dynamiques démographiques

Seuls les pôles affichent un SMA négatif (−0,10 %/an) parmi les 4 types de territoire. Les couronnes urbaines captent les flux les plus importants (+0,57 %/an), suivies du rural autonome (+0,50 %/an). Le rural autonome cumule attractivité migratoire et vieillissement avancé (IV = 159).

Code
ggplot(agg_typo4, aes(x = reorder(type_terr, sma), y = sma, fill = sma > 0)) +
  geom_col(width = 0.6) +
  geom_hline(yintercept = 0, color = "#333", linewidth = 0.5) +
  coord_flip() +
  scale_fill_manual(values = c("TRUE" = col_cyan, "FALSE" = col_spacegray), guide = "none") +
  labs(x = NULL, y = "SMA TCAM (%/an)",
       caption = make_source("INSEE RP", "MIGCOM"))

SMA moyen pondéré par type de territoire
Code
tbl_typo <- agg_typo4 %>%
  mutate(pop_pct = pop / sum(pop) * 100) %>%
  select(type_terr, n_comm, pop_pct, pop_tcam, sma, sn, tr, iv, tmi_cadre, tmi_65p)

rt_styled(
  tbl_typo,
  title = "8 indicateurs × 4 types de territoire",
  source = make_source("INSEE RP", "MIGCOM"),
  columns = list(
    type_terr = colDef(name = "Type", minWidth = 150, style = list(fontWeight = "500")),
    n_comm = col_num("Communes"),
    pop_pct = col_num("% pop", digits = 1),
    pop_tcam = col_variation(tbl_typo, "pop_tcam", label = "TCAM pop", unit = "%/an"),
    sma = col_variation(tbl_typo, "sma", label = "SMA", unit = "%/an"),
    sn = col_variation(tbl_typo, "sn", label = "SN", unit = "%/an"),
    tr = col_num("TR", digits = 1),
    iv = col_num("IV", digits = 0),
    tmi_cadre = col_variation(tbl_typo, "tmi_cadre", label = "TMI Cadres", unit = "pts", digits = 1),
    tmi_65p = col_variation(tbl_typo, "tmi_65p", label = "TMI 65+", unit = "pts", digits = 1)
  )
)
Table 6: Profils démographiques par type de territoire
8 indicateurs × 4 types de territoire
Source : INSEE RP · MIGCOM

Le tableau confirme un gradient net. Les pôles concentrent la population et le solde naturel positif (+0,39 %/an) mais perdent en migration nette (SMA = −0,10 %/an) — seul type de territoire à afficher un SMA négatif. Les couronnes urbaines captent les flux les plus importants (SMA = +0,57 %/an) tout en conservant un SN positif : elles constituent le moteur de la périurbanisation. Le rural autonome cumule les contrastes : SMA le deuxième plus élevé (+0,50 %/an) mais SN le plus déficitaire (−0,54 %/an) et indice de vieillissement à 159. Ces communes attirent massivement des cadres (TMI = +2,81 pts) et des retraités (TMI 65+ = +0,71 pts) sans pour autant régénérer leur dynamique naturelle.

Ce profil statique masque cependant les évolutions récentes. Le gradient a-t-il bougé entre les deux périodes intercensitaires, et tous les types de territoire suivent-ils la même trajectoire ?

Le rural autonome accélère, le rural périurbain décroche

3 types de territoire sur 4 voient leur SMA s’améliorer entre T1 et T2. Rural autonome enregistre la plus forte accélération (+0,15 pts), tandis que Rural périurbain affiche le repli le plus marqué (-0,12 pts). ::: {.panel-tabset}

SMA (attractivité migratoire)

Code
ggplot(agg_biperiode, aes(y = reorder(type_terr, sma_t2))) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#666", linewidth = 0.5) +
  geom_vline(xintercept = fr_sma_t1, linetype = "dotted", color = col_cyan, alpha = 0.6) +
  geom_vline(xintercept = fr_sma_t2, linetype = "dotted", color = col_spacegray, alpha = 0.6) +
  geom_segment(
    aes(x = sma_t1, xend = sma_t2, yend = reorder(type_terr, sma_t2),
        color = sma_accel),
    linewidth = 2,
    arrow = arrow(length = unit(0.2, "cm"), type = "closed")
  ) +
  geom_point(aes(x = sma_t1), shape = 21, fill = "white", size = 4, stroke = 1.2, color = "#888") +
  geom_text(
    aes(x = pmax(sma_t1, sma_t2) + 0.03,
        label = paste0(ifelse(sma_delta > 0, "+", ""), fmt_fr(sma_delta, 2))),
    hjust = 0, size = 3.3, fontface = "bold", color = "#444"
  ) +
  scale_color_manual(values = c("TRUE" = col_green, "FALSE" = col_spacegray), guide = "none") +
  scale_x_continuous(expand = expansion(mult = c(0.05, 0.18))) +
  labs(
    title = "Les couronnes et le rural accélèrent, les pôles freinent",
    subtitle = "SMA TCAM (%/an) — cercle vide = T1 (11-16), pointe flèche = T2 (16-22)",
    x = "SMA TCAM (%/an)", y = NULL,
    caption = make_source("INSEE RP")
  ) +
  annotate("text", x = fr_sma_t1 + 0.005, y = 4.4, label = paste0("Fr. T1 ", fmt_fr(fr_sma_t1, 2)),
           size = 2.5, color = col_cyan, hjust = 0) +
  annotate("text", x = fr_sma_t2 + 0.005, y = 4.4, label = paste0("Fr. T2 ", fmt_fr(fr_sma_t2, 2)),
           size = 2.5, color = col_spacegray, hjust = 0)

Arrow chart — Évolution du SMA par type de territoire entre T1 et T2

Population (TCAM)

Code
ggplot(agg_biperiode, aes(y = reorder(type_terr, pop_tcam_t2))) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#666", linewidth = 0.5) +
  geom_vline(xintercept = fr_pop_t1, linetype = "dotted", color = col_cyan, alpha = 0.6) +
  geom_vline(xintercept = fr_pop_t2, linetype = "dotted", color = col_spacegray, alpha = 0.6) +
  geom_segment(
    aes(x = pop_tcam_t1, xend = pop_tcam_t2, yend = reorder(type_terr, pop_tcam_t2),
        color = pop_delta > 0),
    linewidth = 2,
    arrow = arrow(length = unit(0.2, "cm"), type = "closed")
  ) +
  geom_point(aes(x = pop_tcam_t1), shape = 21, fill = "white", size = 4, stroke = 1.2, color = "#888") +
  geom_text(
    aes(x = pmax(pop_tcam_t1, pop_tcam_t2) + 0.02,
        label = paste0(ifelse(pop_delta > 0, "+", ""), fmt_fr(pop_delta, 2))),
    hjust = 0, size = 3.3, fontface = "bold", color = "#444"
  ) +
  scale_color_manual(values = c("TRUE" = col_green, "FALSE" = col_spacegray), guide = "none") +
  scale_x_continuous(expand = expansion(mult = c(0.05, 0.18))) +
  labs(
    title = "Tous les types de territoire ralentissent en population",
    subtitle = "TCAM population (%/an) — cercle vide = T1 (11-16), pointe flèche = T2 (16-22)",
    x = "TCAM population (%/an)", y = NULL,
    caption = make_source("INSEE RP")
  ) +
  annotate("text", x = fr_pop_t1 + 0.005, y = 4.4, label = paste0("Fr. T1 ", fmt_fr(fr_pop_t1, 2)),
           size = 2.5, color = col_cyan, hjust = 0) +
  annotate("text", x = fr_pop_t2 + 0.005, y = 4.4, label = paste0("Fr. T2 ", fmt_fr(fr_pop_t2, 2)),
           size = 2.5, color = col_spacegray, hjust = 0)

Arrow chart — Évolution du TCAM population par type de territoire

Solde naturel

Code
ggplot(agg_biperiode, aes(y = reorder(type_terr, sn_t2))) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#666", linewidth = 0.5) +
  geom_segment(
    aes(x = sn_t1, xend = sn_t2, yend = reorder(type_terr, sn_t2),
        color = sn_delta > 0),
    linewidth = 2,
    arrow = arrow(length = unit(0.2, "cm"), type = "closed")
  ) +
  geom_point(aes(x = sn_t1), shape = 21, fill = "white", size = 4, stroke = 1.2, color = "#888") +
  geom_text(
    aes(x = pmax(sn_t1, sn_t2) + 0.02,
        label = paste0(ifelse(sn_delta > 0, "+", ""), fmt_fr(sn_delta, 2))),
    hjust = 0, size = 3.3, fontface = "bold", color = "#444"
  ) +
  scale_color_manual(values = c("TRUE" = col_green, "FALSE" = col_spacegray), guide = "none") +
  scale_x_continuous(expand = expansion(mult = c(0.05, 0.18))) +
  labs(
    title = "Le solde naturel se dégrade dans tous les types de territoire",
    subtitle = "SN TCAM (%/an) — cercle vide = T1 (11-16), pointe flèche = T2 (16-22)",
    x = "SN TCAM (%/an)", y = NULL,
    caption = make_source("INSEE RP")
  )

Arrow chart — Évolution du SN par type de territoire

:::

Code
tbl_bp <- agg_biperiode %>%
  mutate(pop_pct = pop / sum(pop) * 100) %>%
  select(type_terr, n_comm, pop_pct, sma_t1, sma_t2, sma_delta, pop_tcam_t1, pop_tcam_t2, pop_delta)

rt_styled(
  tbl_bp,
  title = "Qui accélère, qui freine ? Comparaison T1 (11-16) vs T2 (16-22)",
  source = make_source("INSEE RP"),
  columns = list(
    type_terr = colDef(name = "Type", minWidth = 150, style = list(fontWeight = "500")),
    n_comm = col_num("Communes"),
    pop_pct = col_num("% pop", digits = 1),
    sma_t1 = col_variation(tbl_bp, "sma_t1", label = "SMA T1", unit = "%/an"),
    sma_t2 = col_variation(tbl_bp, "sma_t2", label = "SMA T2", unit = "%/an"),
    sma_delta = col_variation(tbl_bp, "sma_delta", label = "Δ SMA", unit = "pts"),
    pop_tcam_t1 = col_variation(tbl_bp, "pop_tcam_t1", label = "Pop T1", unit = "%/an"),
    pop_tcam_t2 = col_variation(tbl_bp, "pop_tcam_t2", label = "Pop T2", unit = "%/an"),
    pop_delta = col_variation(tbl_bp, "pop_delta", label = "Δ Pop", unit = "pts")
  )
)
Table 7: Comparaison bi-période par type de territoire — SMA, TCAM pop, SN
Qui accélère, qui freine ? Comparaison T1 (11-16) vs T2 (16-22)
Source : INSEE RP

Les moyennes sont pondérées par la population communale 2022. Le delta (Δ) mesure la variation en points de pourcentage entre T1 (2011-2016) et T2 (2016-2022). Un delta SMA positif signifie que l’attractivité migratoire s’améliore.

Les arrow charts révèlent des trajectoires divergentes entre types de territoire. Le rural autonome enregistre l’accélération SMA la plus forte (+0,15 pts), passant de +0,35 à +0,50 %/an — il dépasse désormais les couronnes en rythme de gain. Paradoxe : cette attractivité croissante ne compense pas le déficit naturel, et la population de ces communes bascule en déclin (TCAM population de +0,06 à −0,04 %/an). Les pôles améliorent aussi leur SMA (+0,13 pts) mais restent le seul type en perte migratoire nette (−0,10 %/an en T2) — l’hémorragie ralentit sans s’inverser.

À l’inverse, le rural périurbain est le seul type à voir son attractivité migratoire se dégrader (−0,12 pts). Son SMA passe de +0,41 à +0,29 %/an, et sa croissance démographique chute de moitié (+0,66 à +0,33 %/an). Ce recul contraste avec la dynamique des couronnes urbaines, qui restent le moteur de la périurbanisation (SMA le plus élevé à +0,57 %/an, +0,08 pts d’accélération). Le solde naturel se dégrade partout, mais l’ampleur varie : les pôles perdent peu (−0,04 pts) tandis que le rural périurbain décroche nettement (−0,33 pts en TCAM population).

La lecture croisée des trois onglets fait apparaître un mouvement structurel : la déconcentration résidentielle saute un échelon. Le rural autonome — longtemps éloigné des flux périurbains — capte désormais davantage que le rural de proximité. Ce basculement est cohérent avec les observations POPSU/PUCA sur la déconcentration post-2020, mais il s’agit d’un signal à confirmer sur la prochaine période intercensitaire.

Ces profils moyens sont-ils statistiquement robustes, ou la variabilité intra-type masque-t-elle les écarts entre types ?

Le type de territoire explique surtout le vieillissement et la natalité

Le type de territoire explique 10 % de la variabilité du vieillissement entre communes, mais seulement 1,3 % de celle du SMA. Les flux migratoires dépendent davantage de facteurs locaux que du gradient urbain-rural.

Code
rt_styled(
  kw_df %>% select(indicateur, chi2, p_label, eta2, n, significatif),
  title = "Le type de territoire explique-t-il les dynamiques ?",
  source = make_source("INSEE RP", "MIGCOM"),
  columns = list(
    indicateur = colDef(name = "Indicateur", minWidth = 180, style = list(fontWeight = "500")),
    chi2 = col_num("Chi²", digits = 1),
    p_label = colDef(name = "p-value", minWidth = 90),
    eta2 = col_num("eta²", digits = 4),
    n = col_num("N"),
    significatif = colDef(name = "Significatif",
      style = function(value) {
        if (value == "Oui") list(color = col_green, fontWeight = "600")
        else list(color = col_red)
      })
  )
)
Table 8: Tests de Kruskal-Wallis — Différences entre 4 types de territoire
Le type de territoire explique-t-il les dynamiques ?
Source : INSEE RP · MIGCOM

Le test de Kruskal-Wallis est très significatif attendu avec N > 30 000 communes. L’enjeu est l’eta² (effet de taille) : un eta² > 0,01 indique un effet faible mais détectable, > 0,06 un effet modéré, > 0,14 un effet large. Interpréter la significativité statistique à la lumière de la taille de l’effet.

Les quatre tests sont significatifs (p < 0,001) — attendu avec N > 30 000 communes. L’enjeu est la taille de l’effet. Le vieillissement et le solde naturel montrent des effets modérés : le type de territoire explique respectivement 10 % et 7,8 % de leur variabilité entre communes. En revanche, le SMA (1,3 %) et le taux de rotation (1,0 %) sont faiblement liés au type de territoire — la variabilité intra-type domine largement. Le gradient urbain-rural structure la démographie naturelle, mais il prédit mal les flux migratoires nets.

Code
ggplot(comm_typo %>% filter(!is.na(dm_sma_vtcam_1622)),
       aes(x = type_terr, y = dm_sma_vtcam_1622, fill = type_terr)) +
  geom_boxplot(alpha = 0.7, outlier.size = 0.5, outlier.alpha = 0.3) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#333") +
  scale_fill_manual(values = c(col_cyan, col_magenta, col_green, col_yellow), guide = "none") +
  coord_cartesian(ylim = quantile(comm_typo$dm_sma_vtcam_1622, c(0.01, 0.99), na.rm = TRUE)) +
  labs(x = NULL, y = "SMA TCAM (%/an)",
       caption = make_source("INSEE RP"))

Distribution du SMA par type de territoire
Code
ggplot(comm_typo %>% filter(!is.na(dmf_tr_pct_22)),
       aes(x = type_terr, y = dmf_tr_pct_22, fill = type_terr)) +
  geom_boxplot(alpha = 0.7, outlier.size = 0.5, outlier.alpha = 0.3) +
  scale_fill_manual(values = c(col_cyan, col_magenta, col_green, col_yellow), guide = "none") +
  coord_cartesian(ylim = quantile(comm_typo$dmf_tr_pct_22, c(0.01, 0.99), na.rm = TRUE)) +
  labs(
       x = NULL, y = "Taux de rotation (%)",
       caption = make_source("MIGCOM"))

Distribution du taux de rotation par type
Code
ggplot(comm_typo %>% filter(!is.na(dmv_iv_ind_22)),
       aes(x = type_terr, y = dmv_iv_ind_22, fill = type_terr)) +
  geom_boxplot(alpha = 0.7, outlier.size = 0.5, outlier.alpha = 0.3) +
  scale_fill_manual(values = c(col_cyan, col_magenta, col_green, col_yellow), guide = "none") +
  coord_cartesian(ylim = quantile(comm_typo$dmv_iv_ind_22, c(0.01, 0.99), na.rm = TRUE)) +
  labs(x = NULL, y = "Indice de vieillissement",
       caption = make_source("INSEE RP"))

Distribution de l’indice de vieillissement par type
Code
# Charger TYPO8pers
comm_typo8 <- comm %>%
  left_join(typo %>% mutate(CODGEO = as.character(CODGEO)) %>% select(CODGEO, TYPO8p),
            by = c("code" = "CODGEO")) %>%
  filter(!is.na(TYPO8p))

# Charger labels TYPO8pers
typo8_labels <- read.csv(
  file.path(PROJECT_ROOT, "data/external/geo-tdc/ptypo-dens-rural/tdc_geo_sat3col_TYPO8pers_2025.csv"),
  sep = ";", stringsAsFactors = FALSE, fileEncoding = "UTF-8-BOM"
)

comm_typo8 <- comm_typo8 %>%
  left_join(typo8_labels %>% select(code, lib), by = c("TYPO8p" = "code")) %>%
  rename(type_terr8 = lib) %>%
  filter(!is.na(type_terr8))

agg_typo8 <- agg_weighted_demog(comm_typo8, "type_terr8")

tbl_typo8 <- agg_typo8 %>%
  mutate(pop_pct = pop / sum(pop) * 100) %>%
  select(type_terr8, n_comm, pop_pct, pop_tcam, sma, sn, iv, tmi_cadre, tmi_65p) %>%
  arrange(desc(pop_pct))

rt_styled(
  tbl_typo8,
  title = "Profils détaillés — 8 sous-types de territoire",
  source = make_source("INSEE RP", "MIGCOM"),
  height = 400,
  columns = list(
    type_terr8 = colDef(name = "Sous-type", minWidth = 180, style = list(fontWeight = "500")),
    n_comm = col_num("Communes"),
    pop_pct = col_num("% pop", digits = 1),
    pop_tcam = col_variation(tbl_typo8, "pop_tcam", label = "TCAM pop", unit = "%/an"),
    sma = col_variation(tbl_typo8, "sma", label = "SMA", unit = "%/an"),
    sn = col_variation(tbl_typo8, "sn", label = "SN", unit = "%/an"),
    iv = col_num("IV", digits = 0),
    tmi_cadre = col_variation(tbl_typo8, "tmi_cadre", label = "TMI Cadres", unit = "pts", digits = 1),
    tmi_65p = col_variation(tbl_typo8, "tmi_65p", label = "TMI 65+", unit = "pts", digits = 1)
  )
)
Table 9: Profils démographiques détaillés — 8 sous-types TYPO8pers
Profils détaillés — 8 sous-types de territoire
Source : INSEE RP · MIGCOM

Les boxplots confirment l’interprétation des eta². Pour l’IV, les boîtes sont nettement séparées : la médiane du rural autonome (~160) ne chevauche pas l’intervalle interquartile des pôles (~85). Pour le SN, même séparation nette entre rural autonome (médiane négative) et pôles (médiane positive). En revanche, les distributions du SMA se chevauchent largement entre les 4 types — la dispersion intra-type est plus forte que l’écart entre types. Les pôles affichent la queue gauche la plus longue (communes-centres en forte perte migratoire), tandis que les couronnes ont la médiane la plus élevée.

ImportantBilan H3

H3 confirmée avec nuance : Le gradient urbain-rural structure fortement les dynamiques naturelles (SN, IV : eta² modéré à fort) mais faiblement les flux migratoires (SMA, TR : eta² < 0,015). Les pôles concentrent le SN+ et le brassage, les couronnes et le rural captent les flux nets. L’attractivité migratoire dépend davantage de facteurs locaux (emploi, cadre de vie, marché immobilier) que du type de territoire seul.

Synthèse

L’analyse croise trois niveaux géographiques — 287 zones d’emploi, 96 départements, ~34 000 communes — et cinq angles complémentaires : ralentissement macro, trajectoires migratoires, profils par catégorie, origines-destinations (à développer) et gradient urbain-rural. Le résultat convergent est clair : le ralentissement démographique est structurel et quasi-généralisé, mais sa décomposition en solde naturel et solde migratoire révèle des mécanismes différenciés selon l’échelon et le type de territoire.

Astuce6 constats principaux

1. Ralentissement généralisé — 68% des ZE croissent moins vite entre T1 et T2. Le TCAM national passe de +0,44% à +0,35%.

2. Compensation partielle — 133 ZE compensent un déficit naturel par la migration, mais seulement 51% des ZE vieillissantes (IV > 100) ont un SMA supérieur à leur déficit naturel.

3. Trajectoires persistantes — La corrélation SMA T1 × T2 est de 0,83 : les territoires attractifs le restent, les répulsifs aussi. Mais 120 ZE renforcent leur attractivité tandis que 25 s’enfoncent dans le déclin.

4. Attractivité cumulée, pas opposée — Les départements attractifs captent à la fois cadres et retraités (r = 0,61). Le clivage n’est pas cadres vs. retraités mais territoires attractifs vs. répulsifs globalement.

5. Brassage ≠ attractivité — La corrélation TR × SMA est de -0,12 : un fort taux de rotation ne garantit pas un solde migratoire positif.

6. Le gradient urbain-rural se recompose — Les pôles ont un SN+ mais perdent en migration nette (SMA-). Entre T1 et T2, le rural autonome accélère le plus en attractivité (+0,15 pts) tandis que le rural périurbain décélère (−0,12 pts) — la déconcentration résidentielle saute un échelon. Mais le type de territoire seul explique peu la variabilité du SMA (eta² < 0,015).

Bilan hypothèses

Hypothèse Résultat Parties
H1 : Le SMA compense partiellement le déficit naturel dans les territoires vieillissants Partiellement confirmée — SMA+ fréquent mais insuffisant dans 51% des cas. Le rural dispersé reste en déficit structurel P1
H2 : Les profils migratoires sont différenciés (cadres→métropoles, retraités→littoral) Nuancée — r(TMI cadres, TMI 65+) = 0,61 (positif). Attractivité cumulée plutôt que divergente. Les cadres sont plus géographiquement sélectifs (amplitude ×2,4) P3
H3 : Les flux suivent un gradient centre→périphérie Confirmée partiellement — KW significatif sur SN et IV (eta² modéré), faible sur SMA. Matrice OD à construire pour tester les flux directionnels P4 (à développer), P5

Données manquantes pour compléter l’analyse : matrice OD département × département (fichier détail MIGCOM), distribution des distances de migration, flux par type de territoire AAV (Sankey). Cf. ptvol-volet/pdemo/qeval-method-pdemo_260219.md.


Annexe — Données détaillées

Code
synth <- dep %>%
  summarise(
    across(
      c(dm_pop_vtcam_1622, dm_sma_vtcam_1622, dm_sn_vtcam_1622,
        dmv_iv_ind_22, dmf_tr_pct_22, dmf_tmi_cscadre_22, dmf_tmi_a65p_22),
      list(med = ~median(.x, na.rm = TRUE),
           q10 = ~quantile(.x, 0.10, na.rm = TRUE),
           q90 = ~quantile(.x, 0.90, na.rm = TRUE)),
      .names = "{.col}__{.fn}")
  ) %>%
  pivot_longer(everything(), names_to = c("var", "stat"), names_sep = "__") %>%
  pivot_wider(names_from = stat, values_from = value) %>%
  mutate(
    label = case_when(
      var == "dm_pop_vtcam_1622" ~ "TCAM population (%/an)",
      var == "dm_sma_vtcam_1622" ~ "SMA TCAM (%/an)",
      var == "dm_sn_vtcam_1622" ~ "SN TCAM (%/an)",
      var == "dmv_iv_ind_22" ~ "Indice de vieillissement",
      var == "dmf_tr_pct_22" ~ "Taux de rotation (%)",
      var == "dmf_tmi_cscadre_22" ~ "TMI Cadres (pts)",
      var == "dmf_tmi_a65p_22" ~ "TMI 65+ (pts)"
    ),
    ecart = q90 - q10
  ) %>%
  select(label, med, q10, q90, ecart)

rt_styled(
  synth,
  title = "Tableau récapitulatif — distributions départementales",
  source = make_source("INSEE RP", "MIGCOM"),
  columns = list(
    label = colDef(name = "Indicateur", minWidth = 180, style = list(fontWeight = "500")),
    med = col_num("Médiane", digits = 2),
    q10 = col_num("P10", digits = 2),
    q90 = col_num("P90", digits = 2),
    ecart = colDef(
      header = "Écart P90-P10",
      cell = bar_simple(max(abs(synth$ecart), na.rm = TRUE), fill = col_cyan, bar_width = 60),
      minWidth = 150
    )
  )
)
Table 10
Tableau récapitulatif — distributions départementales
Source : INSEE RP · MIGCOM