Prix, transactions, parc, construction et retournement du marché · France métropolitaine

Rapport
Auteur·rice

Vincent Roue

Date de publication

Édition septembre 2026

1 Synthèse

Logement — cadrage national — France métropolitaine, 101 départements · 306 zones d’emploi · 482 communes de plus de 20 000 habitants
Prix et transactions (DV3F, séries annuelles 2006-2025) — prix médian au m² et nombre de mutations
Parc (INSEE recensement 2023) — vacance, statut d’occupation · Énergie (ADEME DPE 2026)
Sources : DV3F · INSEE RP · ADEME · agrégation PTB

Synthèse nationale

Indicateurs clés · Logement · France Niveau 2025 et dynamique depuis 2022

Afficher le code
# Bandeau KPI = kpi_row (grille auto-fit) : 3 tuiles CHIFFRES + 1 tuile à DEUX MINI-GRAPHES.
# ⚠️ kpi_tiles_synth (jcn-rpt-kpi) = signature (category, items, footer) — À NE PAS CONFONDRE avec
#    kpi_tiles_psect (jcn-kpi-tiles, ex-psect) qui prend title/poids/dyn. Collision de noms résolue
#    par le renommage 260822 : ici on veut la version bandeau.
# ⚠️ Un ggplot NE PEUT PAS entrer dans une cellule de grille → gg_img() le convertit en PNG base64.
#
# ÉVOLUTIONS EN SOUS-LIGNE (item$note) : évolution TOTALE, jamais %/an — conv projet, un %/an sur
# 3 ans ne parle à personne alors qu'un « −9 % depuis 2022 » se retient. Deux références (2022 = le
# retournement, 2019 = l'avant-Covid) sur une ligne italique EN RETRAIT, qui dit visuellement
# qu'elle qualifie le chiffre au-dessus. Et la SPARKLINE est rattachée à SON indicateur (item$spark)
# plutôt qu'en pied de tuile, où l'on ne savait pas quelle courbe illustrait quel chiffre.
.tot  <- function(tcam, n) if (!is.finite(tcam)) NA_real_ else ((1 + tcam / 100)^n - 1) * 100
# forme COMPACTE « ▼ −3,3 % / 2022 » : le `ref` de kpi_evo pose déjà l'année en gris clair. Écrire
# « depuis 2022 » en toutes lettres faisait passer la note sur 3 lignes dans la colonne label.
# Deux lignes ASSUMÉES (<br>) plutôt qu'un séparateur « · » : la colonne label est étroite, le point
# médian se retrouvait orphelin en fin de ligne au gré du retour automatique.
.note2 <- function(t22, t19) paste0(
  kpi_evo(t22, "%", ref = "2022"),
  if (is.finite(t19)) paste0(" · ", kpi_evo(t19, "%", ref = "2019")) else "")
# Légende de sparkline : sans elle on ne sait pas quelle courbe illustre quel chiffre.
.sp_cap <- function(txt) sprintf(
  "<div style='font-size:.7rem;color:#9a9a9a;margin-bottom:1px'>%s</div>", txt)

.p_ix <- SER_FR$px_ix        # médiane des indices départementaux, base 100 = 2015
.p_tr <- SER_FR$trans / 1000 # transactions, milliers/an

# ⚠️ Transactions : les totaux nationaux se lisent sur la SOMME des départements (des stocks, la
# somme est exacte) — surtout PAS sur la médiane des TCAM départementaux, qui n'est pas le TCAM
# national et peut même en inverser le signe.
.trv <- stats::setNames(SER_FR$trans, as.character(SER_FR$annee))
sy_tr_t22 <- (.trv[["2025"]] / .trv[["2022"]] - 1) * 100
sy_tr_t19 <- (.trv[["2025"]] / .trv[["2019"]] - 1) * 100

# Évolutions totales du prix, dérivées des TCAM NATIFS de dbcln (aucune approximation) :
# (1+r1925)^6 = (1+r1922)^3 × (1+r2225)^3  →  la fenêtre 19-22, absente de dbcln, s'en déduit exactement.
sy_px_t2225 <- .tot(sy_px_ev, 3)
sy_px_t1922 <- ((1 + sy_px_evl / 100)^6 / (1 + sy_px_ev / 100)^3 - 1) * 100

# ⚠️ PIÈGE ggplot2 : theme_urbn() règle EXPLICITEMENT axis.title.x / axis.title.y. Or l'héritage de
# thème va du parent vers l'ENFANT, et l'enfant l'emporte → `axis.title = element_blank()` NE LES
# EFFACE PAS. Il faut viser les enfants.
# STYLE DÉPOUILLÉ (user 260825) : ni quadrillage, ni fond blanc, ni graduations — dans une tuile de
# 155 px ces éléments ne portent aucune information lisible et ne font qu'ajouter du bruit. Ne
# restent que la forme de la courbe et deux repères d'années. Fond TRANSPARENT pour épouser le bleu
# de la tuile au lieu d'y découper un rectangle blanc.
# Les mini-graphes ggplot de l'ancienne tuile « Le retournement » ont ete RETIRES : ils
# retracaient exactement les sparklines des tuiles 1 et 2. Leur habillage n'est pas perdu pour
# autant — il est promu dans mutils (`kpi_minichart()` / `kpi_minichart_ends()`, jcn-kpi-tiles.R)
# et reste utilisable partout. La tuile 4 accueille desormais la CONSTRUCTION. (260828)
.p_ct <- SER_CONSTR$logaut / 1000   # logements autorises, milliers/an

# delta SANS triangle : le nombre est deja signe, le triangle faisait doublon et debordait.
.dl <- function(t) kpi_evo(t, "%", ref = NULL, arrow = FALSE)

kpi_row(cols = 4,
  kpi_tiles_synth(kcat("currency-euro", "Prix médian au m²"),
    sub = "2025 · évol / 2022", compact = TRUE, delta_style = "inline",
    items = list(
      list(value = paste0(fmt_fr(sy_px, 0), " €"),             label = "Ensemble",
           delta = .dl(.tot(sy_px_ev, 3))),
      list(value = paste0(fmt_fr(KPI_LOG$px_apt_25, 0), " €"), label = "Appart.",
           delta = .dl(.tot(KPI_LOG$px_apt_ev, 3))),
      list(value = paste0(fmt_fr(KPI_LOG$px_mai_25, 0), " €"), label = "Maison",
           delta = .dl(.tot(KPI_LOG$px_mai_ev, 3)))),
    footer = kpi_sparkline(.p_ix, w = 150, h = 46, annot = TRUE, lab_left = "2015", lab_right = "2025",
                           annot_fmt = function(v) formatC(v, format = "f", digits = 0))),

  kpi_tiles_synth(kcat("arrow-left-right", "Transactions immobilières"),
    sub = "Mutations 2025 · évol / 2022", compact = TRUE, delta_style = "inline",
    items = list(
      list(value = fmt_k(KPI_LOG$trans_25),     label = "Ensemble",
           delta = .dl(sy_tr_t22)),
      list(value = fmt_k(KPI_LOG$trans_apt_25), label = "Appart.",
           delta = .dl(KPI_LOG$trans_apt_ev)),
      list(value = fmt_k(KPI_LOG$trans_mai_25), label = "Maisons",
           delta = .dl(KPI_LOG$trans_mai_ev))),
    footer = kpi_sparkline(.p_tr, col1 = "#8a939b", area = TRUE, zero_base = TRUE,
                           w = 150, h = 46, annot = TRUE, lab_left = "2015", lab_right = "2025", xlab = SER_CONSTR$annee,
                           annot_fmt = function(v) paste0(formatC(v, format = "f", digits = 0), " k"))),

  kpi_tiles_synth(kcat("house-door", "État du parc"),
    sub = "Parc · 2023-2026 · évol / 2017", compact = TRUE, delta_style = "inline",
    items = list(
      list(value = paste0(fmt_fr(sy_vac, 1), " %"),          label = "Logements vacants",
           delta = .dl(KPI_LOG$vac_evol)),
      list(value = paste0(fmt_fr(KPI_LOG$vac2ans, 1), " %"), label = "Vacants > 2 ans"),
      list(value = paste0(fmt_fr(sy_dpe, 1), " %"),          label = "Passoires F-G"))),

  # ⚠️ AUTORISÉS, pas commencés : entre le permis et le chantier il y a un délai, et une part des
  # permis n'est jamais mise en œuvre. Et la série SITADEL s'arrête en 2025, pas 2026 comme le DPE.
  # DOUBLE ÉVOLUTION (delta2) : / 2022 = la chute du retournement, / 2024 = le rebond en cours —
  # une seule référence cachait l'inflexion 2025 (base SITADEL 3 : rebond +15 % vs 2024).
  # sub porte DEJA les deux references -> la ligne d'en-tetes de colonnes ferait doublon (retiree,
  # user 260901). delta_style="inline" : deltas colles au libelle plutot qu'en rangee dediee.
  kpi_tiles_synth(kcat("building", "Logements autorisés"),
    sub = "2025 · évol / 2022 / 2024", compact = TRUE, delta_style = "inline",
    items = list(
      list(value = fmt_k(KPI_LOG$constr_25),     label = "Ensemble",
           delta = .dl(KPI_LOG$constr_ev),     delta2 = .dl(KPI_LOG$constr_ev24)),
      list(value = fmt_k(KPI_LOG$constr_ind_25), label = "Individuel",
           delta = .dl(KPI_LOG$constr_ind_ev), delta2 = .dl(KPI_LOG$constr_ind_ev24)),
      list(value = fmt_k(KPI_LOG$constr_m2_25), label = "m² autorisés",
           delta = .dl(KPI_LOG$constr_m2_ev),  delta2 = .dl(KPI_LOG$constr_m2_ev24))),
    footer = kpi_sparkline(.p_ct, col1 = "#8a6d3b", area = TRUE, zero_base = TRUE,
                           w = 150, h = 46, annot = TRUE, lab_left = "2015", lab_right = "2025", xlab = SER_CONSTR$annee,
                           annot_fmt = function(v) paste0(formatC(v, format = "f", digits = 0), " k"))))
