DREAL Normandie Demande Potentielle en Logements
  • Contexte et objectifs
  • Méthodo
    • Le zonage d’étude
    • La demande potentielle en logements - le concept
    • Les projections de ménages
    • Les projections du parc
  • Résultats
    • Résultat régional
    • Résultats par département
    • Résultats par zone d’étude
    • Les fiches territoire

Contenu

  • Estimation de la demande potentielle en logements à horizon 2035
  • Décomposition par effets
  • Age et composition familiale des ménages
    • Age
    • Composition familiale

Estimation de la demande potentielle en logements à horizon 2035

La prolongation des tendances récentes amène à une estimation de la demande potentielle en logements de l’ordre de 7 000 logements par an pour la Normandie à l’horizon 2035. Si l’on fait varier sensiblement les hypothèses de mode de cohabitation des ménages, des estimations « hautes » et « basses » permettent d’encadrer ce résultat entre 2 600 et 9 700 logements par an à l’échelle régionale.

Code
data_DPL_scenarios_t = transpose (data_DPL_scenarios)

// mettre à jour librairie plot pour faire des round corners
Plot = import("https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm")

// doc :
// https://observablehq.com/@observablehq/input-table
Inputs.table(data_DPL_scenarios_t,{
  header: {
    "valeur": "Nb de logements / an"
  },
  format: {
    "valeur": d => Math.round(d)
  }
})
Code
Plot.plot({
  marginLeft: 100,
  marginRight: 100,
  marginTop: 50,
  //height: 1100,
  width: 450,
  style: {
    fontSize: 12,
  },
  y: {domain:[0,14000], ticks: 7, tickFormat: d => d.toLocaleString('fr-FR'),grid: true, label: "Nb de logements / an"},
  x: {label: "", inset: 70},
  title: "Estimation de la demande potentielle en logements à horizon 2035 (moyenne annuelle)",
  //subtitle: "Subtitle to follow with additional context",

  marks: [
    Plot.barY(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y1: "Tendanciel BAS", 
      y2: "Tendanciel HAUT", 
      fill: "steelblue",
      r: 5
    }),
    Plot.dot(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel BAS",
      fill: "steelblue",
      symbol: "diamond",
      r: 10,
      tip: {
        format: {          
          y: d => Math.round(d).toLocaleString('fr-FR'),                  
                }
           }
    }),
    Plot.dot(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel HAUT",
      fill: "steelblue",
      symbol: "diamond",
      r: 10,
      tip: {
        format: {          
          y: d => Math.round(d).toLocaleString('fr-FR'),                  
                }
           }
    }),
    Plot.dot(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel CENTRAL",
      fill: "black",
      symbol: "star",
      r: 5,
      tip: {
        format: {          
          y: d => Math.round(d).toLocaleString('fr-FR'),                  
                }
           }
    }),
    
    Plot.ruleY([0]),
    
    Plot.text(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel CENTRAL", 
      dx: 45,
      text: d => `Scénario central à\n ${Math.round(d["Tendanciel CENTRAL"]).toLocaleString('fr-FR')} logt/an`,
     // stroke:"white, 
      fill:"black", 
      fontSize :13,       
      textAnchor: "start" 
    }),
    
    Plot.text(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel BAS", 
      dx: 45,
      text: d => `Scénario bas à\n ${Math.round(d["Tendanciel BAS"]).toLocaleString('fr-FR')} logt/an`,
     // stroke:"white, 
      fill:"black", 
      fontSize :13,       
      textAnchor: "start" 
    }),
    
    Plot.text(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Tendanciel HAUT", 
      dx: 45,
      text: d => `Scénario haut à\n ${Math.round(d["Tendanciel HAUT"]).toLocaleString('fr-FR')} logt/an`,
     // stroke:"white, 
      fill:"black", 
      fontSize :13,       
      textAnchor: "start" 
    }),
    
    Plot.tickY(data_DPL_scenarios_t, {
      x: "Libellé zonage",
      y: "Logts ordinaires commencés /an sur [2015-2019]",
      stroke: "orange",
      strokeWidth: 2,
      tip: {
        format: {          
          y: d => Math.round(d).toLocaleString('fr-FR'),                  
                }
           }
      }),
    
     Plot.text(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Logts ordinaires commencés /an sur [2015-2019]", 
      dx: 45,
      text: d => ` ${Math.round(d["Logts ordinaires commencés /an sur [2015-2019]"]).toLocaleString('fr-FR')} logts/an [2015-2019]`,
     // stroke:"white, 
      fill:"orange", 
      fontSize :13,       
      textAnchor: "start" 
    }),
    
    Plot.tickY(data_DPL_scenarios_t, {
      x: "Libellé zonage",
      y: "Logts ordinaires commencés /an sur [2020-2023]",
      stroke: "green",
      strokeWidth: 2,
      tip: {
        format: {          
          y: d => Math.round(d).toLocaleString('fr-FR'),                  
                }
           }
      }),
    
     Plot.text(data_DPL_scenarios_t, {
      x: "Libellé zonage", 
      y: "Logts ordinaires commencés /an sur [2020-2023]", 
      dx: 45,
      text: d => ` ${Math.round(d["Logts ordinaires commencés /an sur [2020-2023]"]).toLocaleString('fr-FR')} logts /an [2020-2023]`,
     // stroke:"white, 
      fill:"green", 
      fontSize :13,       
      textAnchor: "start" 
    }),
  ]
})

