Estimating Customers per Transformer When Meter Data Is Missing

Every affected-customer count rests on knowing which customers each transformer serves, and no estate knows that for every transformer. Meters are added and moved, premises are subdivided, rural services are fed from transformers that were never mapped to them, and acquisitions arrive with a customer system that does not speak to the network model. The instinct is to fill the gap with an average and move on, which works right up to the point where the number appears in a reliability filing and somebody asks how it was derived. The alternative is not better data — that is a multi-year programme — but an explicit ladder of methods, each carrying its own confidence label, so a number’s provenance travels with it. This guide builds that ladder, extending the impact machinery in impact analysis and affected-customer tracing.

Environment Prerequisites

  • Python 3.11 with pandas>=2.0 and geopandas>=1.0 for the spatial fallback.
  • A meter-to-transformer mapping wherever one exists, from the metering or customer information system, keyed on a stable identifier.
  • Billing accounts joinable through a premise identifier, which is the second-best evidence and usually covers a large share of the remainder.
  • Service points with geometry and a transformer association, for the modelled fallback.
  • Transformer ratings and a class-average table derived from the measured population, not from a national figure, so the assumption at least reflects this estate.
  • A confidence field on the output, because the whole design depends on the method travelling with the number.

Schema-Aware Validation Protocol — Run Before Estimating Anything

  1. Measure the coverage of each method first. Knowing that eighty-nine per cent of transformers resolve from measured data changes how much effort the remainder deserves.
  2. Check the meter mapping for staleness. A mapping that has not been refreshed since a subdivision programme will attribute new premises to the wrong transformer confidently.
  3. Confirm the premise identifier is stable across systems. A join that silently drops unmatched rows understates every count it touches.
  4. Derive class averages from this estate’s measured population. An average taken from an industry figure describes somebody else’s network.
  5. Look for transformers with implausible counts. A distribution transformer serving four hundred customers is usually a mapping defect rather than a dense apartment block, and it is worth checking before it propagates.

Minimal Reproducible Implementation

from __future__ import annotations

import logging
from dataclasses import dataclass, field

import pandas as pd

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LOG = logging.getLogger("customer-estimate")

MEASURED, MODELLED, ASSUMED = "MEASURED", "MODELLED", "ASSUMED"


@dataclass
class Estimate:
    transformer_id: str
    customers: int
    method: str
    confidence: str
    detail: str = ""


@dataclass
class EstimateRun:
    estimates: list[Estimate] = field(default_factory=list)

    def coverage(self) -> dict[str, float]:
        total = len(self.estimates) or 1
        out: dict[str, int] = {}
        for est in self.estimates:
            out[est.method] = out.get(est.method, 0) + 1
        return {k: round(100 * v / total, 1) for k, v in out.items()}


def estimate_customers(
    transformers: pd.DataFrame,
    meter_map: pd.DataFrame,
    billing: pd.DataFrame,
    spatial: pd.DataFrame,
    class_average: dict[str, float],
) -> EstimateRun:
    """Attribute a customer count to every transformer using the best evidence available.

    The ladder is deliberate: a measured mapping is used where it exists, a billing join
    where it does not, a spatial association after that, and a class average only as the
    last resort. Every estimate carries the method that produced it, because a number
    whose provenance is lost cannot be defended in a filing.
    """
    run = EstimateRun()
    by_meter = meter_map.groupby("transformer_id")["meter_id"].count().to_dict()
    by_billing = billing.groupby("transformer_id")["account_id"].count().to_dict()
    by_spatial = spatial.groupby("transformer_id")["service_point_id"].count().to_dict()

    for _, row in transformers.iterrows():
        tid = str(row["transformer_id"])

        if tid in by_meter:
            run.estimates.append(Estimate(tid, int(by_meter[tid]), "meter_mapping",
                                          MEASURED))
            continue
        if tid in by_billing:
            run.estimates.append(Estimate(tid, int(by_billing[tid]), "billing_join",
                                          MEASURED))
            continue
        if tid in by_spatial:
            run.estimates.append(Estimate(tid, int(by_spatial[tid]), "spatial_association",
                                          MODELLED,
                                          "service points associated to this transformer"))
            continue

        rating = str(row.get("rating_class", "UNKNOWN"))
        avg = class_average.get(rating)
        if avg is None:
            run.estimates.append(Estimate(tid, 0, "none", ASSUMED,
                                          f"no class average for rating {rating}"))
            continue
        run.estimates.append(Estimate(tid, int(round(avg)), "class_average", ASSUMED,
                                      f"estate average for {rating} rating"))

    LOG.info("coverage by method: %s", run.coverage())
    return run