Prix médian au m² 2025 · évol / 2022
2 616 € Ensemble -3,3 % -3,3 %
3 355 € Appart. -3,3 % -3,3 %
2 177 € Maison -1,5 % -1,5 %
Transactions immobilières Mutations 2025 · évol / 2022
782 k Ensemble -20,8 % -20,8 %
350 k Appart. -23,2 % -23,2 %
431 k Maisons -18,8 % -18,8 %
État du parc Parc · 2023-2026 · évol / 2017
7,8 % Logements vacants -0,3 % -0,3 %
3,4 % Vacants > 2 ans
9,2 % Passoires F-G
Logements autorisés 2025 · évol / 2022 / 2024
348 k Ensemble -20,8 % +15,2 %
117 k Individuel -33,3 % +10,5 %
25,8 M m² autorisés -29,1 % +11,9 %

Où le mètre carré est-il cher, et où décroche-t-il ? · départements

Afficher le code
.d_px <- build_map(.geo_dep_j, "px_25", tooltip_lab = "Prix médian au m²", bins = sy_bins_px, label_col = "nom_officiel",
    interactive = TRUE, hover_key = TRUE, legend_drop = FALSE, show_counts = FALSE,
    titre = "Niveau : prix médian au m² · 2025",
    subtitle = "€/m² · départements",
    source_txt = NULL, n_top = 3, n_bottom = 2, show_values = FALSE,
    label_style = "text", legend_title = "€/m²",
    legend_keywidth = 0.5, legend_keyheight = 0.17,
    contour_geo = GEO_REG, contour_lw = 0.35, contour_color = "#b0b0b0", dd = DD,
    zoom_box = BBOX_IDF) + th_map_sm
map_inset(.d_px, .geo_dep_j, "px_25", codes = INSET_IDF_PC, bins = sy_bins_px,
          code_col = "code_insee", label_col = "nom_officiel", dd = DD,
          points = FALSE, interactive = TRUE, pos = "topright") |>
  girafe_legend(sy_bins_px, width_svg = 4.4, height_svg = 4.35, title = "€/m²", zoom = TRUE)
€/m²clic & ctrl-clic
Afficher le code
.d_ev <- build_map(.geo_dep_j, "px_tcam_2225", tooltip_lab = "Évolution du prix", bins = sy_bins_ev, label_col = "nom_officiel",
    interactive = TRUE, hover_key = TRUE, legend_drop = FALSE, show_counts = FALSE,
    titre = "Dynamique : évolution du prix médian · 2022-2025",
    subtitle = "%/an · départements",
    source_txt = NULL, n_top = 3, n_bottom = 2,
    show_values = TRUE, auto_text_contrast = TRUE,
    label_style = "text", legend_title = "%/an",
    legend_keywidth = 0.5, legend_keyheight = 0.17,
    contour_geo = GEO_REG, contour_lw = 0.35, contour_color = "#b0b0b0", dd = DD,
    zoom_box = BBOX_IDF) + th_map_sm
map_inset(.d_ev, .geo_dep_j, "px_tcam_2225", codes = INSET_IDF_PC, bins = sy_bins_ev,
          code_col = "code_insee", label_col = "nom_officiel", dd = DD,
          points = FALSE, interactive = TRUE, pos = "topright") |>
  girafe_legend(sy_bins_ev, width_svg = 4.4, height_svg = 4.35, title = "%/an", zoom = TRUE)
%/anclic & ctrl-clic

Le même marché vu par les grandes villes · communes de plus de 50 000 habitants

Afficher le code
.m_px <- build_map_points(.cpx, "px_25", tooltip_lab = "Prix médian au m²", bins = sy_bins_px_cm,
    size_col = "pop_bulle", size_range = c(1.3, 9.5), point_border = "#8f8f8f", label_col = "libelle",
    label_exclude = .LBL_SKIP,
    label_style = "text", n_top = 2, n_bottom = 2,
    bg_geo = GEO_DEP, bg_fill = "#f7f6f2", bg_color = "#dedede", bg_lw = 0.22,
    contour_geo = GEO_REG, contour_color = "#c9c9c9", contour_lw = 0.3, interactive = TRUE, dd = DD,
    titre = "Niveau : prix médian au m² · 2025",
    subtitle = "€/m² · taille = population", source_txt = NULL,
    legend_title = "€/m²", show_counts = FALSE, terr_lbl = "communes") + th_map_sm
map_inset(.m_px, .inset_px, "px_25", codes = INSET_IDF_PC, bins = sy_bins_px_cm,
          bg_geo = GEO_DEP, size_col = "P22_POP", label_col = "libelle", dd = DD,
          point_border = "#8f8f8f", size_range = c(0.9, 3.4),
          interactive = TRUE, pos = "topright") |>
  girafe_legend(sy_bins_px_cm, width_svg = 4.4, height_svg = 4.35, title = "€/m²", zoom = TRUE)
€/m²clic & ctrl-clic
Afficher le code
.m_ev <- build_map_points(.cev, "px_tcam_2225", tooltip_lab = "Évolution du prix", bins = sy_bins_ev_cm,
    size_col = "pop_bulle", size_range = c(1.3, 9.5), point_border = "#8f8f8f", label_col = "libelle",
    label_exclude = .LBL_SKIP,
    label_style = "text", n_top = 2, n_bottom = 2,
    bg_geo = GEO_DEP, bg_fill = "#f7f6f2", bg_color = "#dedede", bg_lw = 0.22,
    contour_geo = GEO_REG, contour_color = "#c9c9c9", contour_lw = 0.3, interactive = TRUE, dd = DD,
    titre = "Dynamique : évolution du prix médian · 2022-2025",
    subtitle = "%/an · taille = population", source_txt = NULL,
    legend_title = "%/an", show_counts = FALSE, terr_lbl = "communes") + th_map_sm
map_inset(.m_ev, .inset_ev, "px_tcam_2225", codes = INSET_IDF_PC, bins = sy_bins_ev_cm,
          bg_geo = GEO_DEP, size_col = "P22_POP", label_col = "libelle", dd = DD,
          point_border = "#8f8f8f", size_range = c(0.9, 3.4),
          interactive = TRUE, pos = "topright") |>
  girafe_legend(sy_bins_ev_cm, width_svg = 4.4, height_svg = 4.35, title = "%/an", zoom = TRUE)
%/anclic & ctrl-clic