Décomposition par effets

Si l’on s’intéresse au scénario central, on peut connaître de façon plus précise l’origine de cette demande.

Code
data_long_central_ojs = transpose (data_long_central)

// doc :
// https://observablehq.com/@observablehq/input-table
Inputs.table(data_long_central_ojs,{
  header: {
    "valeur": "Nb de logements / an"
  },
  format: {
    "valeur": d => Math.round(d)
  }
})

Décomposition de l’évolution annuelle du nombre de ménages en Normandie selon le type d’effet (scénario central)


Code
colorPalette = { return {
  "Ménage par an – effet nouvelle population": "#83caff",  // Bleu
  "Ménage par an – effet déformation pyramide des âges": "#9999ff",  // Violet
  "Ménage par an – effet évolution des modes de cohabitation": "#198a8a",  // Cyan
  "Renouvellement par an": "#c5000b",  // Rouge
  "Vacance par an": "#ffd320",  // Jaune
  "Résidences Secondaires et Logts Occasion. par an": "#81d41a"  // Vert
}};

Plot.plot({
  marginLeft: 100,
  marginRight: 200,
  //height: 1100,
  width: 500,
  style: {
    fontSize: 12,
  },
  x: {label: ""},
  y: {grid: true, 
      label: "Nb de logts / an",
      tickFormat: y => y.toLocaleString('fr-FR')
     },
  color: {legend: true, domain: Object.keys(colorPalette), range: Object.values(colorPalette)},

  marks: [
    Plot.barY(data_long_central_ojs.filter(d => d.effet != "Moyenne annuelle Demande Potentielle logements"), {
      x: "Libellé zonage", 
      y: "valeur", 
      fill: "effet", 
      tip: true
    }),
    Plot.ruleY([0]),

    // Cercle pour le fond de texte
    Plot.dotY(data_long_central_ojs.filter(d => d.effet == "Moyenne annuelle Demande Potentielle logements"), {
      x: "Libellé zonage", 
      y: "valeur",
      fill: "white",
      stroke: "black",
      r:20
    }),
    
    Plot.text(data_long_central_ojs.filter(d => d.effet == "Moyenne annuelle Demande Potentielle logements"), {
      x: "Libellé zonage", 
      y: "valeur", 
      //text: "valeur",
      text: d => Math.round(d.valeur).toLocaleString('fr-FR'),
      fontSize :11, 
      textAnchor: "middle" 
    })

  ]
  // color: {legend: {title: "Catégories", position: "right"}}
})
Code
moyenne_regionale = data_long_central_ojs.filter(d => d.effet == "Moyenne annuelle Demande Potentielle logements")[0].valeur.toLocaleString('fr-FR')

