Transferring a Land-Use Regression Model to a New Metro Area

This guide, part of Land-Use Regression for Air Pollution Exposure, covers the decision a health department faces when it has no measurement campaign of its own and a well-documented model exists for a neighbouring city: can that model be used here, and how would you know?

Problem Context & Constraints

A land-use regression model is a set of coefficients on predictors like road length within 100 m and industrial area within 1 km. Nothing in the coefficients is intrinsically local — they are numbers — so transferring the model is technically trivial. What makes it fail is that each coefficient absorbs everything about the source city that was constant during its campaign: the vehicle fleet and its emission factors, how the road network classifies a “primary” road, what the land-cover taxonomy calls industrial, the background concentration, and the meteorological regime.

Move the model 300 km and several of those change at once. A “primary” road in one state’s network may carry 30,000 vehicles a day and in another’s 8,000, so the same predictor value means a different emission. A land-cover product that lumps warehousing with light industry in one vintage and separates them in another shifts the industrial predictor by a factor. The transferred model responds to none of this; it applies the same slope to a differently-scaled input.

Two constraints shape a defensible transfer. Some local measurement is required — not a full campaign, but enough sites to estimate whether the transferred predictions are biased and whether they track local variation at all. Ten to twenty sites, deliberately spread across the predictor range, is a workable minimum. And the predictors must be rebuilt with the source model’s definitions, not the local convention: same road classes, same buffer radii, same transformations, same land-cover mapping. A transfer that changes the predictor definitions is not a transfer, it is a new model wearing borrowed coefficients.

Three Ways a Transferred Model Fails Three scatter panels of observed against predicted concentration at local validation sites, each with the identity line drawn. In the first the points lie parallel to the identity line but offset, indicating a background difference that a simple intercept correction repairs. In the second the points follow a line with a slope well below one, indicating the source city's gradient is steeper than the local one, which an intercept and slope recalibration repairs. In the third the points show no relationship at all, indicating the predictor definitions or the sources differ so much that no recalibration is possible and a local campaign is required. Plot observed against predicted first — the shape names the remedy Offset only fix: intercept correction Slope too shallow fix: recalibrate intercept and slope No relationship fix: none — measure locally horizontal axis: transferred model prediction · vertical axis: local measurement · dashed line is identity Only the first two are repairable, and only the third is common when the road classification differs

Prerequisites

  • python 3.11
  • numpy 1.26.4, pandas 2.2.2, scikit-learn 1.5.0, geopandas 0.14.4
  • The source model’s full specification: terms, coefficients, buffer radii, class definitions, transformations and the measurement period
  • Local validation measurements at 10–20 sites covering the predictor range, not just the geography
  • Local road, land-cover and population layers rebuilt to the source model’s definitions

Step-by-Step Solution

Score the transfer, then choose between using it as-is, recalibrating it, or rejecting it. The decision rule below is deliberately conservative: recalibration is allowed only when the transferred predictions still track local variation.

# Evaluate and recalibrate a transferred land-use regression model.
# Pinned: numpy==1.26.4, pandas==2.2.2, scikit-learn==1.5.0
import logging

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("lur.transfer")

MIN_TRACKING_R = 0.5      # below this the transfer carries no local information


def apply_source_model(local_predictors: pd.DataFrame, spec: dict) -> np.ndarray:
    """Evaluate the source model. spec = {'intercept': float, 'terms': {name: coef}}."""
    missing = [t for t in spec["terms"] if t not in local_predictors.columns]
    if missing:
        raise KeyError(f"predictors missing locally: {missing} — rebuild with source "
                       "definitions before transferring")
    z = np.full(len(local_predictors), float(spec["intercept"]))
    for name, coef in spec["terms"].items():
        z += float(coef) * local_predictors[name].to_numpy(float)
    return z


def evaluate_transfer(observed: np.ndarray, predicted: np.ndarray) -> dict:
    """Bias, scatter and tracking of a transferred model against local measurements."""
    resid = observed - predicted
    r = float(np.corrcoef(observed, predicted)[0, 1])
    # Regress observed on predicted: intercept is the offset, slope is the scaling.
    cal = LinearRegression().fit(predicted.reshape(-1, 1), observed)
    out = {"n": int(len(observed)),
           "bias": float(resid.mean()),
           "rmse": float(np.sqrt((resid ** 2).mean())),
           "pearson_r": r,
           "cal_intercept": float(cal.intercept_),
           "cal_slope": float(cal.coef_[0])}
    log.info("transfer n=%d bias=%+.3f rmse=%.3f r=%.3f slope=%.3f intercept=%+.3f",
             out["n"], out["bias"], out["rmse"], r, out["cal_slope"], out["cal_intercept"])
    return out


def transfer_decision(ev: dict) -> str:
    """Use as-is, recalibrate, or reject — with the reason recorded."""
    if ev["pearson_r"] < MIN_TRACKING_R:
        log.warning("REJECT: transferred predictions do not track local variation "
                    "(r=%.3f) — recalibration would only rescale noise", ev["pearson_r"])
        return "reject"
    if abs(ev["bias"]) < 0.1 * abs(np.nan_to_num(ev["cal_intercept"]) + 1e-9) \
       and 0.9 <= ev["cal_slope"] <= 1.1:
        log.info("USE AS-IS: slope near one and bias negligible")
        return "as_is"
    log.info("RECALIBRATE: apply observed = %.3f + %.3f x predicted",
             ev["cal_intercept"], ev["cal_slope"])
    return "recalibrate"


