Skip to main content

2  Where do donors live?

How far do Hubbard Hall’s donors live from the hall? Distances are straight-line (haversine) from the venue (25 E Main St, Cambridge NY) to each donor household, bucketed into half-open mile bands. Donors whose only address is a PO box are placed at their ZIP’s centroid and flagged “approximate” (excluded from the tight 1-mile count so it stays honest).

Code
import pandas as pd
import plotly.express as px
from hh.io import read_parquet
from hh.viz import style, BLUE, money, pct

households = read_parquet("processed", "households_summary.parquet")
donors = households[households["is_donor"]]
by_band = (
    donors.groupby("distance_band", observed=True)
    .agg(
        households=("id", "count"),
        dollars=("total_donations", "sum"),
        mean_gift=("total_donations", "mean"),
        median_gift=("total_donations", "median"),
    )
    .reset_index()
)
by_band["share"] = by_band["dollars"] / by_band["dollars"].sum()
by_band["cum_share"] = by_band["dollars"].cumsum() / by_band["dollars"].sum()
by_band.style.format(
    {"dollars": money, "mean_gift": money, "median_gift": money, "share": pct, "cum_share": pct}
)
  distance_band households dollars mean_gift median_gift share cum_share
0 [0, 1) 164 $119,310 $728 $92 8.2% 8.2%
1 [1, 5) 182 $470,186 $2,583 $140 32.3% 40.5%
2 [5, 10) 292 $199,136 $682 $50 13.7% 54.1%
3 [10, 20) 247 $319,359 $1,293 $25 21.9% 76.1%
4 [20, ∞) 407 $348,883 $857 $30 23.9% 100.0%
Code
fig = px.bar(by_band, x="distance_band", y="dollars", color_discrete_sequence=[BLUE])
fig.update_layout(
    showlegend=False,
    xaxis_title="Distance from Hubbard Hall (miles)",
    yaxis_title="Donated $",
)
fig.update_yaxes(tickprefix="$", tickformat=",.0f")
fig.update_traces(hovertemplate="$%{y:,.0f}<extra></extra>")
style(fig).show()
Figure 2.1: Donated dollars by distance band (miles)

The tightest band includes some approximate (ZIP-centroid) placements — its centroid share, to read the 1-mile count in context:

Code
prec = (
    donors.groupby("distance_band", observed=True)["geo_precision"]
    .apply(lambda s: (s == "centroid").mean())
    .to_frame("centroid_share")
)
prec.style.format({"centroid_share": pct})
  centroid_share
distance_band  
[0, 1) 12.8%
[1, 5) 1.6%
[5, 10) 9.9%
[10, 20) 15.0%
[20, ∞) 14.7%

2.1 Has donor geography changed over time?

The same dollars, split into roughly five-year periods and shown as shares within each period, so changes in the mix are visible regardless of overall growth. (This uses gift-level dates, unlike the lifetime household totals above.)

Code
fig = px.bar(
    gd, x="period", y="share", color="distance_band",
    color_discrete_map=BAND_COLORS, category_orders={"distance_band": BANDS},
)
fig.update_layout(
    xaxis_title="Period", yaxis_title="Share of donated $", legend_title="Distance band"
)
fig.update_yaxes(tickformat=".0%")
style(fig).show()
Figure 2.2: Donor-dollar share by distance band, by period

The within-10-mile share of donated dollars was 53% in 2013–2015, 55% in 2016–2020, and 54% in 2021–2026.