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
Choix de la zone d’étude
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")

viewof myTerritoire = Inputs.select(data_DPL_scenarios_t, {format: d => d.zone_DPL, label: "", width: 400})

territoire = data_DPL_scenarios_t.filter(d => d.zone_DPL == myTerritoire.zone_DPL)

Le territoire de aurait un besoin de logements par an pour le scénario central.

Code
logt_comm_periode =[
  {
    "Libellé zonage":territoire[0]["Libellé zonage"],
    periode:"lgts commencés 2015-2019", 
    "Logts ordinaires commencés /an":territoire[0]["Logts ordinaires commencés /an sur [2015-2019]"]
  },
  {
    "Libellé zonage":territoire[0]["Libellé zonage"],
    periode:"lgts commencés 2020-2023 (Sitadel)", 
    "Logts ordinaires commencés /an":territoire[0]["Logts ordinaires commencés /an sur [2020-2023]"]
  }
]
Code
Plot.plot({
  marginLeft: 100,
  marginRight: 100,
  marginTop: 50,
  height: 300,
  width: 450,
  style: {
    fontSize: 12,
  },
  y: {tickFormat: d => d.toLocaleString('fr-FR'),grid: true, label: "Nb de logements / an"},
  x: {label: "", inset: 70},
  color: {
    legend: true,
    domain: ["lgts commencés 2015-2019","lgts commencés 2020-2023 (Sitadel)"], // les catégories de fill
    range: ["orange", "#16fa53"] // les couleurs que tu choisis
  },
  subtitle: "Estimation de la demande potentielle en logements à horizon 2035 (moyenne annuelle)",

  marks: [
    Plot.barY(territoire, {
      x: "Libellé zonage", 
      y1: "Tendanciel BAS", 
      y2: "Tendanciel HAUT", 
      fill: "steelblue",
      r: 5
    }),
    
    Plot.dot(territoire, {
      x: "Libellé zonage", 
      y: "Tendanciel BAS",
      fill: "steelblue",
      symbol: "diamond",
      r: 10,
      tip: false
    }),
    
    Plot.dot(territoire, {
      x: "Libellé zonage", 
      y: "Tendanciel HAUT",
      fill: "steelblue",
      symbol: "diamond",
      r: 10,
      tip: false
    }),
    
    Plot.dot(territoire, {
      x: "Libellé zonage", 
      y: "Tendanciel CENTRAL",
      fill: "black",
      symbol: "star",
      r: 5,
      tip: false
    }),
    
    Plot.ruleY([0]),
    
    Plot.tip(
      [`Scénario central à ${myTerritoire["Tendanciel CENTRAL"]} logt/an`],
      {x: "Libellé zonage", y: myTerritoire["Tendanciel CENTRAL"], dy: 3, anchor: "left"}
    ),
    
    Plot.tip(
      [`Scénario haut à ${myTerritoire["Tendanciel HAUT"]} logt/an`],
      {x: "Libellé zonage", y: myTerritoire["Tendanciel HAUT"], dy: 3, anchor: "left"}
    ),
    
    Plot.tip(
      [`Scénario bas à ${myTerritoire["Tendanciel BAS"]} logt/an`],
      {x: "Libellé zonage", y: myTerritoire["Tendanciel BAS"], dy: 3, anchor: "left"}
    ),

    Plot.tickY(logt_comm_periode, {
      x: "Libellé zonage",
      y: "Logts ordinaires commencés /an",
      stroke: "periode",
      strokeWidth: 2,
      fill: "periode",
      tip: true
      })
    
  ]
})
Code
data_long_central_ojs = transpose (data_long_central)

territoire2 = data_long_central_ojs.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])

// territoire2

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
}};
Code
Plot.plot({
  marginLeft: 100,
  marginRight: 200,
  height: 300,
  width: 400,
  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)},
  subtitle: "Origine de la demande potentielle (scénario central)",

  marks: [
    Plot.barY(territoire2.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(territoire2.filter(d => d.effet == "Moyenne annuelle Demande Potentielle logements"), {
      x: "Libellé zonage", 
      y: "valeur",
      fill: "white",
      stroke: "black",
      r:20
    }),
    
    Plot.text(territoire2.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" 
    })

  ]

})
Code
data_long_age_2018_t = transpose (data_long_age_2018)
data_long_age_2035_t = transpose (data_long_age_2035)


// On ajoute la propriété année à 2018
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]

// pour mémoire, on récupère du texte dans valeur, on les retransforme en nombre du coup...

territoire3 = data_long_age_2018_2035_t.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])
Code
Plot.plot({
  label: null,
  marginLeft: 70,
  width: 500,
  height: 160,
  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: "Age de la personne de référence des ménages",
  
  marks: [
    Plot.lineY(territoire3, {
      x: "tranche_age",
      y: d => Number(d.valeur.replace(/\s/g, "")),
      stroke: "annee"
    }),
    
    Plot.dot(territoire3, {
      x: "tranche_age",
      y: d => Number(d.valeur.replace(/\s/g, "")),
      tip: true,
      fill: "annee"
    }),
    
    Plot.ruleY([0]),
    Plot.axisX({y: 0})
    
  ]
})
Code
data_long_age_ojs = transpose (data_long_age)