La valeur moyenne de la région est de logements par an pour le scénario central.

On constate que la baisse du nombre d’habitants entraîne une baisse du nombre de ménages d’environ - 3000 ménages par an. Cette baisse est néanmoins largement compensée par l’augmentation du nombre de ménages liée au vieillissement de la population (+ 4 500 ménages par an) et à la décohabitation (+ 3 400 ménages par an).

L’évolution du parc de logements devrait générer une demande potentielle de + 1 800 logements par an, dont + 500 liés au besoin de renouvellement et aux mutations du parc, + 500 liés à l’évolution de la vacance et + 800 liés à l’évolution des résidences secondaires et logements occasionnels.

À l’échelle de la région, l’évolution des ménages (= résidences principales) représente 75 % de la demande potentielle en logements et l’évolution du parc en représente 25 %.


Age et composition familiale des ménages

Age

Entre 2018 et 2035, le nombre de ménages de plus de 65 ans va considérablement augmenter sur la région, alors que les autres tranches d’âge diminuent.

Code
data_long_age_ojs = transpose (data_long_age)

// doc :
// https://observablehq.com/@observablehq/input-table
Inputs.table(data_long_age_ojs,{
  header: {
    "valeur": "Nb de logements / an"
  },
  format: {
    "valeur": d => Math.round(d)
  }
})
Code
Plot.plot({
  label: null,
  marginLeft: 100,
  width: 900,
  x: {
    label: "nb ménages /an sur la période 2018-2035",
    labelAnchor: "center",
    x: (d) => d.valeur.toLocaleString('fr-FR'),
    grid: true,
    tickFormat: y => y.toLocaleString('fr-FR')
  },
  y: { domain: ["> 80 ans","65-79 ans","50-64 ans","35-49 ans","< 35 ans"], 
       axis : "left" },
  color: {scheme: "PiYG", type: "ordinal"},
  subtitle: "Variation annuelle du nombre de ménages selon leur âge",
  
  marks: [
    Plot.barX(data_long_age_ojs, {
      x: "valeur",
      y: "tranche_age",
      r: 3,
      channels: {nb_menages: d => d.valeur.toLocaleString('fr-FR'), age: "tranche_age"},
      tip: { format: {
                x: false,
                y: false,
                fill: false
                      }},
      fill: (d) => d.valeur>0
    }),
    
    Plot.ruleX([0])
  ]
})
Code
data_long_age_2018_t = transpose (data_long_age_2018)
data_long_age_2035_t = transpose (data_long_age_2035)
Code
temp_2018 = data_long_age_2018_t.map(objet => ({
  ...objet,
  annee: "2018" // en texte parce que c'est pas des choses que je veux additioner ou mettre en continu
}));

// On ajoute la propriété année à 2018
temp_2035 = data_long_age_2035_t.map(objet => ({
  ...objet,
  annee: "2035"
}));

// on fusionne les 2 tableaux
data_long_age_2018_2035_t = [...temp_2018, ...temp_2035]
Code
Plot.plot({
  label: null,
  marginLeft: 50,
  width: 400,
  height: 200,
  y: {
    label: "nb ménages",
    grid: true,
    tickFormat: y => y.toLocaleString('fr-FR')
  },
  x: { domain: ["< 35 ans", "35-49 ans","50-64 ans","65-79 ans","> 80 ans"]},
  color: {
    legend: true,
    domain: ["2018","2035"], // les catégories de fill
    range: ["steelblue", "#b00465"] // les couleurs que tu choisis
  },
  subtitle: "Evolution de l'âge des ménages entre 2018 et 2035",
  
  marks: [
    Plot.lineY(data_long_age_2018_2035_t, {
      x: "tranche_age",
      y: d => Number(d.valeur.replace(/\s/g, "")),
      r :3,
      stroke: "annee"
    }),
    
    Plot.dot(data_long_age_2018_2035_t, {
      x: "tranche_age",
      y: d => Number(d.valeur.replace(/\s/g, "")),
      tip: true,
      fill: "annee"
    }),
    
    Plot.ruleY([0]),
    Plot.axisX({y: 0})
    
  ]
})

