# How a match is computed

The full method for computing neighborhood resemblance scores.

## Overview

LookAlike scores every continental US census tract by how closely its observable characteristics resemble a brand’s “archetype” — the average profile of the neighborhoods where the brand already has stores. The method is transparent and reproducible; this page walks through every step, from raw data to the 0–100 similarity percentile you see on the map.

This tool measures **observable neighborhood resemblance only**. It does **not** predict revenue, sales, foot traffic, or whether a store would succeed at a location. Use it to decide where to look, not to decide where to open.

## The data: 21 features in 6 thematic groups

Every tract is described by 21 observable features, organized into 6 thematic groups. This grouping is central to the method: it prevents correlated features (like income, per-capita income, home value, and rent, which all correlate r > 0.7) from being counted as separate evidence and dominating the score.

### 1\. Income, wealth & housing cost (5 features)

-   Median household income ($)
-   Per-capita income ($)
-   Median home value ($)
-   Median gross rent ($/month)
-   Poverty rate (%)

### 2\. Education (1 feature)

-   Bachelor’s degree or higher (%)

### 3\. Density & transportation (4 features)

-   Population density (people/km²)
-   Households without a vehicle (%)
-   Walk/bike/transit commute (%)
-   Renter-occupied housing (%)

### 4\. Age & household type (3 features)

-   Median age (years)
-   Average household size (people)
-   Households with children (%)

### 5\. Race & ethnicity (3 features)

-   White non-Hispanic (%)
-   Hispanic/Latino (%)
-   Asian non-Hispanic (%)

### 6\. Retail & dining context (5 features)

From Overture Maps Places, aggregated per km²:

-   All points of interest
-   Grocery stores
-   Restaurants
-   Cafés & coffee
-   Shopping & apparel

