Skip to content

Commit 98b327f

Browse files
committed
add 2025 English IMD
1 parent 2fd42c8 commit 98b327f

File tree

10 files changed

+575
-376
lines changed

10 files changed

+575
-376
lines changed

findthatpostcode/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99

1010
from findthatpostcode import blueprints, commands, db
1111
from findthatpostcode.controllers.areas import area_types_count
12-
from findthatpostcode.metadata import AREA_TYPES, KEY_AREA_TYPES, OTHER_CODES
12+
from findthatpostcode.metadata import (
13+
AREA_TYPES,
14+
KEY_AREA_TYPES,
15+
OTHER_CODES,
16+
STATS_FIELDS,
17+
)
1318

1419

1520
def get_es_url(default):
@@ -74,6 +79,7 @@ def inject_now():
7479
other_codes=OTHER_CODES,
7580
area_types=AREA_TYPES,
7681
ethical_ads_publisher=app.config.get("ETHICAL_ADS_PUBLISHER"),
82+
stats_fields=STATS_FIELDS,
7783
)
7884

7985
@app.template_filter()

findthatpostcode/commands/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def init_app(app):
1717
import_cli.add_command(codes.import_msoa_names)
1818
import_cli.add_command(boundaries.import_boundaries)
1919
import_cli.add_command(postcodes.import_nspl)
20+
import_cli.add_command(stats.import_imd2025)
2021
import_cli.add_command(stats.import_imd2019)
2122
import_cli.add_command(stats.import_imd2015)
2223
import_cli.add_command(placenames.import_placenames)

findthatpostcode/commands/stats.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from findthatpostcode import db
1717
from findthatpostcode.commands.codes import AREA_INDEX
1818

19+
IMD2025_URL = "https://assets.publishing.service.gov.uk/media/68ff5daabcb10f6bf9bef911/File_7_IoD2025_All_Ranks_Scores_Deciles_Population_Denominators.csv"
1920
IMD2019_URL = "https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/845345/File_7_-_All_IoD2019_Scores__Ranks__Deciles_and_Population_Denominators_3.csv"
2021
IMD2015_URL = "https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/467774/File_7_ID_2015_All_ranks__deciles_and_scores_for_the_Indices_of_Deprivation__and_population_denominators.csv"
2122

@@ -35,6 +36,10 @@ def parse_field(k, v):
3536
IMD_FIELDS = {
3637
"LSOA code (2011)": "lsoa_code",
3738
"LSOA name (2011)": "lsoa_name",
39+
"LSOA code (2021)": "lsoa_code",
40+
"LSOA name (2021)": "lsoa_name",
41+
"Local Authority District code (2024)": "la_code",
42+
"Local Authority District name (2024)": "la_name",
3843
"Local Authority District code (2019)": "la_code",
3944
"Local Authority District name (2019)": "la_name",
4045
"Local Authority District code (2013)": "la_code",
@@ -162,9 +167,63 @@ def parse_field(k, v):
162167
"Working age population 18-59/64: for use with Employment Deprivation Domain "
163168
"(excluding prisoners)"
164169
): "population_workingage",
170+
"Total population: mid 2022": "population_total",
171+
"Dependent Children aged 0-15: mid 2022": "population_0_15",
172+
"Older population aged 60 and over: mid 2022": "population_60_plus",
173+
"Working age population 18-66 (for use with Employment Deprivation Domain): mid 2022": "population_workingage",
165174
}
166175

167176