territoire4 = data_long_age_ojs.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])
Code
Plot.plot({
  label: "",
  marginLeft: 100,
  marginRight: -20,
  marginBottom: 50,
  width: 500,
  height: 200,
  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(territoire4, {
      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_moco_2035_ojs = transpose (data_long_moco_2035)

territoire5 = data_long_moco_2035_ojs.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])
Code
total_number = Number( territoire5
                          .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: 160,
  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(territoire5.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: 5
    }),
    
    Plot.text(territoire5.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"
    })
  ]
})
Code
data_long_moco_ojs = transpose (data_long_moco)

territoire6 = data_long_moco_ojs.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])
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: 150,
  marginBottom: 50,
  width: 600,
  height: 150,
  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 type",
  
  marks: [
    Plot.barX(territoire6, {
      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])
  ]
})


Les hypothèses utilisées
Code
data_DPL_hypothese_t = JSON.parse(data_DPL_hypothese_json)

territoire7 = data_DPL_hypothese_t.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])
Code
import { aq, op } from '@uwdata/arquero'

// https://observablehq.com/@uwdata/an-illustrated-guide-to-arquero-verbs

// 1ère étape on met la donnée au format arquero avec aq.from
dt_territoire7 = aq.from(territoire7);

// 2ème étape, on chaîne les opérations et on ajuste selon les résultats
// je te met un exemple pour arrondir mais là tu peux tout faire : renommer, réordonner, changer l'unité, pivoter etc...

// cf doc du lien au dessus, c'est fold pour pivoter à la vertical, tes données seront jamais lisible à l'horizontale

dt_territoire7_pivot = dt_territoire7
  .derive({
    "Nombre de ménages au 1er janvier 2018": aq.escape(d=> (Math.round(d["Nombre de ménages au 1er janvier 2018"])).toLocaleString('fr-FR') ),
    "Nombre de ménages au 1er janvier 2035 – scénario BAS": aq.escape(d=> (Math.round(d["Nombre de ménages au 1er janvier 2035 – scénario BAS"])).toLocaleString('fr-FR') ),
    "Nombre de ménages au 1er janvier 2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Nombre de ménages au 1er janvier 2035 – scénario CENTRAL"])).toLocaleString('fr-FR') ),
    "Nombre de ménages au 1er janvier 2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Nombre de ménages au 1er janvier 2035 – scénario HAUT"])).toLocaleString('fr-FR') ),
    "Evol. Ménages 2018-2035 – scénario BAS": aq.escape(d=> (Math.round(d["Evol. Ménages 2018-2035 – scénario BAS"])).toLocaleString('fr-FR') ),
    "Evol. Ménages 2018-2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Evol. Ménages 2018-2035 – scénario CENTRAL"])).toLocaleString('fr-FR') ),
    "Evol. Ménages 2018-2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Evol. Ménages 2018-2035 – scénario HAUT"])).toLocaleString('fr-FR') ),
    "Taux accroissement annuel des ménages – scénario BAS": aq.escape(d=> (Math.round(d["Taux accroissement annuel des ménages – scénario BAS"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux accroissement annuel des ménages – scénario CENTRAL": aq.escape(d=> (Math.round(d["Taux accroissement annuel des ménages – scénario CENTRAL"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux accroissement annuel des ménages – scénario HAUT": aq.escape(d=> (Math.round(d["Taux accroissement annuel des ménages – scénario HAUT"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux de renouvellement annuel retenu – scénario BAS": aq.escape(d=> (Math.round(d["Taux de renouvellement annuel retenu – scénario BAS"]*100000)/1000).toLocaleString('fr-FR') + "%"),
    "Taux de renouvellement annuel retenu – scénario CENTRAL": aq.escape(d=> (d["Taux de renouvellement annuel retenu – scénario CENTRAL"]*100).toLocaleString('fr-FR', { minimumFractionDigits: 3, maximumFractionDigits: 3 }) + "%"),
    "Taux de renouvellement annuel retenu – scénario HAUT": aq.escape(d=> (d["Taux de renouvellement annuel retenu – scénario HAUT"]*100).toLocaleString('fr-FR', { minimumFractionDigits: 3, maximumFractionDigits: 3 }) + "%"),
    "Besoins annuels liés au renouvellement du parc 2021-2035 – scénario BAS": aq.escape(d=> (Math.round(d["Besoins annuels liés au renouvellement du parc 2021-2035 – scénario BAS"])).toLocaleString('fr-FR')),
    "Besoins annuels liés au renouvellement du parc 2021-2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Besoins annuels liés au renouvellement du parc 2021-2035 – scénario CENTRAL"])).toLocaleString('fr-FR')),
    "Besoins annuels liés au renouvellement du parc 2021-2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Besoins annuels liés au renouvellement du parc 2021-2035 – scénario HAUT"])).toLocaleString('fr-FR')),
    "Taux LV au 1er janvier 2035 – scénario BAS": aq.escape(d=> (Math.round(d["Taux LV au 1er janvier 2035 – scénario BAS"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux LV au 1er janvier 2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Taux LV au 1er janvier 2035 – scénario CENTRAL"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux LV au 1er janvier 2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Taux LV au 1er janvier 2035 – scénario HAUT"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Nombre de logements vacants projeté au 1er janvier 2035 – scénario BAS": aq.escape(d=> (Math.round(d["Nombre de logements vacants projeté au 1er janvier 2035 – scénario BAS"])).toLocaleString('fr-FR')),
    "Nombre de logements vacants projeté au 1er janvier 2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Nombre de logements vacants projeté au 1er janvier 2035 – scénario CENTRAL"])).toLocaleString('fr-FR')),
    "Nombre de logements vacants projeté au 1er janvier 2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Nombre de logements vacants projeté au 1er janvier 2035 – scénario HAUT"])).toLocaleString('fr-FR')),
    "Taux de RS/LO visé au 1er janvier 2035 – scénario BAS": aq.escape(d=> (Math.round(d["Taux de RS/LO visé au 1er janvier 2035 – scénario BAS"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux de RS/LO visé au 1er janvier 2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Taux de RS/LO visé au 1er janvier 2035 – scénario CENTRAL"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Taux de RS/LO visé au 1er janvier 2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Taux de RS/LO visé au 1er janvier 2035 – scénario HAUT"]*10000)/100).toLocaleString('fr-FR') + "%"),
    "Nombre de RS/LO projeté au 1er janvier 2035 – scénario BAS": aq.escape(d=> (Math.round(d["Nombre de RS/LO projeté au 1er janvier 2035 – scénario BAS"])).toLocaleString('fr-FR')),
    "Nombre de RS/LO projeté au 1er janvier 2035 – scénario CENTRAL": aq.escape(d=> (Math.round(d["Nombre de RS/LO projeté au 1er janvier 2035 – scénario CENTRAL"])).toLocaleString('fr-FR')),
    "Nombre de RS/LO projeté au 1er janvier 2035 – scénario HAUT": aq.escape(d=> (Math.round(d["Nombre de RS/LO projeté au 1er janvier 2035 – scénario HAUT"])).toLocaleString('fr-FR'))
    
  })
  
  .fold(aq.all()) // ça c'est le pivot
  .rename({'key': 'Indicateur', 'value':'Valeur' }) // et là on renomme key et value (noms automatiques) comme on veut
  .filter(d => d["Valeur"])