These features are drawn from the US Census American Community Survey (ACS 2023, 5-year estimates), Census TIGER/Line tract boundaries, and Overture Maps Places. See [data sources](https://docs.lookalikeiq.com/method/data-sources/) for full provenance.

## Building the archetype

For a brand, the archetype is the **average of the features across every unique census tract the brand occupies** — one observation per tract, so co-located facilities (like a Costco gas station and warehouse at the same location) are counted only once.

Because the archetype uses one observation per occupied tract, a tract with ten of the brand’s stores pulls the average exactly as hard as a tract with one. This neutralizes OpenStreetMap over-counting (the reason for the one-per-tract rule), but it also means the archetype describes “the kinds of neighborhood this brand occupies,” not “where its store volume concentrates.” Separately, because the store list comes from OpenStreetMap, any coverage gap quietly reshapes the profile: a brand whose urban stores are better mapped than its rural ones will look more urban than it is. A store-count-weighted archetype and a coverage read-out are listed under [future methodology work](#future-methodology-work).

For the 11 built-in brands:

-   **Trader Joe’s** (603 stores in 597 unique tracts)
-   **Whole Foods Market** (525 stores in 524 tracts)
-   **Sprouts Farmers Market** (448 stores in 448 tracts)
-   **Chipotle Mexican Grill** (3,068 stores in 2,998 tracts)
-   **Starbucks** (10,984 stores in 9,119 tracts)
-   **Costco Wholesale** (1,225 stores in 618 tracts)
-   **Dollar General** (11,136 stores in 10,299 tracts)
-   **Tractor Supply Co.** (1,554 stores in 1,550 tracts)
-   **Sweetgreen** (167 stores in 166 tracts)
-   **H Mart** (96 stores in 96 tracts)
-   **Cracker Barrel** (639 stores in 634 tracts)

These counts are OpenStreetMap-derived, after two cleaning steps: co-located sub-features are collapsed on a 150 m grid, and each surviving point is assigned to the nearest continental-US tract within 1 km, with anything farther dropped (removing Canada and Mexico bounding-box slop and geocode errors). They reflect OpenStreetMap coverage, which is strong for large chains but not exhaustive.

For a [custom upload](https://docs.lookalikeiq.com/guides/custom-upload/), the archetype is computed the same way from your lat/lon points, with optional weighting by a performance column (revenue, sales, etc.) so the profile leans toward your best locations.

## Clipping extremes

Before any scaling, all feature values are clipped to the **national 1st–99th percentile** (computed across all ~83,000 CONUS tracts). This prevents a handful of extreme neighborhoods — tracts with very high incomes (top-coded in census data), ultra-dense urban cores, or rural sparsity — from distorting the scale. The clipping is applied per feature, so each feature uses its own p1 and p99 bounds.

The clip is applied **before** the brand mean is computed, so a brand whose neighborhoods sit above the 99th percentile on a feature gets a capped archetype on that feature. For Whole Foods on median home value, a $1.2M tract and a $2.5M tract are both pulled down to the national p99 before averaging, so the very top of the range is not distinguished. This is a deliberate trade: it makes the archetype robust to a few extreme tracts at the cost of top-band resolution for premium brands. A gentler or per-brand clip is listed under [future methodology work](#future-methodology-work).

## Computing distance

For each tract and each feature, the method computes a **standardized deviation** from the archetype:

```
z = (clipped_value − archetype_mean) / sigma_eff
```

where `sigma_eff` is the **effective standard deviation with a per-feature variance floor**:

```
sigma_eff = sqrt(sigma_brand² + (λ × sigma_national)²)
```

with floor parameter **λ = 0.5**.

This is a **variance floor**, not a blend toward the national spread. It raises the effective spread of a feature the brand is artificially tight on — so that one feature cannot dominate the distance — and it never reduces the spread of a feature the brand is already wider on than the nation. The quantity `λ × sigma_national` is the smallest effective spread any feature can have; `sigma_brand` above it is passed through unchanged.

The floor **reduces** the dominance of a narrow brand feature; it does **not** eliminate it. A brand’s tight clustering on a theme will still score highly on that theme across all tracts. The floor is applied per feature, so a genuinely distinctive feature keeps its weight.

λ is fixed at 0.5 for every brand, whether it has 96 occupied tracts (H Mart) or 10,299 (Dollar General). A smaller footprint estimates its own spread less reliably and could in principle warrant a stronger floor, but the tool applies the same λ to all brands for simplicity and transparency. Sample-size-aware regularization is listed under [future methodology work](#future-methodology-work).

## Group averaging

Each feature’s squared standardized deviation `z²` is averaged **within its thematic group**:

```
group_distance = mean(z² for all features in the group)
```

Missing features (e.g., a tract with no renter-occupied housing data) are skipped; the mean is taken over available features only.

Then distances are averaged **across the 6 groups** with equal weight:

```
distance = sqrt(mean(group_distance for all 6 groups))
```

A tract must have valid data in at least 4 of the 6 groups to be scored; otherwise it receives a missing value.

This within-group-then-across-group structure ensures each theme (income, density, household, race, education, retail context) counts once, not as many times as it has correlated features.

Averaging within a group is what stops correlated features from over-voting, but it also dilutes a single strongly-divergent feature. A tract that is four standard deviations off on poverty, inside the five-feature income group, contributes an average squared deviation of only about 3.2 for the whole group — the one large gap is softened by the four closer features. The detail panel surfaces the worst within-group feature next to the group mean so this is visible. A related asymmetry: **Education is a group of one feature** (bachelor’s degree or higher), so that single feature carries a full theme’s weight in the across-group average, while each income feature carries one fifth of its theme’s weight. This is a deliberate choice to keep education as its own axis, but it is worth knowing when reading the drivers.

## Distance to percentile

The distance computed above is turned into a **0–100 similarity percentile**, where 100 = the closest tract(s) to the archetype and 0 = the most distant:

```
rank = percentile rank of distance (ascending; ties get average rank)similarity = 100 × (1 − (rank − 0.5) / n_tracts)
```

For example:

-   A tract with the lowest distance ranks 1st and scores 100 (or close to it, after tie-breaking).
-   A tract with the median distance ranks ~41,000th (out of ~83,000) and scores ~50.
-   Ties are broken using average ranks, so multiple tracts with identical distance values receive the same percentile.

Similarity percentiles are **not comparable across brands**. Sweetgreen has a distinctive archetype (tight clustering), so its held-out store neighborhoods score much higher (90th percentile median in [k-fold validation](#validation-k-fold-archetype-coherence)). Starbucks is ubiquitous, so its held-out neighborhoods score lower (57th percentile). The percentile reflects the brand’s archetype clarity, not absolute neighborhood fit. For an across-brand-comparable readout, see [absolute-fit bands](#absolute-fit-bands) below.

## Worked example

Suppose a brand’s archetype has:

-   Median household income: $100,000
-   sigma\_eff for income: $10,000

A candidate tract has a (clipped) median household income of $105,000.

```
z = (105,000 − 100,000) / 10,000 = 0.5contrib = 0.5² = 0.25
```

This tract is 0.5 standard deviations above the archetype’s income — a modest match on that feature. The contribution (0.25) is averaged into the “Income, wealth & housing cost” group with the other features in that group, and the resulting group distance is combined with the other five groups to produce the final distance, which is then percentile-ranked.

## Absolute-fit bands

The percentile answers “how does this tract rank against every other tract, for this brand?” — but by construction a brand’s best tract always sits near the 100th percentile, even if that best tract is only a mediocre absolute match. To answer “how good is this match in absolute terms?”, the detail panel also shows an **absolute-fit band**: Strong, Moderate, or Weak.

The band compares the tract’s raw grouped distance `d` (under equal weighting) against the distribution of the brand’s **own occupied-tract distances**. The pipeline records four quantiles of that distribution per brand (`dist_ref`: p50, p75, p90, p95), and the band is:

-   **Strong** — `d ≤ p75` (as close as the brand’s own typical store neighborhood).
-   **Moderate** — `d ≤ p95` (within the range the brand already operates in).
-   **Weak** — farther than that.
-   **No band** — when the distance or the reference quantiles are missing (an honest blank rather than a guess).

Unlike the percentile, the band is grounded in the brand’s real footprint, so it does not inflate every brand’s top tract to “Strong.” For a [custom upload](https://docs.lookalikeiq.com/guides/custom-upload/), the browser computes the same quantiles from your own uploaded locations’ distances, so the band means the same thing for your footprint. The band is still descriptive resemblance distance, not a viability score.

## Validation: k-fold archetype coherence

To check whether the resemblance score actually captures anything meaningful, the pipeline uses **out-of-sample k-fold validation**. For each of 5 folds:

1.  Hold out a deterministic ~20% of the brand’s store locations.
2.  Rebuild the archetype using only the remaining ~80% of stores.
3.  Score every tract with this reduced archetype.
4.  Record the percentile rank of the held-out stores’ neighborhoods.

A high median held-out percentile means the archetype is coherent — the method re-finds the brand’s real neighborhoods even without seeing them. A low percentile means the brand’s locations are scattered across many neighborhood types.

The folds are **blocked by county**: whole counties are assigned to a fold together, rather than individual stores being split at random. This matters because a held-out store’s same-county neighbors have nearly identical demographics; if they stayed in training, the archetype would “recognize” the held-out neighborhood partly because it had already seen its next-door tracts, inflating the coherence number. County blocking removes that same-county leakage, so the number reported is honest.

### Coherence scores (median held-out percentile, county-blocked)

Brand

Coherence

Interpretation

Whole Foods

83rd

Distinctive archetype; high signal.

Trader Joe’s

77th

Distinctive.

Sprouts

77th

Distinctive.

Costco

67th

Diffuse footprint.

Chipotle

63rd

Diffuse footprint.

Starbucks

57th

Ubiquitous; weak signal (operates in many neighborhood types).

Dollar General

77th

Distinctive rural/small-town signal.

Tractor Supply Co.

75th

Distinctive rural/exurban signal.

Sweetgreen

90th

Very distinctive urban signal.

H Mart

84th

Distinctive metro-community signal.

Cracker Barrel

73rd

Moderate.

County blocking barely moved the original six-brand numbers from the earlier stride-based folds. That is itself an honest result: most of these chains span hundreds to over a thousand counties, so holding out whole counties still leaves demographically similar training data. Same-county autocorrelation was not heavily inflating coherence for these multi-metro brands. A brand concentrated in a handful of counties would show a larger drop.

The app displays each brand’s coherence score so you know how much weight to place on resemblance for each brand. Starbucks’ low coherence doesn’t mean the tool is broken; it reflects reality — Starbucks is strategically designed to be everywhere.

## Explainability: per-feature contributions

When you click a tract in the detail panel, the app computes which features drive the match. It reports:

-   **“Closest features”** — the features where the tract most closely matches the archetype (smallest z²).
-   **“Where it differs — check these”** — the features where the tract deviates most (largest z²).

Each feature shows the tract’s value, the archetype’s mean, and the standardized deviation (z). These match the Python pipeline exactly — the browser scorer is a faithful re-implementation of `pipeline/build_scores.py` and is validated by a consistency test against all built-in brands.

## How group weighting works (tuning scenario assumptions)

The detail panel includes sliders to adjust each group’s weight (0–2×). The weights are applied during group averaging:

```
distance = sqrt(weighted_mean(group_distances))
```

where each group’s contribution is scaled by its weight. A weight of 0 turns off the group entirely; the affected features never appear in explanations. A weight > 1 amplifies that theme.

These weights are your **scenario assumptions**, not learned feature importance or causation. They exist to help you explore “what if this theme mattered more to this brand?” They do not change the underlying data or the archetype — only the distance calculation.

Preset scenarios (Balanced, Affluence-led, Urbanism-led, Community-led) adjust all six group weights as shortcuts.

Each group also has an **advanced per-feature layer**: you can set a weight on a single feature within a group. The effective weight of a feature is its group’s weight multiplied by its per-feature weight, and the within-group aggregation becomes a weighted mean of the squared deviations. With every per-feature weight left at its default of 1, the scores are exactly the equal-weight scores above, so the advanced layer only changes results when you deliberately move a per-feature slider.

## Custom uploads: the scoring stays the same

When you [upload your own store data](https://docs.lookalikeiq.com/guides/custom-upload/), the scoring math is identical to the built-in brands:

1.  Each point is assigned to the **nearest tract centroid**. This is an approximation of the pipeline’s step, which assigns each point to the tract **polygon** that contains it (in an equal-area projection). The two agree for almost every point and disagree only for a point that sits inside one large tract but nearer a small neighboring tract’s centroid. The archetype-mean effect of the rare disagreement is negligible; the scoring after assignment is exactly the same formula.
2.  The archetype is computed as the mean over unique occupied tracts.
3.  If you provide a performance column (revenue, sales, etc.), the archetype is a weighted mean, leaning toward your higher-performing stores.
4.  If you provide a status column (closed, failed, low), a second “failure” archetype is built, and tracts are scored as:
    
    ```
    score = resemblance_to_winners − (penalty strength) × resemblance_to_failures
    ```
    
5.  The result is re-expressed as a 0–100 percentile, just like the built-in brands, and an absolute-fit band is computed from your own uploaded locations’ distances.

Winners are shown as colored dots; failures as red-ringed dots. The score is still descriptive — the labels are your own ground truth, not a sales prediction model. A custom upload can also be [compared against a built-in brand](https://docs.lookalikeiq.com/guides/compare-brands/) — the compare view uses the upload’s plain resemblance percentiles, not the avoid-failures score.

## Browser scoring: client-side re-implementation

The entire scoring pipeline is re-implemented in JavaScript (`web/src/lib/scoring.ts`) so that:

-   The detail panel can show per-feature explanations consistent with the map.
-   Custom uploads compute the archetype and scores in the browser. In the authenticated product, the source file, normalized rows, metric definition, tract assignments, and analysis lineage are persisted privately so teams can return to a reproducible version.

The browser scorer reproduces the Python pipeline’s results to >99.9% accuracy across all built-in brands (within 1 percentile point for >99.9% of tracts; max difference ≤ 2 points). This is verified by `web/src/lib/consistency.test.ts` at each build.

## Future methodology work

Several of the design choices above are deliberate simplifications with known trade-offs. Each of the following would change scores and so needs its own validation before shipping; they are recorded here as candidate improvements, not defects:

-   **Store-count-weighted archetype.** Weight each occupied tract by how many of the brand’s stores it holds, so the profile reflects where store volume concentrates rather than only which neighborhood types the brand occupies. The store-to-tract assignments already exist; this is a weighting change (see the [equal-weighting note](#building-the-archetype)).
-   **Per-brand or gentler winsorization.** Clip each brand against its own distribution, or clip more gently, so a premium brand’s above-p99 archetype is not flattened (see the [clipping ceiling note](#clipping-extremes)).
-   **Sample-size-aware regularization.** Scale the variance-floor λ by footprint size, applying a stronger floor to brands with few occupied tracts whose own spread is estimated less reliably (see the [fixed-floor note](#computing-distance)).
-   **Multi-archetype clustering for diffuse brands.** For a brand whose stores span very different neighborhood types (low coherence, like Starbucks), a single mean archetype is an “average neighborhood” that may match no real operating pattern. Clustering the occupied tracts into two or three archetypes and scoring against the nearest would describe such brands more faithfully.
-   **Trajectory: “becoming like the archetype.”** Add a second ACS vintage and show resemblance drift per tract, describing observed demographic change (not future sales). This is deferred because ACS 2018 5-year uses 2010 tract boundaries while 2023 uses 2020 boundaries; an honest trend signal first needs a 2010-to-2020 tract crosswalk (interpolation weights), which is a substantial project of its own.

## Summary: what the score means

A tract’s similarity percentile reflects how closely its observable census demographics and commercial context resemble the brand’s archetype. High scores mean “looks like the neighborhoods where this brand already is.” They do not predict whether a store would be profitable, generate foot traffic, or succeed in that location. Use the resemblance score as a triage tool — a way to narrow a regional search from tens of thousands of tracts to hundreds of strong candidates — and combine it with local knowledge, real-estate expertise, and on-the-ground scouting.

Canonical URL: https://docs.lookalikeiq.com/method/how-it-works/
