Intermediate LSOA data enrichment with Access to Healthy Assets and Hazards data#
This stage takes the pseudonymised LSOA data derived from the raw GPS data and enriches it with AHAH data.
# Import the necessary libraries and modules
import logging
from pathlib import Path
from bhf_smarthealth.utils import data_io
from bhf_smarthealth.utils import df_utils
from bhf_smarthealth.config import DEFAULT_AHAH_FIELDS
from bhf_smarthealth.utils.aggregators import mode_with_later_tiebreak
# Initiate logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
# Path to the "intermediate" LSOA data and the reference AHAH data
AHAH_DATA_PATH = Path("../../../tests/data/reference/ahah_for_testing.parquet")
LSOA_DATA_PATH = Path("../../../tests/data/intermediate/lsoa_2025-10-09.parquet")
Load in the derived LSOA data
lsoa_df = data_io.load_data_from_file(LSOA_DATA_PATH)
# Show the first five rows
lsoa_df.head()
| LSOA_n_samples | latest_LSOA21NM | latest_LSOA21CD | ||
|---|---|---|---|---|
| sheffield_id | timestamp | |||
| UOS00001 | 2025-10-09 00:00:00 | 7 | Sheffield 073D | E01033264 |
| 2025-10-09 00:05:00 | 4 | Sheffield 022H | E01033270 | |
| 2025-10-09 00:55:00 | 17 | Sheffield 011B | E01008063 | |
| 2025-10-09 01:40:00 | 1 | Sheffield 006D | E01008138 | |
| 2025-10-09 01:55:00 | 9 | Sheffield 004D | E01007891 |
Aggregate derived LSOA values into an hourly modal LSOA
If a tiebreak exists when calculating the modal LSOA, the LSOA associated with the latest timestamp is taken
# Dictionary that dictates which aggregate calculations are performed on which fields
agg_dict = {
"latest_LSOA21CD": mode_with_later_tiebreak,
"latest_LSOA21NM": mode_with_later_tiebreak,
}
# Apply the aggregation
lsoa_modal_df = df_utils.apply_agg_over_interval(
lsoa_df,
agg_dict=agg_dict,
time_period="1h", # 1 hour
)
Rename the columns for clarity
lsoa_modal_df = df_utils.rename_columns(
df=lsoa_modal_df,
col_names=[
"latest_LSOA21CD",
"latest_LSOA21NM",
],
)
lsoa_modal_df.head()
| latest_LSOA21CD | latest_LSOA21NM | |
|---|---|---|
| timestamp | ||
| 2025-10-09 00:00:00 | E01008063 | Sheffield 011B |
| 2025-10-09 01:00:00 | E01007891 | Sheffield 004D |
| 2025-10-09 02:00:00 | E01007390 | Barnsley 030B |
| 2025-10-09 03:00:00 | E01007457 | Barnsley 029E |
| 2025-10-09 04:00:00 | E01007353 | Barnsley 020F |
Load in the reference AHAH data ready to be joined to the aggregated dataset
Note that use of this dataset requires attribution:
“The data for this research have been provided by the Geographic Data Service (geods.ac.uk), a Smart Data Research UK Investment: ES/Z504464/1.”
ahah_df = data_io.load_data_from_file(AHAH_DATA_PATH)
# Show first five rows
ahah_df.head()
| LSOA21CD | ah4blue | ah4dent | ah4gp | ah4hosp | ah4phar | ah4leis | ah4pubs | ah4ffood | ah4gpas | ... | ah4g_rnk | ah4e_rnk | ah4r_rnk | ah4h_pct | ah4g_pct | ah4e_pct | ah4r_pct | ah4ahah | ah4ahah_rnk | ah4ahah_pct | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 6987 | E01007349 | 6.2489 | 4.0517 | 4.4788 | 2.6851 | 2.4753 | 7.8121 | 3.9223 | 3.7386 | 0.4295 | ... | 34646 | 19568 | 7881 | 81 | 82 | 46 | 19 | 23.0148 | 27201 | 64 |
| 6991 | E01007353 | 2.3809 | 1.0516 | 1.5069 | 1.2947 | 1.1472 | 3.7182 | 0.8328 | 0.7208 | 0.3575 | ... | 24692 | 18916 | 35870 | 25 | 58 | 45 | 85 | 19.9964 | 21263 | 50 |
| 7028 | E01007390 | 4.9572 | 1.5597 | 2.4075 | 1.2549 | 1.8103 | 2.4129 | 1.2290 | 1.3465 | 0.3975 | ... | 34019 | 21256 | 27976 | 38 | 80 | 50 | 66 | 21.4113 | 24220 | 57 |
| 7059 | E01007424 | 6.5558 | 6.1045 | 7.2558 | 3.4741 | 3.5114 | 7.3611 | 2.9262 | 3.0673 | 0.4863 | ... | 28016 | 19150 | 7960 | 88 | 66 | 45 | 19 | 21.8908 | 25173 | 60 |
| 7091 | E01007457 | 4.3442 | 2.9063 | 2.8211 | 2.2374 | 2.1592 | 2.5880 | 1.9938 | 2.4178 | 0.5427 | ... | 11503 | 16376 | 19495 | 60 | 27 | 39 | 46 | 13.1279 | 5679 | 14 |
5 rows × 58 columns
Merge this reference data (only the fields required in the final output table) with the hourly-aggregated LSOAs
lsoa_ahah_df = (
lsoa_modal_df.reset_index() # required to maintain the datetime column
.merge(
ahah_df[DEFAULT_AHAH_FIELDS],
how="inner",
left_on=["latest_LSOA21CD"],
right_on=["LSOA21CD"],
)
.set_index("datetime") # reset the DataFrame datetimeindex column
)
# Show the first five rows
lsoa_ahah_df.head()
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
/tmp/ipykernel_2083/2282981879.py in ?()
5 how="inner",
6 left_on=["latest_LSOA21CD"],
7 right_on=["LSOA21CD"],
8 )
----> 9 .set_index("datetime") # reset the DataFrame datetimeindex column
10 )
11
12 # Show the first five rows
~/work/BHF-SmartHealth/BHF-SmartHealth/.venv/lib/python3.12/site-packages/pandas/core/frame.py in ?(self, keys, drop, append, inplace, verify_integrity)
6140 if not found:
6141 missing.append(col)
6142
6143 if missing:
-> 6144 raise KeyError(f"None of {missing} are in the columns")
6145
6146 if inplace:
6147 frame = self
KeyError: "None of ['datetime'] are in the columns"
Remove unecessary columns
This removes the sensitive LSOA fields from the dataframe
lsoa_ahah_df = df_utils.remove_columns(
df=lsoa_ahah_df, col_names=["LSOA21CD", "latest_LSOA21CD", "latest_LSOA21NM"]
)
# Show the first five rows
lsoa_ahah_df.head()