Lecture : chaque maille a sa propre echelle de couleurs. Encart en haut a droite : Paris et la petite couronne, a la maille arrondissement sur les cartes communales. Taille des bulles plafonnee a celle de Marseille. Survol pour le detail, clic sur une classe de la legende pour la filtrer.

Le logement par type de territoire Zonage INSEE en aires d’attraction des villes (CATEAAV 2020) · repère France en trait vertical

Afficher le code
.ty  <- TYPO_LOG[TYPO_LOG$type_lib != "France métropolitaine", ]
.fr  <- TYPO_LOG[TYPO_LOG$type_lib == "France métropolitaine", ]
.ser <- as.character(.ty$type_lib)
# ⚠️ Une ÉVOLUTION ne se représente pas par un databar : la barre part toujours vers la droite, si
# bien que −2,9 % et +0,9 % produisaient deux barres de même sens — illisible et faux. databar_psect
# prévoit exactement ça : `evol` accroche une PASTILLE signée (rose hausse / bleu baisse) au chiffre
# de niveau qu'elle qualifie. L'évolution des prix rejoint donc la colonne « Prix médian ». (260824)
.mk2 <- function(col, evol_col = NULL) list(
  vals = stats::setNames(.ty[[col]], .ser), ref = .fr[[col]][1],
  evol = if (!is.null(evol_col)) stats::setNames(.ty[[evol_col]], .ser) else NULL)
# ORDRE : le VOLUME d'abord (combien d'actes), le PRIX ensuite (à quel niveau) — on dit d'abord
# le poids du segment, puis sa valorisation. Chaque sous-titre porte SA source et SON millésime :
# la table croise du DV3F 2025, du recensement 2023 et du DPE 2026, et rien ne le signalait.
.ind <- list(
  # ⚠️ Pas de repère France sur un VOLUME : comparer les mutations d'un type au TOTAL national n'a
  # pas de sens (c'est une part, pas un écart). On coupe donc `ref` pour cette seule colonne.
  c(list(lbl = "Transactions", sub = "mutations · DV3F 2025 · pastille = évol. 22-25", unit = "", dec = 0),
    within(.mk2("logd_trans_25", "logd_trans_vevol_2225"), rm(ref))),
  c(list(lbl = "Prix médian au m²", sub = "€ · DV3F 2025 · pastille = évol. 22-25", unit = "", dec = 0),
    .mk2("logd_px2q2_glb_25", "logd_px2_glb_vevol_2225")),
  c(list(lbl = "Logements vacants", sub = "% · INSEE RP 2023 · pastille = évol. 17-23, en points", unit = "", dec = 1),
    .mk2("log_vac_pct_22", "log_vac_pct_vdifp_1622")),
  c(list(lbl = "% propriétaires",   sub = "% des résidences principales · INSEE RP 2023 · pastille = évol. 17-23, en points", unit = "", dec = 1),
    .mk2("log_prop_pct_22", "log_prop_pct_vdifp_1622")),
  c(list(lbl = "Passoires F-G",     sub = "% · DPE ADEME 2026", unit = "", dec = 1), .mk2("logd_dpe_fg_pct_26")))
# Une COULEUR PAR SÉRIE (et non 1 accent + du gris) : ici les 4 types se lisent comme un dégradé
# urbain -> rural, donc la couleur porte du sens en plus de distinguer les lignes. (modèle pbnb)
# target = FALSE : les 4 types sont des PAIRS, aucun n'est « la cible » — donc aucune ligne n'est
#   mise en avant, toutes ont le même aspect (défaut TRUE conservé pour psect/pterr : secteur vs France).
# ref_all = TRUE : le trait France est tracé sur CHAQUE barre, pas seulement la première — chaque
#   type se compare alors directement au repère national. L'étiquette « fr … » reste sur la 1re ligne.
cat(databar_psect(.ind, series = .ser, n_col = 5, ref_lab = "fr", col_header = FALSE,
                  pal = PAL_DATABAR_SYNTH, target = FALSE, ref_all = TRUE,
                  lbl_width = "132px"))
Transactions
Prix médian au m²
Logements vacants
% propriétaires
Passoires F-G
Commune-centre
207 546−19,8
2 400−1,2fr 2 616
9,6−0,6fr 7,8
42,3−0,1fr 59,0
7,3fr 8,9
Autre commune du pôle
157 725−24,7
3 583−8,4
6,3−0,1
49,4−0,8
8,0
Couronne périurbaine
299 767−19,3
2 279−2,2
7,2−0,4
71,7+0,0
9,9
Hors attraction (rural)
61 100−22,7
1 727+2,6
8,8−0,6
74,5+0,6
19,6

Lecture : une aire d’attraction se compose d’un pôle — la ville-centre et ses banlieues denses, dites « autres communes du pôle » — puis d’une couronne de communes dont les actifs viennent y travailler ; le reste est hors attraction. Les lignes vont donc du plus urbain au plus rural.
Les autres communes du pôle sont plus chères que les communes-centres (3 583 € contre 2 400 €) par un effet de composition : il n’existe qu’une commune-centre par aire, soit 689 — surtout des petites villes — quand les 1 017 communes de banlieue dense se concentrent dans les grandes agglomérations.