177+
@click.command("imd2025")
178+
@click.option("--es-index", default=AREA_INDEX)
179+
@click.option("--url", default=IMD2025_URL)
180+
@with_appcontext
181+
def import_imd2025(url=IMD2025_URL, es_index=AREA_INDEX):
182+
if current_app.config["DEBUG"]:
183+
requests_cache.install_cache()
184+
185+
es = db.get_db()
186+
187+
r = requests.get(url, stream=True)
188+
189+
reader = csv.DictReader(codecs.iterdecode(r.iter_lines(), "utf-8-sig"))
190+
area_updates = []
191+
for k, area in tqdm.tqdm(enumerate(reader)):
192+
area = {
193+
IMD_FIELDS.get(k.strip(), k.strip()): parse_field(
194+
IMD_FIELDS.get(k.strip(), k.strip()), v
195+
)
196+
for k, v in area.items()
197+
}
198+
area_update = {
199+
"_index": es_index,
200+
"_type": "_doc",
201+
"_op_type": "update",
202+
"_id": area["lsoa_code"],
203+
"doc": {
204+
"stats": {
205+
"imd2025": {k: v for k, v in area.items() if k.startswith("imd_")},
206+
"idaci2025": {
207+
k: v for k, v in area.items() if k.startswith("idaci_")
208+
},
209+
"idaopi2025": {
210+
k: v for k, v in area.items() if k.startswith("idaopi_")
211+
},
212+
"population2022": {
213+
k: v for k, v in area.items() if k.startswith("population_")
214+
},
215+
}
216+
},
217+
}
218+
area_updates.append(area_update)
219+
220+
print("[imd2025] Processed %s areas" % len(area_updates))
221+
print("[elasticsearch] %s areas to save" % len(area_updates))
222+
results = bulk(es, area_updates)
223+
print("[elasticsearch] saved %s areas to %s index" % (results[0], es_index))
224+
print("[elasticsearch] %s errors reported" % len(results[1]))
225+
226+
168227
@click.command("imd2019")
169228
@click.option("--es-index", default=AREA_INDEX)
170229
@click.option("--url", default=IMD2019_URL)

findthatpostcode/controllers/areas.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ def process_attributes(self, data):
195195
data[i] = datetime.strptime(data[i][0:10], "%Y-%m-%d")
196196
except ValueError:
197197
continue
198+
199+
if self.id.startswith("E"):
200+
data["ctry"] = "E92000001"
201+
data["ctry_name"] = "England"
202+
elif self.id.startswith("W"):
203+
data["ctry"] = "W92000004"
204+
data["ctry_name"] = "Wales"
205+
elif self.id.startswith("S"):
206+
data["ctry"] = "S92000003"
207+
data["ctry_name"] = "Scotland"
208+
elif self.id.startswith("N"):
209+
data["ctry"] = "N92000002"
210+
data["ctry_name"] = "Northern Ireland"
211+
198212
return data
199213

200214
@staticmethod

findthatpostcode/metadata.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,24 @@
4343
],
4444
"usertype": ["Small user", "Large user"],
4545
"imd": {
46-
"E92000001": 32844,
47-
"W92000004": 1909,
48-
"S92000003": 6976,
49-
"N92000002": None,
50-
"L93000001": None,
51-
"M83000003": None,
46+
"E92000001-imd2025": 33755,
47+
"W92000004-imd2025": 1909,
48+
"S92000003-imd2025": 6976,
49+
"N92000002-imd2025": None,
50+
"L93000001-imd2025": None,
51+
"M83000003-imd2025": None,
52+
"E92000001-imd2019": 32844,
53+
"W92000004-imd2019": 1909,
54+
"S92000003-imd2019": 6976,
55+
"N92000002-imd2019": None,
56+
"L93000001-imd2019": None,
57+
"M83000003-imd2019": None,
58+
"E92000001-imd2015": 32844,
59+
"W92000004-imd2015": 1909,
60+
"S92000003-imd2015": 6976,
61+
"N92000002-imd2015": None,
62+
"L93000001-imd2015": None,
63+
"M83000003-imd2015": None,
5264
},
5365
}
5466