Code
// cf option dans l'exemple :
// https://observablehq.com/@observablehq/input-tableau

Inputs.table(dt_territoire7_pivot,{
  width: {
    "Indicateur": 500 // pour pas avoir une trop petite première colonne
  },
  rows: 50, // pour éviter l'ascenceur dans le tableau, faudrait compter pour de vrai...
})


Graphes pour s’amuser
Code
data_DPL_scatterplot_t = transpose (data_DPL_scatterplot)

viewof indicateur1 = Inputs.select(Object.keys(data_DPL_scatterplot_t[0]).slice(1), {label: "choix de l'indicateur en abscisse", width: 500})

viewof indicateur2 = Inputs.select(Object.keys(data_DPL_scatterplot_t[0]).slice(1), {label: "choix de l'indicateur en ordonnée", width: 500})
Code
territoire8 = data_DPL_scatterplot_t.filter(d => d["Libellé zonage"] == myTerritoire["Libellé zonage"])

//territoire8
Code
Plot.plot({
  marginLeft: 100,
  marginRight: 100,
  marginTop: 50,
  //height: 1100,
  width: 900,
  style: {
    fontSize: 12,
  },
  x: {tickFormat: d => d.toLocaleString('fr-FR'),label: indicateur1},
  y: {tickFormat: d => d.toLocaleString('fr-FR'),grid: true, label: indicateur2},
//  subtitle: "Demande potentielle en logements à horizon 2035 (moyenne annuelle)",

  marks: [
    Plot.dot(data_DPL_scatterplot_t, {
      x: indicateur1, 
      y: indicateur2,
      channels: {Nom_zone: "Libellé zonage"},
      tip: {
        format: {x: d => Math.round(d).toLocaleString('fr-FR')}
           }
    }),
    
    Plot.dot(data_DPL_scatterplot_t,
        Plot.pointer({
            x: indicateur1,
            y: indicateur2,
            fill: "blue",
            r: 2
                    })
            ),
            
    Plot.dot(territoire8, {
      x: indicateur1, 
      y: indicateur2,
      channels: {Nom_zone: "Libellé zonage"},
      fill: "red",
      r: 8,
      tip: {
        format: {x: d => Math.round(d).toLocaleString('fr-FR')}
           }
    }),
            
    Plot.text(data_DPL_scatterplot_t, {x: indicateur1, y: indicateur2, text: (d) => d["Libellé zonage"], dy: -6, lineAnchor: "bottom", fontSize:7}),
    
   Plot.ruleY([0]),
   Plot.ruleX([0]),
  ]
})
Retour au sommet