Skip to main content

10  How far do people travel to Hubbard Hall?

The donor-geography chapter asked where the money comes from; this one asks where the people come from. Counts are registrations by the registering household’s distance band (straight-line miles — see Methodology); registrations without a geocodable address (about 2%) are dropped.

Code
import pandas as pd
import plotly.express as px
from hh.io import read_parquet
from hh.viz import style, pct
from hh.analytics.timing import WEEKDAY_ORDER

BANDS = ["[0, 1)", "[1, 5)", "[5, 10)", "[10, 20)", "[20, ∞)"]
BAND_COLORS = dict(zip(BANDS, ["#b7d3f6", "#6da7ec", "#3987e5", "#184f95", "#0d366b"]))

regs = read_parquet("processed", "registrations_enriched.parquet")
cat_order = ["theater", "music", "dance", "opera", "class"]
named = regs[regs["event_minorcat"].isin(cat_order)].assign(
    cat=lambda d: d["event_minorcat"]
)
combo = pd.concat([named, regs[regs["event_majorcat"] == "class"].assign(cat="class")])
combo = combo.dropna(subset=["distance_band"])

band_cat = (
    combo.groupby(["cat", "distance_band"], observed=True)
    .size()
    .unstack("distance_band")
    .reindex(index=cat_order, columns=BANDS)
    .fillna(0)
    .astype(int)
)
w10 = (band_cat[BANDS[:3]].sum(axis=1) / band_cat.sum(axis=1))
far = band_cat[BANDS[-1]] / band_cat.sum(axis=1)

Registrations by distance band and programming type:

Code
t = band_cat.copy()
t["within 10 mi"] = w10
t.style.format("{:,}", subset=BANDS).format({"within 10 mi": pct})
distance_band [0, 1) [1, 5) [5, 10) [10, 20) [20, ∞) within 10 mi
cat            
theater 1175 1243 1255 1016 2001 54.9%
music 619 743 679 555 836 59.5%
dance 62 66 106 38 35 76.2%
opera 233 273 256 202 502 52.0%
class 3393 3252 3597 1851 1418 75.8%
Code
share = (
    band_cat.div(band_cat.sum(axis=1), axis=0)
    .reset_index()
    .melt(id_vars="cat", var_name="distance_band", value_name="share")
)
fig = px.bar(
    share, x="cat", y="share", color="distance_band",
    color_discrete_map=BAND_COLORS,
    category_orders={"cat": cat_order, "distance_band": BANDS},
)
fig.update_layout(
    xaxis_title="", yaxis_title="Share of registrations", legend_title="Distance band"
)
fig.update_yaxes(tickformat=".0%")
fig.update_traces(hovertemplate="%{y:.0%}<extra>%{fullData.name}</extra>")
style(fig).show()
Figure 10.1: Where each programming type’s registrations come from (share by distance band)

Classes serve the immediate community — 76% of class registrations come from within 10 miles (dance is similar at 76%). Performances pull from a much wider region: only 55% of theater, 59% of music, and 52% of opera registrations are that local, and roughly a third of theater (30%) and opera (34%) registrations come from beyond 20 miles.

10.1 Do weekend theater audiences travel differently?

For theater only, registrations from each distance band on each day of the week — “do Friday/Saturday audiences travel farther than Sunday’s?”

Code
theater = regs[regs["event_minorcat"] == "theater"].dropna(subset=["weekday", "distance_band"])
day_band = (
    theater.groupby(["weekday", "distance_band"], observed=True)
    .size()
    .unstack("distance_band")
    .reindex(index=WEEKDAY_ORDER, columns=BANDS)
    .fillna(0)
    .astype(int)
)
far_day = (day_band[BANDS[-1]] / day_band.sum(axis=1))
t = day_band.copy()
t["beyond 20 mi"] = far_day
t.style.format("{:,}", subset=BANDS).format({"beyond 20 mi": pct})
distance_band [0, 1) [1, 5) [5, 10) [10, 20) [20, ∞) beyond 20 mi
weekday            
Monday 0 0 0 0 0 nan%
Tuesday 3 6 6 4 9 32.1%
Wednesday 6 5 5 1 2 10.5%
Thursday 7 16 10 7 7 14.9%
Friday 332 412 342 250 434 24.5%
Saturday 555 533 557 425 851 29.1%
Sunday 272 271 335 329 698 36.6%

Sunday draws the farthest-traveling theater audience: 37% of Sunday registrations come from beyond 20 miles, versus 25% on Friday and 29% on Saturday. Friday is the most local night. That fits the within-run finding in When do people come to Hubbard Hall?: the Sunday matinee is not a neighborhood habit — it is when out-of-area visitors come.