def aggregate(estimates: list[Estimate]) -> dict:
    """Total affected customers, split by the confidence of the underlying attribution."""
    out = {"total": 0, "measured": 0, "modelled": 0, "assumed": 0}
    for est in estimates:
        out["total"] += est.customers
        out[est.confidence.lower()] += est.customers
    return out
The estimation ladder, from measured counts down to a stated assumption Every transformer takes the best evidence available to it. A meter-to-transformer mapping is measured and needs no estimate. A billing-account join through the premise is nearly as good. A spatial association between service points and the transformer that feeds them is a model rather than a measurement. And where nothing resolves, a class average by transformer rating is an assumption that must travel with the number so nobody mistakes it for a count. MEASURED meter → transformer mapping exists JOINED billing account via premise MODELLED spatial association to service points ASSUMED class average by transformer rating always CARRY THE METHOD an assumed count that travels as a measured one is how a filing becomes indefensible Four sources of the same number, and only two of them are counts.

The aggregate function returning a split rather than a single total is the part that matters downstream. An impact record saying “4,120 customers, of which 3,900 measured and 220 assumed” can be used for anything; the same record reporting only the total forces every consumer to treat all of it as equally solid.

Confidence by estimation method, and what each may be used for The method decides what the number can support. A measured count belongs in a regulatory filing. A modelled one is fine for dispatch prioritisation, where being ten per cent out changes nothing. An assumed count is adequate for a first response and must never reach a reported index without being labelled. Method Confidence Dispatch Filing Meter mapping measured yes yes Billing join measured yes yes Spatial association modelled yes labelled only Class average assumed yes no The bottom-right cell is where reliability filings get into trouble.

Improving Coverage Rather Than the Averages

It is tempting to spend effort refining the class averages, and it is almost always the wrong investment. An average is a distribution collapsed to a point, and no amount of refinement makes it a count. The effort belongs in moving transformers up the ladder.

The cheapest movement is usually the billing join, because the data exists and the obstacle is a mismatched identifier rather than missing information. The next cheapest is the spatial association, which is a modelling exercise on data already in the network. Only after both are exhausted does field verification become the sensible option, and by then it applies to a small enough population to be affordable.

Tracking the measured share over time is what makes this a programme rather than a series of one-off fixes. A share that is falling means new connections are arriving without attribution, which is a process defect at the point of connection and far cheaper to fix there than to clean up later.

Coverage by method across one distribution estate A typical estate resolves most transformers from measured data and a long tail from models and assumptions. The useful metric is not the average count but the share of customers whose attribution is measured, because that share is what a filing rests on and what a data programme should be moving. share of transformers by attribution method Meter mapping 71 % measured Billing join 18 % measured Spatial association 9 % modelled Class average 2 % assumed Track the measured share; it is the number a filing actually depends on.

Production Deployment Pattern

  1. Run the estimation as part of the impact pipeline, not as a separate data-preparation step, so every impact record carries current attribution.
  2. Publish the confidence split on every count. Dispatch can use the total; a filing uses the measured portion and states the rest.
  3. Recompute class averages from the measured population monthly. They drift as the estate changes, and an average derived from measured data at least tracks this network.
  4. Alarm on implausible counts. A transformer whose count jumps by an order of magnitude between runs is a mapping change, not a load change.
  5. Feed unattributed transformers into the data programme with their served load. The ones worth fixing first are the ones feeding the most customers, which the network model can rank.
  6. Persist the run. Coverage by method, class averages used, and the estimate per transformer, so a later filing can reproduce the number it published.

Conclusion

Missing meter data is a permanent condition rather than a temporary defect, and the useful response is an explicit ladder of methods with the confidence carried through to every consumer. Splitting a published count into measured, modelled and assumed makes it usable for dispatch and defensible in a filing at the same time. Tracking the measured share turns the underlying data gap into a programme with a visible trend, and ranking the unattributed transformers by served load is what puts the effort where the customers are.

For authoritative reference, consult the pandas grouping guide and the IEEE 1366 reliability indices standard.