Composition familiale

Entre 2018 et 2035, le nombre de ménages de personne seule va considérablement augmenter.

En 2035, les ménages de personne seule devraient représenter 42 % de l’ensemble des ménages de la région.

Code
data_long_moco_ojs = transpose (data_long_moco)

// doc :
// https://observablehq.com/@observablehq/input-table
Inputs.table(data_long_moco_ojs,{
  header: {
    "valeur": "Nb de logements / an"
  },
  format: {
    "valeur": d => Math.round(d)
  }
})
Code
colorPalette2 = { return {
  "Couple avec ou sans enfant": "#81d41a",  // vert
  "Personne seule": "#800080",  // Violet
  "Famille monoparentale": "#729fcf",  // bleu
  "Personne hors famille": "#8d281e"  // marron
}};

Plot.plot({
  label: null,
  marginLeft: 200,
  width: 900,
  x: {
    label: "nb ménages /an sur la période 2018-2035",
    labelAnchor: "center",
    x: (d) => d.valeur.toLocaleString('fr-FR'),
    tickFormat: y => y.toLocaleString('fr-FR'),
    grid: true
  },
  y: { domain: ["Personne hors famille","Famille monoparentale","Personne seule","Couple avec ou sans enfant"], 
       axis : "left" },
  color: {legend: true, domain: Object.keys(colorPalette2), range: Object.values(colorPalette2)},
  subtitle: "Variation annuelle du nombre de ménages selon leur composition familiale",
  
  marks: [
    Plot.barX(data_long_moco_ojs, {
      x: "valeur",
      y: "mode_cohab",
      r: 3,
      channels: {nb_menages: d => d.valeur.toLocaleString('fr-FR')},
      tip: { format: {
                x: false,
                y: true,
                fill: false
                      }},
      fill: "mode_cohab"
    }),

    Plot.ruleX([0])
  ]
})
Code
data_long_moco_2035_ojs = transpose (data_long_moco_2035)

// doc :
// https://observablehq.com/@observablehq/input-table
/*Inputs.table(data_long_moco_2035_ojs,{
  header: {
    "valeur": "Nb de logements"
  },
  format: {
    "valeur": d => Math.round(d)
  }
})*/
Code
total_number = Number( data_long_moco_2035_ojs
                          .filter(d => d.mode_cohab == 'total' )[0]
                          .valeur
                          .replace(/\s/g , '')
                        )

Plot.plot({
  subtitle: "Composition des ménages en 2035",
  marginLeft: 100,
  width: 600,
  height:300,
  color: {legend: true, domain: Object.keys(colorPalette2), range: Object.values(colorPalette2)},
  y: {
    label: "nb ménages en 2035",
    grid: true,
    tickFormat: y => y.toLocaleString('fr-FR')
  },
  x: { domain: ["Couple avec ou sans enfant", "Personne seule", "Famille monoparentale", "Personne hors famille"], label: "" },
  
  marks: [
    Plot.barY(data_long_moco_2035_ojs.filter(d => d.mode_cohab != 'total' ), {
      x: "mode_cohab",
      y: d => Number(d.valeur.replace(/\s/g, "")),
      fill: "mode_cohab",
      tip: { format: {fill: false}},
      r: 3
    }),
    
    Plot.text(data_long_moco_2035_ojs.filter(d => d.mode_cohab != 'total'), {
      text: d => `${(d.valeur.replace(/\s/g, "") / total_number * 100).toFixed(1) } %`, // toFixed de 1 pour 1 décimale
      y: d => Number(d.valeur.replace(/\s/g, "")),
      x: "mode_cohab",
      textAnchor: "middle",
      lineAnchor: "top",
      fontWeight: "bold",
      fontSize: 15,
      dy: -13,
      fill: "black"
    })
  ]
})
Retour au sommet