Afficher le code
# Encadré de messages-clés, modèle psect (`.encadre-synth` du thème canonical). Il REMPLACE les
# `.insight` + puces `.cld-add` : mêmes chiffres, mais rassemblés en un bloc de tête lisible seul.
.rur <- TYPO_LOG$logd_dpe_fg_pct_26[TYPO_LOG$type_lib == "Hors attraction (rural)"]
.cen <- TYPO_LOG$logd_dpe_fg_pct_26[TYPO_LOG$type_lib == "Commune-centre"]
cat("::: {.encadre-synth}
[Ce qu'il faut retenir]{.encadre-synth-titre}

")

Ce qu’il faut retenir

Afficher le code
cat(sprintf(
"- **Le marché s'est retourné en 2022** — après une hausse de **%s %%** entre 2019 et 2022, le prix médian recule de **%s %%** en trois ans, à **%s €** le m².
- **L'ajustement passe par les volumes, pas par les prix** — les ventes reculent de **%s %%** depuis 2022, soit six fois plus vite que les prix. Le marché se bloque avant de se déprécier.
- **La construction décroche encore plus fort** — **%s** logements autorisés en 2025, un recul de **%s %%** depuis 2022 et de **%s %%** pour l'individuel : c'est l'offre future qui se contracte.
- **Le rural tient les prix, au prix d'un parc dégradé** — seul type de territoire encore en hausse, il concentre **%s %%** de passoires énergétiques contre **%s %%** dans les communes-centres. Le calendrier d'interdiction de location va révéler cette fragilité.
",
  fmt_fr(sy_px_t1922, 0), fmt_fr(abs(sy_px_t2225), 0), fmt_fr(sy_px, 0),
  fmt_fr(abs(sy_tr_t22), 0),
  fmt_k(KPI_LOG$constr_25), fmt_fr(abs(KPI_LOG$constr_ev), 0), fmt_fr(abs(KPI_LOG$constr_ind_ev), 0),
  fmt_fr(.rur, 1), fmt_fr(.cen, 1)))
  • Le marché s’est retourné en 2022 — après une hausse de 18 % entre 2019 et 2022, le prix médian recule de 3 % en trois ans, à 2 616 € le m².
  • L’ajustement passe par les volumes, pas par les prix — les ventes reculent de 21 % depuis 2022, soit six fois plus vite que les prix. Le marché se bloque avant de se déprécier.
  • La construction décroche encore plus fort348 k logements autorisés en 2025, un recul de 21 % depuis 2022 et de 33 % pour l’individuel : c’est l’offre future qui se contracte.
  • Le rural tient les prix, au prix d’un parc dégradé — seul type de territoire encore en hausse, il concentre 19,6 % de passoires énergétiques contre 7,3 % dans les communes-centres. Le calendrier d’interdiction de location va révéler cette fragilité.
Afficher le code
cat("
:::

")

2 Méthode, données et axes d’analyse

Le marché du logement est lu à quatre échelles emboîtées, de la plus large à la plus fine : la France, le type de territoire (commune-centre, autre commune du pôle, couronne périurbaine, hors attraction), le département et la zone d’emploi, enfin la commune de plus de 20 000 habitants. Chaque partie thématique suit cette descente : d’abord la tendance nationale, puis sa géographie, enfin le détail communal.

Périmètre : France métropolitaine — 101 départements, 306 zones d’emploi et 482 communes de plus de 20 000 habitants.

Sources et millésimes : prix et transactions DV3F (séries annuelles 2006-2025, prix médian au m² et nombre de mutations) · parc et statut d’occupation INSEE recensement 2023 · performance énergétique ADEME DPE 2026 · construction Sitadel 3 (logements autorisés 2013-2025 et commencés 2013-2024, extraction SDES d’août 2026). Agrégation PTB.

Périodes de lecture : les cartes retiennent 2019-2025 (six ans, avant-COVID à aujourd’hui). L’analyse du retournement compare deux fenêtres de trois ans chacune2019-2022 (reprise) et 2022-2025 (retournement) — pour que les rythmes soient directement comparables.

Note de fiabilité — les zones blanches des cartes de prix ont deux causes distinctes. La Moselle, le Bas-Rhin et le Haut-Rhin n’apparaissent sur aucune carte de prix ni de transaction : ces trois départements relèvent du livre foncier d’Alsace-Moselle et non du fichier immobilier dont DV3F est extrait — l’absence est juridique et définitive, non un défaut de couverture. Ailleurs, le prix médian au m² n’est calculé que si le territoire enregistre assez de mutations dans l’année, ce qui laisse quelques communes de côté. La couverture atteint 92 % des départements et 92 % des communes de plus de 20 000 habitants. Les données de construction, elles, couvrent la France entière : les cartes de la partie construction n’ont aucune zone blanche.

3 Contexte : ce qui a retourné le marché

Le marché résidentiel français a connu deux régimes successifs en six ans. Comprendre le retournement de 2022 suppose de rappeler ce qui l’a provoqué : un choc de financement, puis un durcissement réglementaire du parc.

Le choc de financement : la fin de l’argent gratuit

Le taux moyen des crédits immobiliers passe d’environ 1,1 % fin 2021 à plus de 4 % fin 2023 — un quadruplement en deux ans.

La remontée des taux directeurs de la Banque centrale européenne, engagée en juillet 2022 pour contrer l’inflation, s’est transmise très vite au crédit immobilier. Le renchérissement du coût de l’emprunt a mécaniquement réduit la capacité d’achat des ménages : à mensualité constante, un acquéreur perd de l’ordre d’un cinquième de son pouvoir d’achat immobilier entre 2021 et 2023. La demande solvable s’est contractée, et le nombre de transactions avec elle.

L’effet ne joue pas partout de la même façon. Là où les prix sont élevés et la part d’acheteurs primo-accédants importante — grandes agglomérations, marchés tendus — la contrainte de financement mord davantage. Les marchés peu chers, où l’apport pèse moins lourd, encaissent mieux le choc.

Sources : Banque de France (taux des crédits nouveaux à l’habitat), INSEE (indice des prix à la consommation). Chiffres de cadrage à confirmer/actualiser avant diffusion.

Le durcissement réglementaire : le parc sous contrainte énergétique

Depuis 2023, les logements les plus énergivores sortent progressivement du marché locatif — un calendrier qui pèse sur la valeur du parc ancien.

La loi Climat et résilience organise l’interdiction progressive de location des passoires énergétiques : étiquette G depuis 2025, F à horizon 2028, E à horizon 2034. Le diagnostic de performance énergétique, opposable depuis 2021, est devenu un paramètre de prix : à caractéristiques comparables, un logement mal classé se négocie avec une décote, et le coût des travaux de rénovation entre dans la négociation.

Cette contrainte frappe inégalement les territoires : le parc ancien, rural et de maisons individuelles concentre les mauvaises étiquettes, quand le parc récent et collectif des zones denses s’en tire mieux. La partie consacrée au parc et à l’énergie quantifie cet écart.

S’y ajoutent des dispositifs qui déplacent la demande plutôt qu’ils ne la réduisent : encadrement des loyers dans les zones tendues, restrictions sur les meublés de tourisme dans les communes touristiques, extinction progressive du dispositif Pinel dans le neuf.

Sources : loi Climat et résilience (2021), calendrier DPE. Bonus à explorer : l’effet de la location de courte durée sur les marchés locaux nécessiterait les données de plateformes (projet pbnb) — périmètre communal et méthodologie distincts, non intégré à ce rapport.

4 Analyse

Transactions : le marché s’est-il grippé ?

Le nombre de ventes réagit avant les prix : quand le crédit se renchérit, les acquéreurs renoncent ou reportent, et le volume décroche pendant que les valeurs tiennent encore. C’est donc par les transactions que se lit le premier signal du retournement.

Trajectoire nationale et par type de territoire

Le marché a culminé en 2021 avant de reculer de 25 % en trois ans : un retournement brutal, et de même ampleur dans tous les types de territoire.

La reprise post-COVID avait porté les ventes à un niveau inédit — desserrement résidentiel, épargne accumulée, crédit encore bon marché. Le renversement de 2021 est synchrone : commune-centre, couronne ou rural reculent ensemble, ce qui signe une cause macro-financière et non un phénomène local. Le rural, parti de plus bas, amortit à peine mieux que les pôles.

Afficher le code
log_table_auto("serie_trans_typo", tr_idx[tr_idx$annee %in% c(2015, 2019, 2022, 2025),
                                          c("type_lib", "annee", "indice")], label_col = "type_lib")
plot_line_indice100(tr_idx, x = "annee", idx = "indice", serie = "type_lib",
  x_breaks = seq(2015, 2025, 2), points = TRUE, value_labels = FALSE,
  mean_ref = tr_ref, mean_lab = "France", mean_col = "#3a3a3a",
  title    = sprintf("Les ventes ont culminé en %d avant de décrocher partout", tr_pic),
  subtitle = sprintf("Nombre de mutations · indice base 100 = %d · par type de territoire — DV3F",
                     params$millesime_base),
  y_lab = sprintf("Indice (%d = 100)", params$millesime_base))

Départements et zones d’emploi

Le recul des ventes touche la quasi-totalité des départements : la médiane s’établit à -6,8 % par an depuis 2022.

La carte départementale donne la vue d’ensemble, la zone d’emploi révèle les contrastes que la moyenne départementale efface (figure ci-contre). L’effet de maille est net : quelques bassins résistent au sein de départements qui, globalement, décrochent.

Afficher le code
bins_tr <- auto_bins(c(DEP_LOG$trans_tcam_2225, ZE_LOG$trans_tcam_2225) |>
                       (\(x) x[is.finite(x)])(), var_name = "trans_tcam_2225")
log_map_auto("carte_trans_dep", "trans_tcam_2225", DEP_LOG, "libelle")
build_map(dplyr::left_join(GEO_DEP, DEP_LOG, by = c("code_insee" = "code")),
  "trans_tcam_2225", bins = bins_tr, label_col = "nom_officiel",
  titre = "Départements", subtitle = "Transactions · %/an 2022-2025",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Afficher le code
build_map(dplyr::left_join(GEO_ZE, ZE_LOG, by = c("ze2020" = "code")),
  "trans_tcam_2225", bins = bins_tr, label_col = "libze2020",
  titre = "Zones d'emploi", subtitle = "Transactions · %/an 2022-2025",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Communes de plus de 20 000 habitants

Afficher le code
cm <- COMM_PTS[is.finite(COMM_PTS$trans_tcam_2225), ]
log_describe_ranking("comm_trans_2225", sf::st_drop_geometry(cm), "libelle", "trans_tcam_2225")
build_map_points(cm, "trans_tcam_2225",
  size_col = "P22_POP", label_col = "libelle", label_style = "text",
  n_top = 4, n_bottom = 4, bg_geo = GEO_DEP, contour_geo = GEO_REG,
  titre = "Communes de plus de 20 000 habitants",
  subtitle = "Transactions · %/an 2022-2025 · taille = population",
  source_txt = make_source("DV3F · INSEE RP"), terr_lbl = "communes") + th_map_sm

À l’échelle communale, le recul des ventes est quasi général : 98 % des 442 communes de plus de 20 000 habitants enregistrent moins de mutations en 2025 qu’en 2022.

La dispersion reste toutefois marquée : entre la commune la plus touchée et celle qui résiste le mieux, l’écart atteint 51 points de croissance annuelle. Les marchés les plus chers et les plus dépendants de l’investissement locatif figurent parmi les plus exposés, tandis que les villes moyennes au foncier abordable amortissent le choc.

Prix : la hausse est-elle enrayée ?

Les prix réagissent avec retard au retournement des volumes : les vendeurs résistent à la baisse avant de céder. Cette partie lit d’abord le niveau — combien coûte le mètre carré, et où — puis l’évolution, pour distinguer les marchés qui poursuivent leur hausse de ceux qui corrigent.

Trajectoire nationale et par type de territoire

Le rural a le plus renchéri depuis 2015 (indice 128), les communes les plus chères le moins (114) : la hausse a resserré les écarts.

La décennie écoulée a joué contre la hiérarchie établie. Les territoires les moins chers — communes hors attraction des villes, à 1 727 € le m² en 2025 — ont connu la progression la plus vive, portée par le desserrement résidentiel et la recherche d’espace après 2020. À l’inverse, les communes du pôle les plus valorisées (3 583 € le m²) ont vu leur avance s’éroder.

Le retournement, lui, est visible sur la fin de courbe : après le pic de 2022, les indices s’aplatissent voire refluent, sans effacer les gains accumulés (figure ci-dessous).

Afficher le code
log_table_auto("serie_prix_typo", px_idx[px_idx$annee %in% c(2015, 2019, 2022, 2025),
                                         c("type_lib", "annee", "indice")], label_col = "type_lib")
px_ref <- data.frame(annee = SER_FR$annee, fr = SER_FR$px_ix)   # mediane des indices dep. (conv)
plot_line_indice100(px_idx, x = "annee", idx = "indice", serie = "type_lib",
  x_breaks = seq(2015, 2025, 2), points = TRUE, value_labels = FALSE,
  mean_ref = px_ref, mean_lab = "France", mean_col = "#3a3a3a",
  title    = "Le rural a rattrapé une partie de son retard sur les pôles",
  subtitle = sprintf("Prix médian au m² · indice base 100 = %d · par type de territoire — DV3F",
                     params$millesime_base),
  y_lab = sprintf("Indice (%d = 100)", params$millesime_base))

Départements et zones d’emploi : niveau

Un facteur 11,1 sépare Paris (9 706 €/m²) de Creuse (874 €/m²).

La géographie des prix est celle de la tension : Île-de-France, littoral méditerranéen, façade atlantique et couronnes alpines forment un arc de valeurs élevées, tandis que la diagonale intérieure reste sous les 1 500 € le m². La zone d’emploi révèle des îlots que la moyenne départementale masque — stations littorales ou de montagne enclavées dans des départements abordables (figure ci-contre).

Afficher le code
bins_px <- auto_bins(c(DEP_LOG$px_25, ZE_LOG$px_25) |> (\(x) x[is.finite(x)])(), var_name = "px_25")
build_map(dplyr::left_join(GEO_DEP, DEP_LOG, by = c("code_insee" = "code")),
  "px_25", bins = bins_px, label_col = "nom_officiel",
  titre = "Départements", subtitle = "Prix médian au m² · 2025 (€)",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Afficher le code
build_map(dplyr::left_join(GEO_ZE, ZE_LOG, by = c("ze2020" = "code")),
  "px_25", bins = bins_px, label_col = "libze2020",
  titre = "Zones d'emploi", subtitle = "Prix médian au m² · 2025 (€)",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Départements et zones d’emploi : évolution 2019-2025

Sur six ans, la hausse reste générale — mais elle s’est concentrée sur les marchés abordables, pas sur les plus chers.

La lecture en évolution inverse presque la carte des niveaux : les départements les plus chers affichent les progressions les plus faibles, quand l’intérieur rural enregistre les hausses les plus fortes. Le mouvement de rattrapage amorcé pendant la crise sanitaire s’y lit directement.

Afficher le code
bins_pe <- auto_bins(c(DEP_LOG$px_tcam_1925, ZE_LOG$px_tcam_1925) |> (\(x) x[is.finite(x)])(),
                     var_name = "px_tcam_1925")
log_map_auto("carte_prix_evol_dep", "px_tcam_1925", DEP_LOG, "libelle")
build_map(dplyr::left_join(GEO_DEP, DEP_LOG, by = c("code_insee" = "code")),
  "px_tcam_1925", bins = bins_pe, label_col = "nom_officiel",
  titre = "Départements", subtitle = "Prix médian · %/an 2019-2025",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Afficher le code
build_map(dplyr::left_join(GEO_ZE, ZE_LOG, by = c("ze2020" = "code")),
  "px_tcam_1925", bins = bins_pe, label_col = "libze2020",
  titre = "Zones d'emploi", subtitle = "Prix médian · %/an 2019-2025",
  source_txt = make_source("DV3F"), n_top = 3, n_bottom = 3,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Communes de plus de 20 000 habitants

Afficher le code
cp <- COMM_PTS[is.finite(COMM_PTS$px_25), ]
log_describe_ranking("comm_prix_25", sf::st_drop_geometry(cp), "libelle", "px_25")
build_map_points(cp, "px_25",
  size_col = "P22_POP", label_col = "libelle", label_style = "text",
  n_top = 4, n_bottom = 4, bg_geo = GEO_DEP, contour_geo = GEO_REG,
  titre = "Communes de plus de 20 000 habitants",
  subtitle = "Prix médian au m² · 2025 (€) · taille = population",
  source_txt = make_source("DV3F · INSEE RP"), terr_lbl = "communes") + th_map_sm

À l’échelle des grandes villes, le prix médian s’établit à 3 181 € le m². L’écart reste considérable : les 10 % les plus chères dépassent 5 404 € quand les 10 % les plus abordables restent sous 1 652 €, soit un rapport de 3,3.

Cette maille est la plus parlante pour un lecteur : elle isole les marchés urbains de leur environnement rural, et fait apparaître des situations que le département noie — une ville moyenne tendue dans un département abordable, ou l’inverse.

Le logement selon huit lectures du territoire

Un même parc se décrit très différemment selon la grille qu’on lui applique. Les quatre catégories d’aires d’attraction utilisées jusqu’ici disent l’essentiel du gradient urbain-rural, mais elles ignorent ce qui structure vraiment un marché du logement : la tension réglementaire, la proximité de la mer, la pression touristique, la taille de la ville.

La correction des prix est proportionnelle à la tension : la zone Abis perd 9,4 % depuis 2022 quand la zone C en gagne 1,4 %.

Le zonage A/B/C — celui qui commande les dispositifs fiscaux et l’encadrement des loyers — donne la lecture la plus nette. Le prix médian va de 6 003 € le m² en zone Abis à 1 630 € en zone C, un facteur 3,7. Et le retournement suit exactement le même ordre : plus un marché était tendu, plus il corrige.

Deux grilles apportent ce que l’urbain-rural ne voit pas. Les communes hyper-touristiques (plus de 40 % de résidences secondaires) affichent 4 185 € le m² — au niveau des grandes agglomérations — et continuent de progresser de 5,6 % quand le reste recule. Le littoral joue le même rôle.

À l’inverse, les bourgs et villages de moins de 3 500 habitants, qui rassemblent 31 618 communes, cumulent le prix le plus bas (1 875 €) et la part de passoires énergétiques la plus élevée (15,5 %) : un parc abordable mais coûteux à remettre aux normes.

Afficher le code
.tb <- TYPO_RECAP[, c("bloc", "lib", "n_comm", "px_25", "px_ev", "vac_22", "dpe_fg")]
names(.tb) <- c("bloc", "Type de territoire", "Communes", "Prix médian",
                "Évol. prix", "Vacance", "Passoires F-G")
gt_styled(.tb, groupname_col = "bloc",
  title = "Le zonage de tension classe les marchés mieux que l'opposition urbain-rural",
  subtitle = "Prix médian au m² 2025 · évolution totale 2022-2025 · parc RP 2023 · DPE 2026",
  source = "DV3F · INSEE RP · ADEME · grille de typologies TDC-ref") |>
  gt::fmt_number(c("Communes"), decimals = 0, sep_mark = " ") |>
  gt::fmt_number(c("Prix médian"), decimals = 0, sep_mark = " ", pattern = "{x} €") |>
  gt::fmt_number(c("Évol. prix", "Vacance", "Passoires F-G"), decimals = 1,
                 dec_mark = ",", pattern = "{x} %") |>
  gt::data_color(columns = "Évol. prix", palette = c("#2166ac", "#f7f7f7", "#b2182b"),
                 domain = c(-10, 10)) |>
  gt::tab_options(quarto.disable_processing = TRUE)
Le zonage de tension classe les marchés mieux que l’opposition urbain-rural
Prix médian au m² 2025 · évolution totale 2022-2025 · parc RP 2023 · DPE 2026
Type de territoire Communes Prix médian Évol. prix Vacance Passoires F-G
References
Petite couronne (92-93-94) — communes limitrophes Paris 27 6 857 € −10,3 % 6,9 % 11,8 %
Ruralites & centralites
rural periurbain (AAV>=50k) 14 449 2 039 € −1,4 % 7,3 % 12,2 %
rural non periurbain 16 147 1 658 € 2,2 % 9,1 % 18,2 %
Rural (peu dense) — isole : hors attraction villes 8 897 1 727 € 2,6 % 8,8 % 19,6 %
dense 741 3 551 € −5,7 % 7,8 % 8,3 %
intermediaire 3 580 2 518 € −3,0 % 7,6 % 7,4 %
Geographie & pression touristique
Zones touristiques — touristique modere (15-40%) 7 673 2 656 € 4,3 % 8,4 % 13,8 %
Zones touristiques — hyper-touristique (>40%) 2 513 4 185 € 5,6 % 3,1 % 11,4 %
Aires d attraction
Paris — pole 355 5 238 € −4,3 % 7,2 % 11,8 %
Paris — couronne 1 590 2 730 € −8,5 % 6,7 % 11,5 %
Aires >=700k hors Paris — pole 413 3 060 € −3,8 % 7,9 % 5,8 %
Aires >=700k hors Paris — couronne 7 953 2 622 € −3,2 % 6,3 % 7,5 %
Aires 200-700k — pole 224 1 946 € −0,5 % 9,5 % 7,2 %
Aires 200-700k — couronne 7 392 1 846 € −0,1 % 7,6 % 11,7 %
Autres aires 7 825 1 824 € 1,4 % 9,1 % 12,6 %
Tension immobiliere
Tendu (Abis+A) 1 001 4 179 € −4,2 % 6,2 % 7,4 %
Zone Abis 132 6 003 € −9,4 % 6,6 % 11,7 %
Zone A 869 3 881 € −4,2 % 6,2 % 6,1 %
Intermediaire (B1+B2) 5 545 2 429 € −2,8 % 7,6 % 7,3 %
Zone B1 2 383 2 667 € −3,0 % 7,5 % 6,6 %
Zone B2 3 162 1 899 € −1,3 % 8,0 % 9,1 %
Detendu (C) 28 326 1 630 € 1,4 % 9,2 % 14,8 %
Taille de ville
Bourg ou village (<3.5k) 31 618 1 875 € 0,0 % 7,9 % 15,5 %
Petite commune (3.5-10k) 2 145 2 559 € −3,5 % 7,2 % 8,1 %
Petite ville (10-20k) 529 2 761 € −4,7 % 7,0 % 7,1 %
Ville moyenne (20-50k) 346 3 111 € −6,4 % 7,6 % 7,9 %
Ville (50-100k) 98 3 587 € −4,7 % 7,4 % 7,4 %
Grande ville (100-200k) 39 3 176 € −4,1 % 9,4 % 10,0 %
Metropole (>=200k) 9 3 927 € −3,9 % 8,3 % 6,4 %
Centralites INRAE
Centre majeur 175 2 975 € −2,4 % 9,2 % 8,4 %
Centre structurant 742 3 337 € −6,4 % 7,3 % 7,9 %
Centre intermediaire 2 877 2 601 € −3,8 % 7,1 % 8,4 %
Centre local 6 999 2 171 € −1,3 % 7,1 % 12,0 %
Centres fragiles (score 31-45) 2 456 1 547 € 0,8 % 11,3 % 12,3 %
Centres mod. fragiles (16-30) 5 480 2 560 € −2,0 % 7,4 % 7,4 %
Centres peu fragiles (0-15) 2 851 3 673 € −7,4 % 6,5 % 9,0 %
Programmes nationaux
France Ruralites Revitalisation 19 979 1 818 € 2,5 % 9,8 % 14,8 %
Plus Beaux Villages de France 191 2 166 € 7,1 % 8,3 % 15,3 %
Petites Cites de Caractere 206 1 863 € 2,0 % 9,5 % 12,7 %
Station Classee de Tourisme 472 3 667 € 1,6 % 6,9 % 6,6 %
Commune Touristique 1 096 2 505 € −1,7 % 8,1 % 7,5 %
Ville Art et Histoire 118 2 722 € −1,3 % 9,2 % 6,6 %
Ville Fleurie 4 fleurs 75 2 500 € −1,9 % 8,9 % 7,2 %
DV3F · INSEE RP · ADEME · grille de typologies TDC-ref

Lecture : les lignes agrégées de la grille de référence (« Zones touristiques » = modérée et hyper-touristique réunies) sont écartées — on peut sommer des stocks, pas des médianes de prix. Les programmes Petites Villes de Demain et Action Cœur de Ville figurent dans la grille mais n’ont aucune donnée de prix dans la source : ils n’apparaissent donc pas.

Parc et énergie : quel état du bâti ?

Au-delà des prix et des volumes, la valeur d’un marché tient à l’état de son parc : un logement vacant ne se vend pas, une passoire énergétique se décote et sortira du marché locatif. Cette partie mesure ces contraintes structurelles et leur géographie.

Vacance et passoires : deux fragilités qui se cumulent dans le rural

Le rural cumule les handicaps : 8,8 % de logements vacants et 19,6 % de passoires énergétiques, contre 9,6 % et 7,3 % dans les communes-centres.

La vacance signale un parc qui ne trouve pas preneur — logements trop anciens, mal situés ou trop coûteux à remettre en état. Elle est structurellement plus élevée hors des aires d’attraction, où la demande est faible et le bâti ancien.

Les passoires énergétiques (étiquettes F et G) suivent la même géographie, pour une raison mécanique : le rural concentre les maisons individuelles construites avant les premières réglementations thermiques. Avec le calendrier d’interdiction de location, ce parc devient invendable au prix d’hier sans travaux — un facteur de décote qui pèsera d’autant plus que la demande locale est déjà faible.

Afficher le code
log_map_auto("carte_dpe_ze", "logd_dpe_fg_pct_26", ZE, "libelle")
build_map(dplyr::left_join(GEO_ZE, ZE[, c("code", "logd_dpe_fg_pct_26")], by = c("ze2020" = "code")),
  "logd_dpe_fg_pct_26", bin_type = "auto", label_col = "libze2020",
  titre = "Les passoires énergétiques dessinent la France rurale et de montagne",
  subtitle = "Part des logements classés F ou G · 2026 (%)",
  source_txt = make_source("ADEME DPE"), n_top = 4, n_bottom = 4,
  label_style = "text", show_values = FALSE, legend_keywidth = 0.62, legend_keyheight = 0.2,
  contour_geo = GEO_REG, contour_lw = 0.4, contour_color = "#b0b0b0", dd = DD) + th_map_sm

Lecture : part des diagnostics de performance énergétique classés F ou G dans le parc diagnostiqué de la zone d’emploi. Un taux élevé signale un parc ancien exposé au calendrier d’interdiction de location de la loi Climat et résilience.

Prix et performance énergétique : les marchés chers ont le parc le plus sain

La part de passoires recule à mesure que le prix au m² monte : les territoires valorisés disposent d’un parc plus récent ou davantage rénové.

Le croisement confirme un cercle qui s’auto-entretient. Un parc performant soutient la valeur, la valeur finance la rénovation. À l’inverse, un parc dégradé décote, et la décote décourage l’investissement de rénovation — piège dont les marchés détendus peinent à sortir.

Afficher le code
ds <- dplyr::left_join(ZE_LOG[, c("code", "libelle", "px_25")],
                       ZE[, c("code", "logd_dpe_fg_pct_26", "P22_POP")], by = "code")
ds <- ds[is.finite(ds$px_25) & is.finite(ds$logd_dpe_fg_pct_26), ]
log_scatter_auto("scatter_prix_dpe", ds, "px_25", "logd_dpe_fg_pct_26", "libelle")
plot_scatter(ds, x = "px_25", y = "logd_dpe_fg_pct_26",
  label_col = "libelle", size_col = "P22_POP",
  n_labels = 10, label_mode = "quadrant_extremes",
  ref_lines = "none", mean_lines = TRUE, engine = "ggplot2",
  title    = "Plus le marché est cher, moins le parc compte de passoires",
  subtitle = "Prix médian m² 2025 × part d'étiquettes F-G 2026 · zones d'emploi · taille = population",
  xlab = "Prix médian au m² 2025 (€) →", ylab = "Part de passoires F-G (%)",
  caption = make_source("DV3F · ADEME"))

La construction neuve : un rebond qui part de très bas

Le parc décrit dans la partie précédente est un stock ; la construction est le robinet qui l’alimente. Deux questions structurent cette partie : le retournement de 2022 a-t-il fermé ce robinet durablement, et l’effort de construction se porte-t-il là où le parc est déjà tendu ?

Le retournement a fermé le robinet, 2025 le rouvre à peine

348 000 logements autorisés en 2025 : un rebond de 15 % en un an, après une chute d’un tiers entre 2022 et 2024.

La remontée des taux a frappé la production neuve encore plus fort que les transactions : entre 2022 et 2024, les autorisations perdent 31,3 %, le point bas de toute la série. Le rebond de 2025 (+15,2 % sur un an) est réel mais part de très bas : le niveau reste 20,8 % sous celui de 2022 et 23,3 % sous le pic de 2017 (figure ci-contre). La courbe des mises en chantier suit le même profil avec un étage de moins — une part des permis n’est jamais mise en œuvre, et le chantier suit l’autorisation avec des mois de délai.

Afficher le code
.sc <- SER_CONSTR
ggplot(.sc, aes(x = annee)) +
  geom_col(aes(y = logaut, fill = "Logements autorisés"), width = 0.62) +
  geom_line(aes(y = logcom, colour = "Logements commencés"), linewidth = 0.9, na.rm = TRUE) +
  geom_point(aes(y = logcom, colour = "Logements commencés"), size = 1.7, na.rm = TRUE) +
  scale_fill_manual(values = c("Logements autorisés" = "#b08d57")) +
  scale_colour_manual(values = c("Logements commencés" = "#54595f")) +
  scale_x_continuous(breaks = .sc$annee) +
  scale_y_continuous(labels = function(x) paste0(x / 1000, " k")) +
  labs(title = "Un tiers d'autorisations en moins entre 2022 et 2024, un premier rebond en 2025",
       subtitle = "Logements autorisés (barres) et commencés (courbe) · France métropolitaine · données collectées, non estimées",
       x = NULL, y = NULL, fill = NULL, colour = NULL,
       caption = make_source("SDES Sitadel 3, extraction 08/2026")) +
  theme_urbn() + theme(legend.position = "top")

Lecture : logements autorisés = permis délivrés, l’indicateur le plus exhaustif au niveau local ; logements commencés = chantiers ouverts, remontés avec un délai d’environ dix-huit mois — le millésime 2025 n’est pas encore collecté et n’est donc pas affiché. Série SDES en base Sitadel 3 (bascule mars 2026) : niveaux non comparables aux chiffres publiés avant cette date.

L’effort de construction fuit les marchés déjà tendus

La médiane départementale s’établit à 8,6 logement autorisé par an pour 1 000 logements existants — Val-de-Marne en produit 2,0 fois plus.

Rapportées au parc existant, les autorisations 2022-2025 dessinent une géographie qui ne recouvre ni celle des prix ni celle de la tension : l’effort le plus intense se lit dans Val-de-Marne, Haute-Savoie et Haute-Corse (17,3 à 15,4 pour 1 000), quand Paris, Nièvre et Haute-Marne renouvellent leur parc deux à trois fois moins vite (cartes ci-dessous). La maille des zones d’emploi affine le constat : les bassins littoraux et périurbains concentrent la production, les bassins de tradition industrielle et les centres les plus chers construisent peu.

Afficher le code
log_map_auto("carte_constr_tx_dep", "logs_logaut_tx1klog22_2225", CT_DEP, "libelle")
.m_ct_d <- build_map(.geo_dep_ct, "logs_logaut_tx1klog22_2225",
    tooltip_lab = "Logements autorisés /1000 logements", bins = ct_bins_dep, label_col = "nom_officiel",
    interactive = TRUE, hover_key = TRUE, legend_drop = FALSE, show_counts = FALSE,
    titre = "Taux de construction : logements autorisés pour 1 000 logements · 2022-2025",
    subtitle = "moyenne annuelle /1000 logements (parc 2022) · départements",
    source_txt = NULL, n_top = 3, n_bottom = 2, show_values = FALSE,
    label_style = "text", legend_title = "/1000",
    legend_keywidth = 0.5, legend_keyheight = 0.17,
    contour_geo = GEO_REG, contour_lw = 0.35, contour_color = "#b0b0b0", dd = DD,
    zoom_box = BBOX_IDF) + th_map_sm
map_inset(.m_ct_d, .geo_dep_ct, "logs_logaut_tx1klog22_2225", codes = INSET_IDF_PC, bins = ct_bins_dep,
          code_col = "code_insee", label_col = "nom_officiel", dd = DD,
          points = FALSE, interactive = TRUE, pos = "topright") |>
  girafe_legend(ct_bins_dep, width_svg = 4.4, height_svg = 4.35, title = "/1000", zoom = TRUE)
/1000clic & ctrl-clic
Afficher le code
log_map_auto("carte_constr_tx_ze", "logs_logaut_tx1klog22_2225", CT_ZE, "libelle")
.m_ct_z <- build_map(.geo_ze_ct, "logs_logaut_tx1klog22_2225",
    tooltip_lab = "Logements autorisés /1000 logements", bins = ct_bins_ze, label_col = "libelle",
    interactive = TRUE, hover_key = TRUE, legend_drop = FALSE, show_counts = FALSE,
    titre = "Taux de construction : logements autorisés pour 1 000 logements · 2022-2025",
    subtitle = "moyenne annuelle /1000 logements (parc 2022) · zones d'emploi",
    source_txt = NULL, n_top = 3, n_bottom = 2, show_values = FALSE,
    label_style = "text", legend_title = "/1000",
    legend_keywidth = 0.5, legend_keyheight = 0.17,
    contour_geo = GEO_REG, contour_lw = 0.35, contour_color = "#b0b0b0", dd = DD) + th_map_sm
girafe_legend(.m_ct_z, ct_bins_ze, width_svg = 4.4, height_svg = 4.35, title = "/1000", zoom = TRUE)
/1000clic & ctrl-clic

Lecture : le taux rapporte la production au parc existant (et non aux habitants) — il se lit comme un rythme de renouvellement du stock, et il ne flatte pas les territoires à forte vacance. Chaque maille a sa propre échelle de couleurs. Survol ou clic sur la légende pour isoler une classe. Ce sont les seules cartes du rapport sans zone blanche : Sitadel couvre la France entière, quand la Moselle, le Bas-Rhin et le Haut-Rhin sont absents des cartes de prix et de transactions — ces trois départements relèvent du livre foncier d’Alsace-Moselle et non du fichier immobilier, donc des bases DVF.

Point mort : à quoi a servi ce qui a été construit ?

Sur 2017-2023, 66 % de la construction française a servi à maintenir la population en place — pas à en accueillir.

Le point mort est le nombre de logements qu’il faut produire juste pour que rien ne change : compenser le desserrement des ménages (985 000 logements sur la fenêtre), la disparition ou transformation de logements, et l’absorption par la vacance (35 000) et les résidences secondaires (205 000). Sur 2,04 million de logements commencés entre 2016 et 2021, seuls 702 000 ont effectivement servi à loger de la population supplémentaire — un ratio production / point mort de 1,52.

Décomposition standard des programmes locaux de l’habitat (PLH) : construits = desserrement + renouvellement + Δ vacance + Δ résidences secondaires + effet démographique. Le desserrement = logements nécessaires pour loger la population de départ à la taille des ménages d’arrivée ; le renouvellement = construits − variation du parc total (il se calcule et peut être négatif : divisions de logements, transformations de locaux). Fenêtre : recensements 2017 et 2023, numérateur = logements commencés cumulés 2017-2022 (Sitadel 3, données collectées). Le bouclage des cinq composantes redonne exactement les logements commencés. Le point mort est une méthode, pas une norme officielle : les composantes varient légèrement selon les bureaux d’études — la formule utilisée ici est celle ci-dessus, sans ajustement.

Le retournement : qui décroche depuis 2022 ?

Les parties précédentes ont établi le double mouvement — volumes en chute, prix qui s’aplatissent. Reste à savoir qui décroche. En comparant deux fenêtres de trois ans, 2019-2022 puis 2022-2025, on distingue les territoires qui ont simplement ralenti de ceux qui ont vraiment basculé.

Deux temps du marché : la hausse s’est arrêtée net

Le rythme des prix passe de +5,7 % à +0,2 % par an entre les deux périodes : la hausse ne ralentit pas, elle s’arrête.

L’écart entre les deux fenêtres est sans ambiguïté. Sur 2019-2022, la quasi-totalité des départements progressaient de plus de 4 % par an. Sur 2022-2025, la médiane tombe à +0,2 % et 41 départements passent en territoire négatif.

Le nuage ci-dessous croise les deux rythmes : chaque point sous la diagonale a ralenti, et tout point sous l’axe horizontal baisse désormais. La dispersion verticale montre que le freinage n’est pas uniforme — c’est là que se lit la différenciation territoriale.

Afficher le code
log_scatter_auto("scatter_retourn_dep", dd_ok, "px_tcam_2225", "px_tcam_1922", "libelle")
plot_scatter(dd_ok, x = "px_tcam_2225", y = "px_tcam_1922",
  label_col = "libelle", size_col = "px_25",
  n_labels = 12, label_mode = "quadrant_extremes",
  ref_lines = "auto", ref_x = 0, ref_y = 0, mean_lines = FALSE, engine = "ggplot2",
  title    = "La hausse d'hier ne garantit pas la tenue d'aujourd'hui",
  subtitle = sprintf("Prix médian m² · %%/an %d-%d (vertical) et %d-%d (horizontal) · départements · taille = niveau de prix",
                     params$retourn_p1[1], params$retourn_p1[2], params$retourn_p2[1], params$retourn_p2[2]),
  xlab = sprintf("Rythme %d-%d (%%/an) →", params$retourn_p2[1], params$retourn_p2[2]),
  ylab = sprintf("Rythme %d-%d (%%/an)", params$retourn_p1[1], params$retourn_p1[2]),
  caption = make_source("DV3F"))

Afficher le code
zz <- ZE_LOG[is.finite(ZE_LOG$px_tcam_1922) & is.finite(ZE_LOG$px_tcam_2225), ]
plot_scatter(zz, x = "px_tcam_2225", y = "px_tcam_1922",
  label_col = "libelle", size_col = "px_25",
  n_labels = 12, label_mode = "quadrant_extremes",
  ref_lines = "auto", ref_x = 0, ref_y = 0, mean_lines = FALSE, engine = "ggplot2",
  title    = "À l'échelle des bassins, le freinage se lit plus contrasté",
  subtitle = "Prix médian m² · rythmes comparés · zones d'emploi · taille = niveau de prix",
  xlab = sprintf("Rythme %d-%d (%%/an) →", params$retourn_p2[1], params$retourn_p2[2]),
  ylab = sprintf("Rythme %d-%d (%%/an)", params$retourn_p1[1], params$retourn_p1[2]),
  caption = make_source("DV3F"))

Afficher le code
cc <- sf::st_drop_geometry(COMM_PTS)
cc <- cc[is.finite(cc$px_tcam_1922) & is.finite(cc$px_tcam_2225), ]
plot_scatter(cc, x = "px_tcam_2225", y = "px_tcam_1922",
  label_col = "libelle", size_col = "P22_POP",
  n_labels = 12, label_mode = "quadrant_extremes",
  ref_lines = "auto", ref_x = 0, ref_y = 0, mean_lines = FALSE, engine = "ggplot2",
  title    = "Les grandes villes concentrent les corrections les plus nettes",
  subtitle = sprintf("Prix médian m² · rythmes comparés · %d communes de plus de 20 000 habitants", nrow(cc)),
  xlab = sprintf("Rythme %d-%d (%%/an) →", params$retourn_p2[1], params$retourn_p2[2]),
  ylab = sprintf("Rythme %d-%d (%%/an)", params$retourn_p1[1], params$retourn_p1[2]),
  caption = make_source("DV3F · INSEE RP"))

Lecture : chaque point est un territoire. En bas à droite, les marchés qui accélèrent ou tiennent ; en haut à gauche, ceux qui montaient fort et corrigent le plus brutalement. La diagonale sépare l’accélération du ralentissement.

Qui décroche, qui résiste ?

Les marchés les plus chers corrigent, les moins chers tiennent : le retournement rebat la hiérarchie construite pendant la décennie de hausse.

Le décrochage le plus marqué frappe Loire-Atlantique, Rhône et Finistère — des marchés qui perdent respectivement 12,1, 10,9 et 10,6 points de rythme annuel entre les deux périodes.

À l’inverse, Hautes-Alpes, Ardennes et Aveyron maintiennent une progression de +4,2 % à +2,6 % par an. Le point commun de ces marchés résistants : un niveau de prix bas, donc une contrainte de financement moins mordante pour les acquéreurs.

Afficher le code
sel <- rbind(head(dd_ok[order(dd_ok$bascule), ], 8), head(dd_ok[order(-dd_ok$bascule), ], 8))
sel$lib <- sel$libelle
log_describe_ranking("arrow_retourn_dep", sel, "libelle", "bascule")
plot_arrow(sel[order(sel$px_tcam_2225), ], "px_tcam_1922", "px_tcam_2225",
  title    = "Les huit départements qui freinent le plus, face aux huit qui tiennent",
  subtitle = sprintf("Prix médian m² · rythme %d-%d (rond) puis %d-%d (pointe), en %%/an",
                     params$retourn_p1[1], params$retourn_p1[2], params$retourn_p2[1], params$retourn_p2[2]),
  xlab     = "Rythme annuel des prix (%/an)",
  caption  = make_source("DV3F"), n_highlight = 3, n_detail = 2)

Tableau de synthèse par type de territoire

Le tableau referme la partie en réunissant, pour chaque type de territoire, les indicateurs travaillés dans les quatre thématiques : niveau de prix, dynamique, vacance, propriétaires, performance énergétique.

Afficher le code
log_table_auto("synth_typo_logement", TYPO_LOG, label_col = "type_lib")
tbl_typo_transpose(
  TYPO_LOG, TBL_INDICS_LOG,
  focus_lab = "France métropolitaine",
  title    = "Le logement par type de territoire, toutes dimensions",
  subtitle = "France métropolitaine · prix et transactions DV3F 2025, parc RP 2023, DPE 2026",
  source   = paste0(make_source("DV3F · INSEE RP · ADEME"),
                    " · barre = position relative dans la colonne · ● polarité"))
Le logement par type de territoire, toutes dimensions
France métropolitaine · prix et transactions DV3F 2025, parc RP 2023, DPE 2026
Type de territoire
.
Prix m2 global
€/m² · 2025
△ Prix m² glob
%/an · 22-25
Nb transactions
nb · 2025
Tx vacance
% · 2022
% Propriétaires
% · 2022
% Passoires F+G
% · 2026
Commune-centre
2 400,0
-0,4
207 546,0
9,6
42,3
7,3
Autre commune du pôle
3 583,3
-2,9
157 725,0
6,3
49,4
8,0
Couronne périurbaine
2 279,4
-0,7
299 767,0
7,2
71,7
9,9
Hors attraction (rural)
1 727,3
+0,9
61 100,0
8,8
74,5
19,6
France métropolitaine
2 615,9
-1,1
726 138,0
7,8
59,0
8,9
Source : DV3F · INSEE RP · ADEME · barre = position relative dans la colonne · ● polarité

Lecture : chaque colonne est un indicateur ; la barre situe le type de territoire au sein de la colonne. La couleur de remplissage suit la polarité (vert = favorable si élevé, bordeaux = défavorable, gris = neutre). La ligne France métropolitaine sert de référence.