g = metrics["glm"]
show_metric_table(
[
("Training rows (downsampled for GLM)", f"{g['n_obs']:,}"),
("Full dataset rows", f"{g['n_full']:,}"),
("Positive link-years", f"{g['n_pos']:,} ({g['n_pos']/g['n_full']:.2%})"),
("Pseudo-R² (1 - D/D₀)", f"{g['pseudo_r2']:.3f}"),
("AIC", f"{g['aic']:,.0f}"),
("Features", f"{len(g['features'])}"),
],
caption="GLM training surface",
)
GLM_FEATURE_DETAILS = {
"road_class_ord": (
"Ordinal road-class code, with motorways highest and unknown lowest.",
"OS Open Roads `road_classification`; encoded in `road_risk.model.constants`.",
),
"form_of_way_ord": (
"Ordinal carriageway/form code distinguishing duals, slips, roundabouts, and single carriageways.",
"OS Open Roads `form_of_way`; encoded in `road_risk.model.constants`.",
),
"is_motorway": (
"Binary flag for motorway links.",
"Derived from OS Open Roads `road_classification`.",
),
"is_a_road": (
"Binary flag for A-road links.",
"Derived from OS Open Roads `road_classification`.",
),
"is_slip_road": (
"Binary flag for slip roads.",
"Derived from OS Open Roads `form_of_way`.",
),
"is_roundabout": (
"Binary flag for roundabout links.",
"Derived from OS Open Roads `form_of_way`.",
),
"is_dual": (
"Binary flag for dual or collapsed-dual carriageway links.",
"Derived from OS Open Roads `form_of_way`.",
),
"is_trunk": (
"Binary flag for trunk-road links.",
"OS Open Roads `is_trunk`.",
),
"is_primary": (
"Binary flag for primary-route links.",
"OS Open Roads `is_primary`.",
),
"log_link_length": (
"Natural log of link length in kilometres; separate from the exposure offset so length can depart from a strict linear effect.",
"OS Open Roads `link_length_km`.",
),
"is_covid": (
"Binary flag for Covid-period years.",
"AADF/model year; 2020 and 2021 in `road_risk.model.constants`.",
),
"year_norm": (
"Training year scaled from 0 to 1 across the model period.",
"AADF/model year in the Stage 2 link-year table.",
),
"hgv_proportion": (
"Heavy-goods-vehicle share of traffic.",
"`data/features/road_traffic_features.parquet`, derived from AADF traffic composition.",
),
"degree_mean": (
"Mean graph degree of the link's start and end nodes; a junction-complexity proxy.",
"`data/features/network_features.parquet`, built from the OS Open Roads graph.",
),
"betweenness": (
"Approximate betweenness centrality averaged across the link endpoints.",
"`data/features/network_features.parquet`, built with NetworkX on OS Open Roads.",
),
"betweenness_relative": (
"Log centrality relative to the mean for the same road class.",
"`data/features/network_features.parquet`, derived from `betweenness` and road class.",
),
"dist_to_major_km": (
"Graph distance to the nearest motorway or A-road node.",
"`data/features/network_features.parquet`, built from the OS Open Roads graph.",
),
"pop_density_per_km2": (
"Population density at the road-link centroid.",
"GB OA population-density context joined to road-link centroids.",
),
"overall_decile_within_country": (
"Overall deprivation decile within each nation; 1 is most deprived, 10 least deprived.",
"England IoD 2025, Wales WIMD 2019, Scotland SIMD 2020v2; joined by link centroid.",
),
"income_decile_within_country": (
"Income deprivation decile within each nation; 1 is most deprived, 10 least deprived.",
"England/Wales/Scotland deprivation context where available; joined by link centroid.",
),
"employment_decile_within_country": (
"Employment deprivation decile within each nation; 1 is most deprived, 10 least deprived.",
"England/Wales/Scotland deprivation context where available; joined by link centroid.",
),
"deprivation_country_england": (
"Country indicator for England deprivation assignments.",
"GB deprivation assignment provenance.",
),
"deprivation_country_wales": (
"Country indicator for Wales deprivation assignments.",
"GB deprivation assignment provenance.",
),
"deprivation_country_scotland": (
"Country indicator for Scotland deprivation assignments.",
"GB deprivation assignment provenance.",
),
}
def describe_glm_feature(feature):
if feature.endswith("_missing"):
base = feature.removesuffix("_missing")
treatment = "Missingness indicator: 1 where the raw feature was unavailable."
elif feature.endswith("_imputed"):
base = feature.removesuffix("_imputed")
treatment = "Median-imputed numeric value used by the GLM."
else:
base = feature
treatment = "Core model feature."
description, source = GLM_FEATURE_DETAILS.get(
base,
("Model input from the Stage 2 training table.", "Stage 2 modelling dataset."),
)
return {
"Feature": feature,
"Description": description,
"Source / construction": source,
"GLM treatment": treatment,
}
show_table(
pd.DataFrame([describe_glm_feature(feature) for feature in g["features"]]),
caption="GLM feature list with source and treatment",
index=False,
)