@@ -63,6 +75,18 @@
6375
("ruc21", "2021 Census rural-urban classification", True),
6476
]
6577
STATS_FIELDS = [
78+
(
79+
"imd2025_rank",
80+
"Index of multiple deprivation (2025) rank",
81+
False,
82+
"stats.imd2025.imd_rank",
83+
),
84+
(
85+
"imd2025_decile",
86+
"Index of multiple deprivation (2025) decile",
87+
False,
88+
"stats.imd2025.imd_decile",
89+
),
6690
(
6791
"imd2019_rank",
6892
"Index of multiple deprivation (2019) rank",

findthatpostcode/templates/about.html.j2

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
<a href="mailto:info@findthatpostcode.uk" class="link blue underline-hover">info@findthatpostcode.uk</a>
2525
</p>
2626
<h2 class="f2-ns f3 header-font normal" id="data-sources">Data sources</h2>
27+
<p>Data used under the <a class="link blue underline-hover"
28+
href="https://www.ons.gov.uk/methodology/geography/licences">Open Government Licence</a>
29+
unless otherwise stated.</p>
2730
<ul>
2831
<li><strong>Postcode data</strong>: <a class="link blue underline-hover"
2932
href="https://geoportal.statistics.gov.uk/search?q=PRD_NSPL&sort=Date%20Created%7Ccreated%7Cdesc">NSPL</a>
@@ -46,6 +49,9 @@
4649
class="link blue underline-hover"
4750
href="https://www.parliament.uk/site-information/copyright/open-parliament-licence/">Open Parliament
4851
Licence</a>)</li>
52+
<li><strong>Index of Multiple Deprivation (2025)</strong>: <a class="link blue underline-hover"
53+
href="https://www.gov.uk/government/statistics/english-indices-of-deprivation-2025">Ministry of Housing,
54+
Communities and Local Government</a></li>
4955
</ul>
5056
<p>
5157
<a href="https://www.ons.gov.uk/methodology/geography/licences" class="link blue underline-hover">Postcode data from

findthatpostcode/templates/area.html.j2

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
{% else %}
77
{% set subtitle = "<span class='bg-red white pa2 mt2 dib'>INACTIVE</span> {}".format(result.relationships.areatype.get_name(result.id)) %}
88
{% endif %}
9+
{% if result.attributes.ctry_name and result.attributes.ctry_name != result.attributes.name %}
10+
{% set subtitle = subtitle + " in " + result.attributes.ctry_name %}
11+
{% endif %}
912
{% set example_postcodes = result.relationships.example_postcodes %}
1013
{% extends "base.html.j2" %}
1114

@@ -98,6 +101,30 @@
98101
</ul>
99102
{% endcall %}
100103

104+
{% if result.attributes.stats and result.attributes.stats.imd2025 %}
105+
{% set rank = result.attributes.stats.imd2025.imd_rank %}
106+
{% set total_25 = other_codes.imd[result.attributes.ctry + "-imd2025"] %}
107+
{% set total_19 = other_codes.imd[result.attributes.ctry + "-imd2019"] %}
108+
{% set total_15 = other_codes.imd[result.attributes.ctry + "-imd2015"] %}
109+
{% call info_block("Index of multiple deprivation (2025)") %}
110+
<p><strong>{{ "{:,.0f}".format(rank) }}</strong> out of {{ "{:,.0f}".format(total_25) }} lower super output areas in
111+
{{ result.attributes.ctry_name }} (where 1 is the most deprived LSOA).</p>
112+
<p><strong>{{ "{:.0%}".format( rank|float / total_25|float ) }}</strong> of LSOAs in
113+
{{ result.attributes.ctry_name }}
114+
are more deprived than this one.</p>
115+
{% if result.attributes.stats.imd2019 %}
116+
<p>In <strong>2019</strong>
117+
{{ "{:.0%}".format( result.attributes.stats.imd2019.imd_rank|float / total_19|float ) }}</strong> of LSOAs in
118+
{{ result.attributes.ctry_name }} were more deprived than this one.</p>
119+
{% endif %}
120+
{% if result.attributes.stats.imd2015 %}
121+
<p>In <strong>2015</strong>
122+
{{ "{:.0%}".format( result.attributes.stats.imd2015.imd_rank|float / total_15|float ) }}</strong> of LSOAs in
123+
{{ result.attributes.ctry_name }} were more deprived than this one.</p>
124+
{% endif %}
125+
{% endcall %}
126+
{% endif %}
127+
101128
{% if result.relationships.areatype.id in ["msoa11", "msoa21"] %}
102129
<p class="mt4">
103130
The name of this area is from the <a class="link blue underline-hover"

findthatpostcode/templates/postcode.html.j2

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,26 @@
111111
{% endcall %}
112112
{% endif %}
113113

114-
{% for a in result.relationships.areas if a.attributes.stats and a.attributes.stats.imd2019 %}
114+
{% for a in result.relationships.areas if a.attributes.stats and a.attributes.stats.imd2025 %}
115115
{% if loop.first %}
116-
{% set rank = a.attributes.stats.imd2019.imd_rank %}
117-
{% set total = other_codes.imd[result.attributes.ctry] %}
118-
{% call info_block("Index of multiple deprivation (2019)") %}
119-
<p><strong>{{ "{:,.0f}".format(rank) }}</strong> out of {{ "{:,.0f}".format(total) }} lower super output areas in
116+
{% set rank = a.attributes.stats.imd2025.imd_rank %}
117+
{% set total_25 = other_codes.imd[result.attributes.ctry + "-imd2025"] %}
118+
{% set total_19 = other_codes.imd[result.attributes.ctry + "-imd2019"] %}
119+
{% set total_15 = other_codes.imd[result.attributes.ctry + "-imd2015"] %}
120+
{% call info_block("Index of multiple deprivation (2025)") %}
121+
<p><strong>{{ "{:,.0f}".format(rank) }}</strong> out of {{ "{:,.0f}".format(total_25) }} lower super output areas in
120122
{{ result.attributes.ctry_name }} (where 1 is the most deprived LSOA).</p>
121-
<p><strong>{{ "{:.0%}".format( rank|float / total|float ) }}</strong> of LSOAs in {{ result.attributes.ctry_name }}
123+
<p><strong>{{ "{:.0%}".format( rank|float / total_25|float ) }}</strong> of LSOAs in
124+
{{ result.attributes.ctry_name }}
122125
are more deprived than this one.</p>
126+
{% if a.attributes.stats.imd2019 %}
127+
<p>In <strong>2019</strong>
128+
{{ "{:.0%}".format( a.attributes.stats.imd2019.imd_rank|float / total_19|float ) }}</strong> of LSOAs in
129+
{{ result.attributes.ctry.name }} were more deprived than this one.</p>
130+
{% endif %}
123131
{% if a.attributes.stats.imd2015 %}
124132
<p>In <strong>2015</strong>
125-
{{ "{:.0%}".format( a.attributes.stats.imd2015.imd_rank|float / total|float ) }}</strong> of LSOAs in
133+
{{ "{:.0%}".format( a.attributes.stats.imd2015.imd_rank|float / total_15|float ) }}</strong> of LSOAs in
126134
{{ result.attributes.ctry.name }} were more deprived than this one.</p>
127135
{% endif %}
128136
{% endcall %}

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ Statistics can be added to areas, using ONS data. The available statistics are
134134
added to LSOAs, but could also be added to other areas.
135135

136136
```bash
137+
flask import imd2025
137138
flask import imd2019
138139
flask import imd2015
139140
```
@@ -340,6 +341,7 @@ dokku run find-that-postcode flask import nspl --year=2021
340341
dokku run find-that-postcode flask import rgc
341342
dokku run find-that-postcode flask import chd
342343
dokku run find-that-postcode flask import msoanames
344+
dokku run find-that-postcode flask import imd2025
343345
dokku run find-that-postcode flask import imd2019
344346
dokku run find-that-postcode flask import imd2015
345347
dokku run find-that-postcode flask import placenames

0 commit comments

Comments
 (0)