def recalibrate(predicted: np.ndarray, ev: dict) -> np.ndarray:
    """Two-parameter correction. Never refit the source coefficients on 15 sites."""
    return ev["cal_intercept"] + ev["cal_slope"] * predicted

The design point in that code is that recalibration touches two parameters, not the model. With fifteen local sites there is no possibility of refitting a five-term model — that is a new campaign with a fifth of the sites it needs. Correcting the intercept and slope of the predictions, on the other hand, is estimating two parameters from fifteen observations, which is a reasonable ask, and it repairs the two failures that transfers most often exhibit: a background offset and a mismatched gradient.

The rejection branch exists because recalibration is seductive. A linear correction can always be fitted, and it will always improve the apparent fit at the validation sites. If the transferred predictions do not correlate with local measurements, that correction is rescaling noise, and applying it produces a surface that looks calibrated and carries no information. The correlation threshold is the guard, and it should be declared before the validation data are seen.

The Transfer Decision A decision sequence beginning with local validation at ten to twenty sites. The first test asks whether the transferred predictions correlate with local measurements above the declared threshold. If not, the transfer is rejected and a local campaign is required. If they do, the second test asks whether the calibration slope is near one and the bias negligible, in which case the model is used as it stands. Otherwise a two-parameter recalibration of intercept and slope is applied, and the recalibrated model is revalidated on sites that were held out of the calibration. Two questions decide whether a borrowed model can be used here Local validation 10–20 sites, full predictor range Does it track at all? correlation above threshold No — reject run a local campaign Slope near one? and bias negligible Yes — use as-is record the validation anyway Recalibrate two parameters, not five Revalidate on held-out sites the calibration sites cannot score it

Validation & Edge Cases

Validation sites used twice. Fitting the calibration on all fifteen sites and then reporting the fit at those same sites is the flat-validation error again, in miniature. Split them, or use leave-one-out for the calibration statistics.

Predictor definitions quietly localised. The most common cause of the no-relationship panel is a road classification that was mapped to local conventions rather than the source model’s. Rebuild with the source definitions and re-score before concluding the transfer has failed.

A predictor absent locally. A distance-to-port term in a source model cannot be evaluated in an inland city. Setting it to zero applies the coefficient to a value outside the sampled range. The term must be dropped and the model refitted at source, or the transfer abandoned.

A different measurement period. A model fitted in 2014 transferred to 2024 measurements carries a decade of fleet change, which appears mostly as a slope below one. Recalibration absorbs it, but the corrected model is then valid for the new period only, and must be labelled accordingly.

A city with a genuinely different source mix. Transfer assumes the same sources drive the contrast in both places. A model fitted where traffic dominates, moved to a city where residential wood burning drives winter concentrations, will track poorly in winter and acceptably in summer, and a single annual validation hides that entirely. Validate by season where the source mix is seasonal, and report the seasonal statistics separately rather than pooling them into one reassuring annual number.

Recalibration parameters reused across years. The intercept and slope correction is estimated against measurements from one period. Carrying it forward unchanged across a five-year cohort assumes the background and the gradient both stayed put. Re-estimate the two parameters whenever new local measurements exist, keep the history, and treat a drifting slope as evidence that the transfer is decaying rather than as noise to average over.

Validation sites all in one part of the predictor range. Fifteen background sites will show an excellent correlation and estimate the near-road behaviour not at all. Distribute the local sites by predictor value, not by map coverage.

INFO lur.transfer transfer n=16 bias=+3.104 rmse=5.221 r=0.741 slope=0.612 intercept=+7.885
INFO lur.transfer RECALIBRATE: apply observed = 7.885 + 0.612 x predicted

Recalibration buys back accuracy only where the transferred predictions already track local variation, which shows clearly in the before-and-after error at the held-out sites:

What Recalibration Recovers, and What It Cannot Three bars of held-out error at local validation sites. The transferred model as published has the largest error, driven by an offset and a slope mismatch. After two-parameter recalibration the error falls substantially, recovering most of the gap. A model fitted from a full local campaign has the lowest error, and the remaining difference between the recalibrated and local bars is the cost of borrowing rather than measuring, which recalibration cannot remove because it does not change which predictors were chosen. Recalibration fixes offset and slope, not the choice of predictors 5.2 transferred as-is 3.1 recalibrated 2.4 local campaign cost of borrowing held-out root mean squared error at local sites, micrograms per cubic metre

Compliance Notes

Document the transfer as a distinct artifact from both the source model and a local model. The record needs the source model citation and full specification, the local predictor build confirming the source definitions were used, the local validation site list with how they were chosen across the predictor range, the evaluation statistics, the decision taken with the threshold that drove it, and the recalibration parameters where one was applied.

Where the transferred model feeds a published exposure estimate, the uncertainty reported must be the local validation error, not the source model’s original cross-validated error. Those numbers routinely differ by a factor of two, and quoting the source figure attributes to your city a validation that was never performed there.